MySQL的MVCC
MySQL的MVCC
知识点助记
现在,图书馆(数据库)里面有一本非常热门的小说,很多人都想读,也有些人想在上面写。
- 如果没有MVCC,那么这一本书一次就只能借给一个人,这保证了数据安全,但效率极低(串行化)
- 有MVCC:
- 每当有人要写笔记(UPDATE),他就把当前书页复制一份,让读者在复印件上写,原书永远留在图书馆
- 当有人要读的时候(SELECT),管理员就会根据这个读者的入馆时间,给他看那一刻所有书的快照复印件
解析
MVCC (Multi-Version Concurrency Control):多版本并发控制。
主要通过为数据行创建多个版本(快照)来实现并发控制,读操作读取的是历史某个节点的的旧版本快照,写操作则创建新版本,这样做到读写分离,不会阻塞
这样可以大幅提升数据库在读写混合场景下的并发性能。
具体实现
InnoDB主要通过三个关键技术和一个核心组件来实现MVCC
- 隐藏字段
每行数据都有两个或三个隐藏字段
- DB_TRX_ID(6字节):事务ID,标识最后一个插入或更新此行数据的事务ID
- DB_ROLL_PTR(6字节):回滚指针,指向这条记录的上一个版本的地址(Undo log的位置)
- DB_ROW_ID(6字节):行ID,如果表没有主键,InnoDB会自动生成这个字段做为聚簇索引
- Undo log(回滚日志)
- 存放数据旧版本的地方,是MVCC的快照仓库
- 每当一个事务要更新某行数据的时候
- 会先将该行数据的当前版本拷贝到Undo log中
- 再用回滚指针DB_ROLL_PTR将新旧版本串联起来,生成一个版本链
- ReadView(一致性视图)
- 这是MVCC的快照生成器
- 每个事务再执行快照读(普通SELECT)时,都会生成一个ReadView。他决定了这个事务能看到哪个版本的数据
ReadView主要包含:
- m_ids: 当前系统中所有活跃(未提交)事务的ID集合
- min_trx_id: 当前系统中所有活跃事务中,最小的ID
- max_trx_id: 系统下一个将要分配事务的ID
- creator_trx_id: 创建这个ReadView的事务自己的ID
- 版本链数据访问规则
当一条数据有多个版本时,InnoDB会根据当前事务的ReadView,沿着版本链(通过DB_ROLL_PTR指针)找到它应该看到的那个版本。规则是:
- 如果版本trx_id < min_trx_id,说明该版本再当前事务启动前已提交,可见
- 如果版本trx_id > max_trx_id,说明该版本是在当前事务启动后才生成的,不可见
- 如果版本trx_id > min_trx_id && trx_id <= max_trx_id,则检查trx_id是否在m_ids(活跃事务列表)中:
- 在:说明该版本还是由未提交的事务生成的,不可见
- 不在:说明该版本事务已提交,可见
如果不可见,就顺着回滚指针DB_ROLL_PTR找到上一个版本,重复上述判断,直到找到可见的版本为止。
注意事项
MVCC不是完全不加锁,而是MVCC让普通的读操作无需加锁,性能极高,但写操作肯定要加行锁,而且SELECT … FOR UPDATE/LOCK IN SHARE MODE
这类的操作也会加锁。MVCC解决的是读写不阻塞,但写写仍然互斥。
快照读和当前读的区别
快照读:普通的SELECT语句,基于MVCC,不加锁。
SELECT * FROM table WHERE ...; -- 这是快照读 |
当前读:读取数据的最新版本,并且会加锁,阻塞其他事务
SELECT * FROM table WHERE ... LOCK IN SHARE MODE; -- 加共享锁(S锁) |
而且RC和RR级别下的ReadView的生成时机不同
- RC:每次执行快照读之前,都会生成一个新的ReadView。这就是他不能避免不可重复读的原因,因为他每次都能看到最新已提交的数据
- RR:只在第一次执行快照读时生成一个ReadView,后续整个事务都复用这个View。所以他能避免不可重复读,因为他自始至终看到的都是一个快照
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 花海!
