关于 MySQL 中 InnoDB 的 MVCC 的一些理解

2016/4/17 posted in  理论概念  

在 MySQL 中 InnoDB 的 MVCC 实现机制是给每行记录增加三个隐藏字段:

  • 6 字节的 DB_TRX_ID,用于记录最近一次插入或更新的事务标示。删除也是一种更新,用一个特殊的字节表示该行已删除。
  • 7 字节的 DB_ROLL_PTR,回滚指针,指向 undo log 记录。
  • 6 字节的 DB_ROW_ID,自增长的 row ID。 参考:InnoDB Multi-Versioning

事务的四种隔离级别

虽然说事务的隔离级别定义的是读数据的要求,而实际上也可以说是定义了写(当前读)数据的要求。

  • 未提交读 (Read Uncommitted): 允许一个事务读取到其它事务中未提交的数据,会出现脏读、不可重复读、幻读。
  • 已提交读 (Read Committed): 只能读取到已提交的数据,会出现不可重复读、幻读。
  • 可重复读 (Repeated Read): 在同一事务内相同条件下多次查询结果一致,会出现幻读。InnoDB 默认事务隔离级别。
  • 未提交读 (Serilizable): 事务串行化执行。

锁相关概念

  • 共享锁 (S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排它锁 (X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。 说明
    • 共享锁和排他锁都是行级锁,行级锁对系统开销较大,但处理高并发较好。
    • 对于 insert,update,delete 语句 InnoDB 会自动给对应数据加排它锁,对于 select 语句 InnoDB 默认不会加任何锁(因为 MySQL 引入了一种快照读的机制,减少了锁的处理,提升了并发能力),除非显示的给记录加锁。

事务执行 update 流程

  • 用排它锁锁定满足条件的行
  • 记录 redo log
  • 把该行修改前的值复制到 undo log
  • 修改当前行的值,填写事务编号,使回滚指针指向 undo log 中修改前的行
  • 当事务发生异常,则根据回滚指针从 undo log 中找出事务修改前的版本,并恢复。

InnoDB 中 MVCC 的实现原理

因为在 InnoDB 的 MVCC 实现机制中记录了每行的事务版本号,版本号按一定规则递增的。在 Repeatable Read 事务隔离级别下:

  • select: 读取创建版本号 <= 当前事务版本号,删除版本号为空或 > 当前事务版本号
  • insert: 保存当前事务版本号作为创建版本号
  • delete: 保存当前事务版本号作为删除版本号
  • update: 插入一条新纪录,保存当前事务版本号作为创建版本号;同时保存当前事务版本号为修改前记录的删除版本号。

MySQL 事务中的两种读

  • 快照读 (Snapshot Read):

    select * from table ...; 不加锁
    
  • 当前读 (Current Read):

    select * from table ... lock in share mode; 加共享锁
    select * from table ... for update; 加排它锁
    insert ...
    update ...
    delete ...
    

快照读读取出来的可能时历史数据,而 update 和 delete 需要获取实时数据。
虽然说事务的隔离级别定义的是读数据的要求,而实际上也可以说是定义了写(当前读)数据的要求。
在 RR 事务级别是存在幻读问题的,虽然快照读可以解决。但是在当前读中,MySQL 是如何解决的?
为了解决当前读中的幻读问题,MySQL 事务使用了 Next-Key 锁,这里就不详诉了,可参考 Innodb中的事务隔离级别和锁的关系 一文的后半段。