事务篇:

事务的四大特性:

1、原子性:一个事务要么全部完成要么全部不完成,不会结束在中间的某个环节而且事务再执行过程中发生错误,会被回滚到事务开始前的状态。

2、一致性:事务操作前后,数据满足完整性约束,数据库保持一致性状态。

比如:用户A和用户B在银行分别有800和600,总共1400元,用户A给用户B转账200元,分为两步,从A账户扣除200变成600,B账户增加200变成800,而不会出现A账户扣除,B账户没有增加的情况。

3、隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据的时候不会相互干扰,每个事务都有一个数据空间,对其他并发事务是隔离的。

比如:消费者之间的消费不会互相影响。

4、持久性:数据处理结束之后,对数据的修改永久化存储,即便系统故障也不会丢失

并行事务引起的问题

  • 脏读

    • 如果一个事务读到了另外一个事务未提交的修改过的数据,就是脏读。

    • 比如:事务A和事务B同时在处理,事务A对余额进行修改但是还没有提交,事务B此时读取数据,读到了事务A还没有提交的修改后的余额数据,此时就是出现了脏读,如果事务A此时因为某种原因触发回滚,导致余额回滚成初始值,那么事务B实际上获取到的就是过期的数据。

  • 不可重复读

    • 在一个事务中,多次读取同一个数据,结果出现前后两次数据不一样的情况,就是发生了不可重复读。

    • 比如:事务A和事务B在同时处理同一个数据,事务A先读取了一次这个数据,然后事务B修改了这个数据并提交,事务A再读取此数据,发现两次读取的数据不一样,就出现了不可重复度的情况。

  • 幻读

    • 在一个事务中,多次查询符合某个条件的记录数量,出现前后两次记录数量不一致的情况就意味着发生了幻读。

    • 比如:事务A和事务B同时在处理,事务A先查询了大于100w的记录,然后事务B插入了一条200w的记录,并提交,此时事务A再次查询大于100w的记录,发现记录数量多了一条就发生了幻读。

事务的隔离级别

  • 读未提交:一个事务还没有提交,他所做的变更就能被其他事务看到
  • 读并提交:一个事务所做的变更在提交之后才能被其他事务看到
  • 可重复读:一个事务执行过程中所看到的数据,一直跟这个事务启动时看到的数据一致
  • 串行化:会对记录加上读写锁,在多个事务对这条记录进行读写操作的时候,如果发生了读写冲突,后访问的事务必须等前一个事务执行完成才会继续执行。

四种隔离级别具体是如何实现的呢?

  • 读未提交:直接读取最新的数据即可。
  • 串行化:添加读写锁避免并行访问
  • 读提交:每次执行语句前都会重新生成一个ReadView。
  • 可重复读:启动事务的时候创建一个ReadView,在这个事务内的多个语句操作都只会使用刚开始事务创建的那个Read View

Read View在MVCC如何工作的?

8bf869f9f469ae4cccf625e3b6149a7

聚簇索引记录中还有两个隐藏列,如下:

  • trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里。s
  • roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

b9c22a2e97d0cf6b03142f28edef0d0

大概流程:当你访问一条记录时,数据库会根据该记录的事务 ID(trx_id)和你的 Read View 来确定你是否能看到这个记录。

  1. 如果记录的 trx_id 小于 Read View 的 min_trx_id 值,说明这个记录在当前 Read View 创建之前就已经被提交,因此对当前事务可见。

  2. 如果记录的 trx_id 大于等于 Read View 的 max_trx_id 值,说明这个记录在当前 Read View 创建之后才被提交,因此对当前事务不可见。

  3. 如果记录的 trx_id 在 Read View 的 min_trx_id 和 max_trx_id 之间,那么需要检查这个 trx_id 是否在 Read View 的 m_ids 列表中:

    a. 如果在 m_ids 列表中,说明这个事务是活跃的(还未提交),因此该记录对当前事务不可见。

    b. 如果不在 m_ids 列表中,说明这个事务已经提交,因此该记录对当前事务可见。

在可重复读的情况下,可能会发生幻读的场景

场景一:

在可重复读隔离级别下,事务 A 第一次执行普通的 select 语句时生成了一个 ReadView,之后事务 B 向表中新插入了一条 id = 5 的记录并提交。接着,事务 A 对 id = 5 这条记录进行了更新操作,在这个时刻,这条新记录的 trx_id 隐藏列的值就变成了事务 A 的事务 id,之后事务 A 再使用普通 select 语句去查询这条记录时就可以看到这条记录。

这里解释一下:实际上当事务A对id=5的记录进行更新操作之后再次查询id=5的记录的时候,此时已经变成了当前读,而不是快照读了,所以对于事务A的MVCC机制也失效。

场景二:

  • T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录。
  • T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
  • T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。