MySQL的MVCC
1. 什么是MVCC
MVCC中文是多版本并发控制,顾名思义,是一种提高并发的技术。根据逻辑,在并发上只有读读是不阻塞的,而读写、写读、写写是需要阻塞。但是在MVCC出来之后,只有写写才能阻塞,其他三个都可以并行操作。这也是InnoDB存储引擎受欢迎的原因之一。
InnoDB并不是单纯的行级锁,而是同时实现了MVCC,所以也可以认为MVCC是行级锁的另一种形式。MVCC有多种实现方式:乐观并发控制、悲观并发控制。同时MVCC只能在READ COMMIT 和 REPEATABLE READ 两个隔离级别下工作。因为 READ UNCOMMIT 总是读取最新的行,并不符合当前事务版本的数据行,SERIALIZABLE是对所有读的行都加锁。
2. READ VIEW 和 SNAPSHOT
InnoDB为实现MVCC所使用的内部快照,RR(REPEATABLE READ)隔离级别下在第一次查询时创建READ VIEW,RC(READ COMMITTED)隔离级别下会在每次查询时创建read view
先看一下相关的概念:
InnoDB支持MVCC多版本,其中READ COMMIT和REPEATABLE READ隔离级别是利用consistent read view(一致读视图)方式支持的。 所谓consistent read view就是在某一时刻给事务系统trx_sys打SNAPSHOT(快照),把当时trx_sys状态(包括活跃读写事务数组)记下来,之后的所有读操作根据其事务ID(即trx_id)与SNAPSHOT中的trx_sys的状态作比较,以此判断READ VIEW对于事务的可见性。
REDA VIEW 说白了就是为了做可见性判断的,解释为 本事务不可见的其他活跃事务 。
- READ COMMIT:事务中的每一个select都会生成一个快照。
- REPEATABLE READ:只会在该事务中第一次select生成后创建快照,将当前系统中活跃的其他事务记录下来。
3. undo log
undo log 是 MVCC 事务特性的重要组成部分,做了变更操作时就会产生 undo log,undo log 默认被记录到系统表空间中,但从5.6开始,也可以使用独立的 undo log 表空间。
undo log 记录中存储的是老版本的数据,当一个旧的事务需要读取事务时,为了能够获取老版本的数据,需要顺着undo log 的链找到满足其可见性的记录。版本链很长时,会很耗时。
- insert:insert操作在事务提交前支队当前事务可见,产生的undo log 可以在事务提交后直接删除。
- update/delete:需要维护多版本信息,在INNODB里,update和delete产生的日志会被归为update_undo。
4. 行数据的三个字段
4.1. DB_TRX_ID - 事务ID
6字节,用来表示最近一次对本行记录做修改的事务(insert/update)的标识符,也是最后一次修改本行记录的事务id。
4.2. DB_ROLL_PTR - 回滚指针
7字节,指写入回滚段的撤销日志记录,如果一行记录被更新,则撤销日志记录 包含 重建该记录被更新之前内容 所必须的信息。
4.3. DB_ROW_ID
6字节,如果没有指定主键索引,InnoDB会为每一行生成一个单调递增的ID。
5. 可见性比较算法
- 要读取的行的最后提交事务id = trx_id_current
- 当前新开事务id = new_id
- 当前新开事务创建的快照 read view 中 最早的事务id 为 up_limit_id,最迟的事务id为 low_limit_id(low_limit_id = 未开启的事务id = 当前最大事务id + 1)
步骤:
- trx_id_current < up_limit_id:新事物在读取该行记录时,稳定的事务ID小于最小活跃ID,就可以直接获取该可见的值。
- trx_id_current >= trx_id_last:记录的稳定事务在本次新事物创建之后才开启的,所以当前的值并不可见。(这里指的RR隔离级别)。
- trx_id_current <= trx_id_current <= trx_id_last:该行记录所在事务在本次新事务创建的时候处于活动状态,从up_limit_id到low_limit_id进行遍历,如果等于他们中的某个事务id,则不可见。否则可见。
- 接着3中的不可见,从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo log的版本号,将它赋值该trx_id_current,然后重新判断。
6. 当前读和快照读
RR隔离级别是通过行排他锁+MVCC实现的,不仅可以保证可重复读,还可以部分防止幻读,并非完全防止。
事务B在事务A执行中,插入了一条数据并提交,事务A再次查询,虽然读取的undo log 中不存在,但是update 或者 delete 这条数据可以成功。
6.1. 当前读
当前读指的是加锁的读或者是增删改
- select ······ lock in share mode
- select ······ for update
- insert
- update
- delete
当前读是通过record lock(记录锁) 和 gap lock(间隙锁)来实现的。
6.2. 快照读
简单的select,并不包含加锁。
快照读是通过MVCC+undo log实现的。
7. MVCC特点
- 每行数据都存在一个版本,每次数据更新时都更新该版本。
- 修改时,copy当前的版本,任意更改,各个事务之间保证隔离。
- 保存时比较版本号,如果commit成功,覆盖原来记录,如果失败,则回滚。
- 根据版本号判断是否保存成功,优点类似于乐观锁的原理。
- 事务以排他锁的形式修改原始数据。
- 把修改前的数据存放在undo log,通过回滚指针与主数据关联。
- 如果修改失败,则回复undo log 中的数据,也就是回滚。
翻译自:https://segmentfault.com/a/1190000012650596
推荐一个宝藏up主小姐姐:https://www.bilibili.com/video/BV1Vk4y1k7KQ?from=search&seid=17055943543545838942