InnoDB的锁机制深入理解(7)

悲观锁:在UPDATE执行前,SELECT后面加上FOR UPDATE来给记录加锁,保证记录在UPDATE前不被修改。SELECT ... FOR UPDATE是加上了X锁,也可以通过SELECT ... LOCK IN SHARE MODE加上S锁,来防止其他事务对该行的修改。

无论是乐观锁还是悲观锁,使用的思想都是一致的,那就是当前读。乐观锁利用当前读判断是否是最新版本,悲观锁利用当前读锁定行。
但是使用乐观锁时仍然需要非常谨慎,因为RR是可重复读的,一定不能在UPDATE之前先把版本号读取出来。

5. InnoDB对不同语句执行时的加锁状况

如果一个SQL语句要对二级索引(非主键索引)设置X模式的Record锁,InnoDB还会检索出相应的聚簇索引(主键索引)并对它们设置锁定。

5.1 SELECT ... FROM...不加锁

SELECT ... FROM是快照读取,除了SERIALIZABLE的事务隔离级别,该SQL语句执行时不会加任何锁。

SERIALIZABLE级别下,SELECT语句的执行会在遇到的索引记录上设置S模式的next-key锁。但是对于唯一索引,只锁定索引记录,而不会锁定gap。

5.2 UPDATE系列

S锁读取(SELECT ... LOCK IN SHARE MODE),X锁读取(SELECT ... FOR UPDATE)、更新UPDATE和删除DELETE这四类语句,采用的锁取决于搜索条件中使用的索引类型。

如果使用唯一索引,InnoDB仅锁定索引记录本身,不锁定间隙。

如果使用非唯一索引,或者未命中索引,InnoDB使用间隙锁或者next-key锁来锁定索引范围,这样就可以阻止其他事务插入锁定范围。

5.2.1 UPDATE语句

UPDATE ... WHERE ...在搜索遇到的每条记录上设置一个独占的next-key锁,如果是唯一索引只锁定记录。
当UPDATE修改聚簇索引时,将对受影响的二级索引采用隐式锁,隐式锁是在索引中对二级索引的记录逻辑加锁,实际上不产生锁对象,不占用内存空间。

例如update test set code=100 where id=10;执行的时候code=10的索引(code是二级索引,见文中给出的建表语句)会被加隐式锁,只有隐式锁产生冲突时才会变成显式锁(如S锁、X锁)。即此时另一个事务也去更新id=10这条记录,隐式锁就会升级为显示锁。
这样做的好处是降低了锁的开销。

UPDATE可能会导致新的普通索引的插入。当新的索引插入之前,会首先执行一次重复索引检查。在重复检查和插入时,更新操作会对受影响的二级索引记录采用共享锁定(S锁)。

5.2.2 DELETE语句

DELETE FROM ... WHERE ...在搜索遇到的每条记录上设置一个独占的next-key锁,如果是唯一索引只锁定记录。

5.3 INSERT

INSERT区别于UPDATE系列单独列出,是因为它的处理方式较为特别。

插入行之前,会设置一种插入意向锁,插入意向锁表示插入的意图。如果其它事务在要插入的位置上设置了X锁,则无法获取插入意向锁,插入操作也因此阻塞。

INSERT在插入的行上设置X锁。该锁是一个Record锁,并不是next-key锁,即只锁定记录本身,不锁定间隙,因此不会阻止其他会话在这行记录前的间隙中插入新的记录。
具体的加锁过程,见6.2。

6. 可能的死锁场景 6.1 Duplicate key error引发的死锁

并发条件下,唯一键索引冲突可能会导致死锁,这种死锁一般分为两种,一种是rollback引发,另一种是commit引发。

6.1.1 rollback引发的Duplicate key死锁

我命名为insert-insert-insert-rollback死锁

事务一事务二事务三
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values (2,2);
Query OK, 1 row affected (0.01 sec)
         
    mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values (2,2);
执行之后被阻塞,等待事务一
     
        mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test values (2,2);
执行之后被阻塞,等待事务一
 
mysql>rollback;
Query OK, 0 rows affected (0.00 sec)
         
    ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction      
        Query OK, 1 row affected (16.13 sec)  

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/d7c44bb9ed60b10c737e877a225bbadf.html