什么是 MVCC ?
- mvcc,全称 multi-version concurrency control,即多版本并发控制
- mvcc 是 mysql 的 innodb 引擎为了解决读写冲突,通过不加锁的方式而实现的一种解决冲突的处理方式
为什么需要 MVCC ?
- 数据库原生的锁
- 最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源,但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要
- 最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源,但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要
- 读写锁
- 读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥,这样就提升系统的并发能力,之后发现并发读还是不够
- 读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥,这样就提升系统的并发能力,之后发现并发读还是不够
- mvcc
- 读写之间也不冲突的方法,读取数据时通过快照的方式将数据保存下来,这样读锁就和写锁不冲突,不同的事务 session 会看到特定版本的数据,快照是一种概念模型,不同的数据库可能用不同的方式来实现这种功能
MVCC 适用于的事务隔离级别
- mvcc 只在 read_committed、repeatable_read 两个隔离级别下工作
- 其他两个隔离级别够和 mvcc 不兼容,因为 read_uncommitted 总是读取最新的数据行, 而不是符合当前事务版本的数据行,而 serializable 会对所有读取的行都加锁
MVCC 实现原理
- 实现原理依赖记录中的 3个隐式字段,undo日志 ,read view 来实现
- 隐式字段
- DB_TRX_ID:记录最近更新这条记录的事物id,大小为 6 个字节
- DB_ROLL_PTR:是一个回滚指针,用于配合 undo日志,指向上一个旧版本,大小为 7 个字节
- DB_ROW_ID:数据库默认为该行记录生成的唯一隐式主键,大小为 6 个字节(表没有主键会生成)
- 隐式字段
- 业务场景
- 基于 undo_log 生成的版本链:
- Read View 是一个数据结构,包含 4 个字段
- m_ids:当前活跃的事务编号集合
- min_trx_id:最小活跃事务编号
- max_trx_ id:预分配事务编号,当前最大事务编号 + 1
- creator_trx_id:read view 创建者的事务编号
- 这个业务场景生成的 Read View
- m_ids = (2,3,4)
- min_trx_id = 2
- max_trx_id = 5
- creator_trx_id = 4
- 版本链数据访问规则:
- 版本链的最新事物id 值为 3,就是当前的事物id
- 判断当前事务id 是否等于 creator_trx_id(4),等于说明数据就是自己这个事务更改的,可以访问
- 判断 trx_id < min_trx_id(2),成立说明数据已经提交,可以访问
- 判断 trx_ id > max_trx_id(5),成立说明该事务是在 readview 生成以后才开启,不允许访问
- 判断 min_trx_id(2) <= trx_id <= max_ trx_id(5),成立在 m_ids 数据中对比,不存在则代表数据是已提交的,可以访问
- 比较流程:
- 当前事物id 为 3,不是 creator_trx_id(4),也不 < min_trx_id(2),但是在事物id 范围内
- 事物id 为 3 无法访问,就基于 undo_log 向上寻找到事物id 为 2,找到事物id 为 2 的进行比较,不是 creator_trx_id(4),也不 < min_trx_id(2),但是在事物id 范围内
- 事物id 为 2 也无法访问,基于 undo_log 向上寻找到事物id 为 1,符合 < min_trx_id(2),可以访问数据
- 因此根据 readview 可以读取到1号事务提交的数据:张三
- 不同隔离级别下的 MVCC
- read_committed:在读已提交的隔离级别下,每次快照读都是生成最新的 readview,所以在上述场景中,事物4 第二次读取数据的时候,事物2 已提交了事物,所以在事物4 中出现了不可重复读
- repeatable_read:在可重复读的隔离级别下,readview 读取数据会沿用当前事物第一次生成的 readview,所以不会出现不可重复读的情况