https://www.zhihu.com/question/263820564

1. 快照读 与 当前读:

1.1 快照读:

MySQl 默认的隔离级别是 “可重复读” 。快照读的数据可以看作一个快照,其他事务的修改不会改变这个快照值,也就是说快照读的数据不一定是最新值。

使用场景:这就保证了”可重复读”,快照不用加锁,并发效率高。除了串行化,其他隔离级别中事务都是快照读

1.2 当前读:

当前读指的就是最新值。既然要求是最新值,那就需要进行加锁限制,所以当前读是需要加锁的,同时因为当前读一定是最新的数据,所以就无法保证 “可重复读”。

使用场景:可串行化中事务的读操作是当前读,而四种隔离级别中的所有修改(insert / update / delete ) 操作都属于当前读,修改操作是先”读”找到数据具体的位置才能进行”修改”。

2. MVCC 机制:

https://blog.csdn.net/waves_/article/details/105295060
Mvcc 是一种多版本的并发控制,能够增加读写效率
而它的实现来自于以下三部分:

2.1 隐藏字段:

在每条数据后都存在三个隐藏字段,用于 回滚 以及 快照

  • db_trx_id : 创建或修改该记录的事务id(最新的)
  • db_row_id: 隐藏主键(有主键或者唯一非空索引的话,可不存在)
  • db_roll_ptr:回滚指针(配合 undo-log 进行回滚),指向记录当前行的 undo log 信息690169-20210219225053349-2091811633.png

    2.2 undo log 回滚日志

    当事务需要读取记录的时候,如果当前行不可见,就可顺着 undo log 链找到满足可见性条件的行记录版本。

  • insert undo log : 事务对于 insert 新纪录时产生的 undo log,只在事务回滚时需要,提交后可立即丢弃

  • update undo log:事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要,只有当数据库所使用的快照中不涉及该日志记录,对应的回滚日志才会被purge线程删除。

    2.3 read view

    主要用于进行可见性判断,保存以下几个字段,就和快照一样。

  • trx_ids : 系统活跃事务 Id, ReadView 初始化时,当前未提交的其他活跃事务列表,不包括自己和已提交事务。

  • up_limit_id : 活跃事务列表中最小的id。
  • low_limit_id : 当前出现过的最大的事务id + 1,即下一个将被分配的事务ID。


    并且 read view 的产生时机不同,造成了 RC, RR 两种隔离级别的不同可见性。

  • 使用 RR ,事务在第一条 select 读操作之后,创建快照,将系统中其他的事务记录起来。

  • 使用 RC, 事务每条 select 语句都会创建一个快照

关于可见性算法:
当用户在事务中要读取某个记录时,innode 会将该行的 trx_id (最新修改该行的事务 ID) 和其中的某些变量进行比较,看这个事务是否能读取该行。 就是看这个事务能否读这行数据

假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,Read View的活跃事务列表trx_ids中最早的事务ID为up_limit_id,将在生成这个Read Vew时系统出现过的最大的事务ID+1记为low_limit_id(即还未分配的事务ID)。

  1. 如果 trx_id < up_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之前就提交了,所以该记录行的值对当前事务是可见的。跳到步骤5。
    2. 如果 trx_id >= low_limit_id, 那么表明“最新修改该行的事务”在“当前事务”创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤4。
    3. 如果 up_limit_id <= trx_id < low_limit_id, 表明“最新修改该行的事务”在“当前事务”创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表trx_ids进行查找:

(1) 假如在活跃事务列表中能找到 id 对应的事务,表明在 “当前事务” 创建快照之前,”该记录行的值”被 id 为 trx_id 的事务修改了,但没有提交。或者在 “当前事务” 创建快照之后,”该记录行的值” 被 id 为 trx_id 的事务修改了。在这个状态下,这个记录行的值对当前事务都是不可见的
(2) 在活跃事务列表中找不到,表明 id 为 trx_id 的事务在修改 该记录行的值之后,创建快照前已经提交,所以对当前事务可见。

  1. 在该记录行的 DB_ROLL_PTR 指针所指向的undo log回滚段中,取出最新的的旧事务号DB_TRX_ID, 将它赋给trx_id,然后跳到步骤1重新开始判断。
    5. 将该可见行的值返回。

对于 RC 和 RR 存在 不同的 readview 生成机制:

  • 对于 RC, 事务 begin 之后,每次 select 语句,快照都会重置,会重新生成一个快照。
  • 对于 RR,事务 begin 之后,执行第一条 select 时,才会创造一个快照,将其他活跃的事务记录起来,直到事务结束。

3. 演示 undo log

image.png

4.演示可见性算法:

事务 1001 事务 1002 事务 1003
begin
begin
执行select:生成 read view,生成 db_trx_id(上次对数据进行操作的事务id) = 1000; 当前活跃的事务db_trx_id 列表:1002;当前最小的事务 low_limit_id : 1002 ;当前将要分配的下一个事务 up_limit_id : 1003; 此时 db_trx_id < 1002; 说明事务开启前,上一个事务已修改完成,已经commit,所以可以读到此数据
执行 select :生成 readview 此时查询的数据行的 db_trx_id = 1000; 而 当前活跃的 事务db_trx_id : 1001;当前最小的活跃事务 low_limit_id : 1001;下一个将要分配的事务up_limit_id : 1003;此时 db_trx_id < low_limit_id = 1001;说明在这个事务开启前,上一个事务已经完成提交,可以读取次数据
begin
update field = 1
commit
select 数据: (1)重新生成 read view此时 ,由于事务 1003更改了数据,所以db_trx_id = 1003, 而此时活跃的事务列表 trx_id : 1003, 此时活跃的事务中最小的事务 low_limit_id = 1003, 此时将要分配的下一个 up_limit_id =1004,而 1003 <= 1003 < 1004,即 low < db < up。而此时 db_trx_id 在活跃事务列表中,此时不可读。