前提: mysql:8.0.20
非锁引
session1:begin;
session1:update user set name='aaa' where no='222';
session2:begin;
session2:update user set name='bbb' where no='333';(不阻塞)
session1:begin;
session1:update user set name='aaa' where no='222';
session2:begin;
session2:select * from user where no='333' for update;(阻塞)
解析:
由于no无锁引,因此只能走聚簇索引,进行全部扫描。加锁如下:
注:如果一个条件无法通过索引快速过滤,那么存储引擎层面就会将所有记录加锁后返回,然后由MySQL Server层进行过滤。因此也就把所有的记录,都锁上了。
但在实际中,MySQL做了优化,如同前面作用1所提到的。在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁 (违背了2PL的约束)。这样做,保证了最后只会持有满足条件记录上的锁,但是每条记录的加锁操作还是不能省略的
其实MySQL支持3种类型的读语句:
- 普通读(也称一致性读,英文名:Consistent Read)
这个就是指普通的SELECT语句,在末尾不加FOR UPDATE或者LOCK IN SHARE MODE的SELECT语句。普通读的执行方式是生成ReadView直接利用MVCC机制来进行读取,并不会对记录进行加锁。 - 锁定读(英文名:Locking Read)
这个就是事务在读取记录之前,需要先获取该记录对应的锁。当然,获取什么类型的锁取决于当前事务的隔离级别、语句的执行计划、查询条件等因素,具体可参见 - 半一致性读(英文名:Semi-Consistent Read)
这是一种夹在普通读和锁定读之间的一种读取方式。它只在READ COMMITTED隔离级别下(或者在开启了innodb_locks_unsafe_for_binlog系统变量的情况下)使用UPDATE语句时才会使用。具体的含义就是当UPDATE语句读取已经被其他事务加了锁的记录时,InnoDB会将该记录的最新提交的版本读出来,然后判断该版本是否与UPDATE语句中的WHERE条件相匹配,如果不匹配则不对该记录加锁,从而跳到下一条记录;如果匹配则再次读取该记录并对其进行加锁。这样子处理只是为了让UPDATE语句尽量少被别的语句阻塞。
小贴士:
半一致性读只适用于对聚簇索引记录加锁的情况,并不适用于对二级索引记录加锁的情况。
很显然,我们上边的例子中是因为事务T2执行UPDATE语句时使用了半一致性读,判断id列值为2这条记录的最新提交版本的num列值均不为UPDATE语句中WHERE条件中的'333',所以直接就跳过它们,不对它们加锁了。
有锁引
session1:begin;
session1:update user set name='aaa' where name='zhang';
session2:begin;
session2:update user set name='bbb' where name='zhang1';(不阻塞)
session1:begin;
session1:update user set name='aaa' where name='zhang';
session2:begin;
session2:select * from user where name ='zhang1' for update;(不阻塞)
结论: 加锁引的重要性