在 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 中找出事务修改前的版本,并恢复。