基于 隐藏字段、undo log、read view
多版本 指的是 undo log 的回滚链
并发控制管理 read view+隐藏列
处理读-写冲突
,读使用快照读,当前读是加锁的操作。
不加锁的读
select * from user where...
加锁
隐藏字段 trx_id roll_pointer
undo日志
中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。事务id
为8
,那么此刻该条记录的示意图如下所示:undo log 版本链
每次对记录进行改动
,都会记录一条undo日志,每条undo日志也都有一个roll_pointer
属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志
都连起来,串成一个链表:
对该记录每次更新后,都会将旧值放到一条undo日志
中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer
属性连接成一个链表,我们把这个链表称之为版本链
,版本链的头节点就是当前记录最新的值。
read view 在一个事物在使用mvcc机制进行快照读操作时产生的读视图
mvcc 针对 读已提交 可重复读
id列表
注意:low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。比如,现在有id为 1 ,2 , 3 这三个事务,之后id为 3 的事务提交了。那么一个新的读事务在生成ReadView时,trx_ids就包括 1 和 2 ,up_limit_id的值就是 1 ,low_limit_id的值就是 4 。
例子
trx_ids为tr2、tr3、tr:5和trx8的集合,系统的最大事务ID (low_limit_id)为trx8+1(如果之前没有其他的新增事务),活跃的最小事务ID (up_limit_id)为trx2。
lnnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。
了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:
在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次Read View。
如表所示:
事务 | 说明 |
---|---|
begin; | |
select * from student where id >2; | 获取一次Read View |
… | |
select * from student where id >2; | 获取一次Read View |
commit; |
此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。
当隔离级别为可重复读的时候,就避免了不可重复读,这是因为·一个事务只在第一次 SELECT 的时候会获取一次 Read View
,而后面所有的 SELECT 都会复用这个 Read View,如下表所示: