InnoDB多版本并发控制
目的:快照读不用加锁,降低开销。
实现原理
MVCC的实现是通过保存数据在某个时间的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
InnoDB的MVCC,在每一行记录后面都保存了几个隐藏字段:
- db_row_id,行id,用来生成默认聚簇索引(聚簇索引,保存的数据在物理磁盘中按顺序保存,这样相关数据保存在一起,提高查询速度)
- db_trx_id,产生当前记录项的事务id
- db_roll_ptr,undo log指针,指向当前记录项的undo log
- deleted_bit,删除标记位,用于标识该记录是否是删除记录
每开始一个新的事务时,系统版本号会自动递增,而事务开始时刻的系统版本号会作为事务id,也就是db_trx_id。
事务开始时,会创建一个read_view,用于记录当前所有活跃且未提交的事务id。
read_view中包含有最小未提交的事务id(minId)、最大未提交事务id(maxId)和当前所有活跃事务id(trx_ids)。
其中,记录行的事务id小于minId,则表示此行记录在事务开始之前就已提交,是可见的;
记录行的事务id大于maxId或者不在trx_ids中,则表示此行记录是其它事务创建的,是不可见的;
记录行的事务id大于等于minId且小于等于maxId且在trx_ids中,若记录行的事务id等于当前事务id,则表明是当前事务创建的,是可见的,若记录行的事务id不等于当前事务id,则表明不是当前事务创建的,不可见。
若此记录行deleted_bit为0,表示是可见的有效记录。若此记录行deleted_bit为1,表示是可见的无效记录(已被删除)。
举例说明
假设当前表如下,
| id | code | db_trx_id | db_roll_ptr | deleted_bit |
|---|---|---|---|---|
| 1 | 2 | 1 | null | 0 |
此时,开启事务A,事务id为2,其read_view的minId不存在,maxId也不存在,trx_ids为[]。继续开启事务B,事务id为3,其read_view的minId为2,maxId也为2,trx_ids为[2]。
-
update
事务A执行更新sql,update xxx set code = 3 where id = 1;此时表为:
id code db_trx_id db_roll_ptr deleted_bit 1 2 1 null 1 1 3 2 1 0 事务B执行查询sql,
select * from xxx where id = 1;查到id=1,code=3这行记录,发现db_trx_id为2,满足2>=minId(B) && 2<=maxId(B) && 2属于trx_ids,所以断定此行记录是其他未提交事务创建的,不应该可见。继续根据db_roll_ptr查到此行记录的上一个版本id=1,code=2这行记录,发现db_trx_id为1,满足1<minId(B),说明此行记录是在事务B开始之前就已提交的,应该可见。所以这条查询sql返回的结果是id=1,code=2。
-
delete 事务A执行删除sql,
delete from xxx where id = 1;此时表为:
id code db_trx_id db_roll_ptr deleted_bit 1 2 1 null 1 1 2 2 1 1 事务B执行查询sql,
select * from xxx where id = 1;查到id=1,code=2这行记录,发现db_trx_id为2,满足2>=minId(B) && 2<=maxId(B) && 2属于trx_ids,所以断定此行记录是其他未提交事务修改的,不应该可见。继续根据db_roll_ptr查到此行记录的上一个版本id=1,code=2,db_trx_id=1这行记录,发现db_trx_id为1,满足1<minId(B),说明此行记录是在事务B开始之前就已提交的,应该可见。所以这条查询sql返回的结果是id=1,code=2。
-
insert 事务A执行新增sql,
insert into xxx (id, code) values (2, 3);此时表为:
id code db_trx_id db_roll_ptr deleted_bit 1 2 1 null 0 2 3 2 null 0 事务B执行查询sql,
select * from xxx where id = 2;查到id=2,code=3这行记录,发现db_trx_id为2,满足2>=minId(B) && 2<=maxId(B) && 2属于trx_ids,所以断定此行记录是其他未提交事务创建的,不应该可见。而db_roll_ptr为null,说明此行记录新插入的没有上一个版本,所以这条查询sql返回的结果是空。