MySQL InnoDB多版本并发控制

1,212 阅读4分钟

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返回的结果是空。