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

2016/4/17 posted in  理论概念 comments

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

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

MySQL 事务中的两种读

  • 快照读 (Snapshot Read):
    猜测:在事务中进行 select 时,事务会记录当前正在读取的表的最大 DB_ROW_ID,以后就会读取不大于该 DB_RWO_ID 的记录。

    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 锁。

不可重复读与幻读的区别

在使用行锁机制的前提下(当前读),可重复读在读取时对读到的行加锁,这样就可以防止其它事务修改或删除数据;但是无法阻止其它事务新增数据,当事务再次执行相同的 SQL 读取数据时,确发现多出一条,这就是幻读。

事务的四种隔离级别

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

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

事务执行 update 流程

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