| select * from table ....; |
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
| select * from table where ? lock in share mode; select * from table where ? for update; insert; update ; delete; |
事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。
写("当前读")
事务的隔离级别中虽然只定义了读数据的要求,实际上这也可以说是写数据的要求。上文的“读”,实际是讲的快照读;而这里说的“写”就是当前读了。
为了解决当前读中的幻读问题,MySQL事务使用了Next-Key锁。
Next-Key锁
Next-Key锁是行锁和GAP(间隙锁)的合并,行锁上文已经介绍了,接下来说下GAP间隙锁。
行锁可以防止不同事务版本的数据修改提交时造成数据冲突的情况。但如何避免别的事务插入数据就成了问题。我们可以看看RR级别和RC级别的对比
RC级别:
事务A 事务B
| begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); commit; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 10 初三二班 30 RR级别: 事务A 事务B begin; begin; select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三二班 30 update class_teacher set class_name='初三四班' where teacher_id=30; insert into class_teacher values (null,'初三二班',30); waiting.... select id,class_name,teacher_id from class_teacher where teacher_id=30; id class_name teacher_id 2 初三四班 30 commit; |
事务Acommit后,事务B的insert执行。
通过对比我们可以发现,在RC级别中,事务A修改了所有teacher_id=30的数据,但是当事务Binsert进新数据后,事务A发现莫名其妙多了一行teacher_id=30的数据,而且没有被之前的update语句所修改,这就是“当前读”的幻读。
RR级别中,事务A在update后加锁,事务B无法插入新数据,这样事务A在update前后读的数据保持一致,避免了幻读。这个锁,就是Gap锁。
MySQL是这么实现的:
在class_teacher这张表中,teacher_id是个索引,那么它就会维护一套B+树的数据关系,为了简化,我们用链表结构来表达(实际上是个树形结构,但原理相同)










