MySQL MVCC机制!
MySQL MVCC机制!
月伴飞鱼MVCC是为了解决 读-写 之间阻塞的问题(排他锁会阻塞读操作),写操作还是需要加锁(
Next-Key Lock
)。如果没有MVCC,那么修改数据的操作会加排它锁,其它的读写操作都会阻塞,这样的话效率会比较低。
MVCC通过Undo Log + Read View进行数据读取。
- Undo Log保存了历史快照。
- Read View规则判断当前版本的数据是否可见。
快照读与当前读
快照读:
快照读读取的是快照数据。
- 不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读。
快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。
- 既然是基于多版本,那么快照读可能读到的 并不一定是数据的最新版本,而 有可能是之前的历史版本。
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。
1 | SELECT * FROM player WHERE ... |
当前读:
读取的是记录的 最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
加锁的 SELECT,或者对数据进行增删改都会进行当前读。
1 | SELECT * FROM student LOCK IN SHARE MODE; # 共享锁 |
Undo Log版本链
InnoDB 聚簇索引记录中包含 3 个隐藏的列:
DB_ROW_ID
(隐藏的自增 ID):
- 如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
DB_TRX_ID
(事务ID):
- 记录最近更新这条行记录的事务 ID,大小为 6 个字节。
DB_ROLL_PTR
(回滚指针) :
- 指向该行回滚段的指针,大小为 7 个字节。
- 该行记录上所有旧版本,在 undo 中都通过链表的形式组织。
每次对记录进行改动,都会记录一条undo日志,每条undo日志都有一个
roll_pointer
属性。将这些undo日志都连起来,串成一个链表,这个链表称之为 版本链。
- 版本链的 头节点 就是当前记录 最新的值。
- 每个版本中包含生成该版本时对应的 事务id。
ReadView
ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。
判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。
ReadView主要包含4个内容:
creator_trx_id
:
- 创建这个 Read View 的事务 ID。
trx_ids
:
- 在生成ReadView时当前系统中 活跃的 读写事务的事务id列表(活跃:启动了但还没提交)。
up_limit_id
:
- 活跃的 事务中 最小的事务 ID。
low_limit_id
:
- 表示生成ReadView时系统中应该分配给下一个事务的id 值。
ReadView规则:
如果被访问版本的
trx_id
属性值与 ReadView 中的creator_trx_id
值 相同:
- 意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的
trx_id
属性值 小于 ReadView 中的up_limit_id
值:
- 表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的
trx_id
属性值 大于或等于 ReadView中的low_limit_id
值:
- 表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本 不可以 被当前事务访问。
如果被访问版本的
trx_id
属性值在 ReadView 的up_limit_id
和low_limit_id
之间。需要判断一下
trx_id
属性值是不是在trx_ids
列表中。
如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。
如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
MVCC整体操作流程
首先 获取事务自己的版本号,也就是事务 ID。
获取 ReadView。
查询得到的数据,然后与 ReadView 中的事务版本号进行比较。
如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照。
最后 返回符合规则的数据。
如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性。
- 依此类推,直到版本链中的最后一个版本。
如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
READ COMMITTED事务,在每次查询开始时都会生成一个独立的ReadView。
REPEATABLE READ事务,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。
MVCC幻读被彻底解决了吗
可重复读隔离级别(默认隔离级),根据不同的查询方式,分别提出了避免幻读的方案:
- 针对快照读(普通
select
语句),是通过 MVCC 方式解决了幻读。- 针对当前读(
select ... for update
等语句),是通过next-key lock
(记录锁+间隙锁)方式解决了幻读。