最近想回老家工作,也没咋看就找工作,遇到了好多问MVCC的,一直讲的不太清晰,所以写个笔记记一下MVCC的理解。有什么不对的欢迎评论指正。
MVCC保证事务的隔离性
知识准备
binlog(归档日志)
数据库记录操作的日志,比如什么时候做了什么操作,用户数据同步等
redo log(重做日志)保证事务的持久性
InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力,为append写入,用于数据库恢复
undo log(回滚日志)保证事务的原子性
回滚日志,记录数据在什么时候被什么事务做了什么操作
隐藏字段
DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id
DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log
DB_ROW_ID(6字节):隐藏主键,如果没有设置主键且该表没有唯一非空索引时,就会多一个隐藏主键
read view
m_low_limit_id:系统未分配的下一个事务id,如当前事务中最大值为N,则m_low_limit_id=n+1
m_up_limit_id:活跃事务列表中最小事务id
m_ids:活跃事务id列表,即当前未提交的事务的id集合
m_creator_trx_id:创建该 Read View 的事务id
二阶段提交
保证redo log和bin log都有记录或都没记录
可见性算法,判断当前事务是否可以获取到改动后的数据
如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5
m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的
如果 m_up_limit_id <= DB_TRX_ID < m_low_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)
如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
在该记录行的 DB_ROLL_PTR 指针所指向的
undo log取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空
当前读 读取数据库最新版本数据
- select lock in share mode
- select for update
- update
- insert
- delete
快照读 读取数据库历史版本数据
- select
事务隔离级别
- READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
测试会发现 在READ-COMMITTED和REPEATABLE-READ下,两个事务操作数据读取结果是不一样的;
READ-COMMITTED: 每次进行快照读的时候都会重新生成readview,所以每次都可以获取到最新数据
REPEATABLE-READ: 只有当前事务第一次进行快照读的时候生成readview,之后的快照读都会使用之前的readview
但是MVCC无法解决幻读的问题
比如AB事务同时操作数据库表C
| A | B |
| SELECT * FROM C WHERE ***(假如此时查到3条) | |
| INSERT INTO C VALUES(***) (此时B插入一条) | |
| SELECT * FROM C WHERE ***(此时还是查到3条) | |
|
UPDEAT C SET *** WHERE ***(此时A想去更新刚刚查询到的3条数据) 但是结果是更新了4条 ,吧B刚刚插入的这条更新了 |
MVCC是无法解决幻读问题的
只能通过加锁来避免
| A | B |
| SELECT * FROM C WHERE *** FOR UPDATE(假如此时查到3条,这个时候已经加锁了) | |
| INSERT INTO C VALUES(***) (此时B插入一条,但是被阻塞了) | |
| SELECT * FROM C WHERE ***(此时还是查到3条) | |
|
UPDEAT C SET *** WHERE ***(此时A想去更新刚刚查询到的3条数据) 结果是更新了3条 |
幻读问题产生原因
如果事务中操作都是快照度,则不会产生幻读
如果事务中是快照读+当前读,则有可能产生幻读
本文深入解析MVCC(多版本并发控制)如何确保事务隔离性,包括binlog、redolog及undolog的作用,ReadView的工作机制,以及不同事务隔离级别的特点。并通过示例说明MVCC在解决幻读问题上的局限性。
2452

被折叠的 条评论
为什么被折叠?



