1、普通读
简单的select操作(不包括 select ... lock in share mode, select ... for update)。
- Read Committed隔离级别:每次select都生成一个快照读。
- Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。
普通读的执行方式是生成 ReadView,直接利用 MVCC 机制来进行读取,并不会对记录进行加锁。
对于 SERIALIZABLE 隔离级别来说,如果 autocommit 系统变量被设置为OFF,那普通读的语句会转变为锁定读,和在普通的 SELECT 语句后边加 LOCK IN SHARE MODE 达成的效果一样。
实现方式
普通读是通过 undo log + MVCC 来实现的
下图右侧黄色部分是数据:一行数据记录,主键 ID 是 10,object = 'Goland' ,被 update 更新为 object = 'Python' 。
事务会先使用“排他锁”锁定该行,将该行当前的值复制到 undo log 中,然后再真正地修改当前行的值,最后填写事务的 DB_TRX_ID ,使用回滚指针 DB_ROLL_PTR 指向 undo log 中修改前的行。
这里解释一下 DB_TRX_ID 和 DB_ROLL_PTR 所代表的含义:
- DB_TRX_ID : 6 字节 DB_TRX_ID 字段,表示最后更新的事务 id ( update , delete , insert ) 。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已软删除。
- DB_ROLL_PTR : 7 字节回滚指针,指向前一个版本的 undo log 记录,组成 undo 链表。如果更新了行,则撤消日志记录包含在更新行之前重建行内容所需的信息。
insert undo log 只在事务回滚时需要, 事务提交就可以删掉了。update undo log 包括 update 和 delete , 回滚和快照读都需要。
2、当前读
当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
如以下这些 SQL 类型:
- select ... lock in share mode 、
- select ... for update、
- update 、delete 、insert
例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。
实现方式
- 行锁(Record Lock):锁直接加在索引记录上面。
- 间隙锁(Gap Lock):是 Innodb 为了解决幻读问题时引入的锁机制,所以只有在 Read Repeatable 、Serializable 隔离级别才有。
- Next-Key Lock :Record Lock + Gap Lock,锁定一个范围并且锁定记录本身 。
下面通过一个例子来说明当前读的实现方式,例如下面这条 SQL:
delete from T where age = 7;
进行下面的实验:
测试可知 delete from T where age = 7; 语句在 age 上的加锁区间为 (4,10) ,图解如下: