7)InnoDB加锁案例分析

前面分析过MySQL的锁分类方式,MyISAM仅支持表锁,不支持行锁,所以不需要做过多分析。这篇文章就针对InnoDB分析一下加锁的案例。

创建一个表student,带有字段id,name,age,address,其中id是主键,name是唯一索引,age是普通索引,address没有索引。

初始化数据
mysql> select * from student;
+----+-------+-----+---------+
| id | name  | age | address |
+----+-------+-----+---------+
|  1 | gary  |  30 | suzhou  |
|  2 | kerry |  30 | nantong |
+----+-------+-----+---------+
2 rows in set (0.00 sec)

非索引项的锁升级表锁

在session 1里面查询address = 'nantong'的记录,并加上排他锁。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student where address = 'nantong' for update;
+----+-------+-----+---------+
| id | name  | age | address |
+----+-------+-----+---------+
|  2 | kerry |  30 | nantong |
+----+-------+-----+---------+
1 row in set (0.00 sec)

session 2里面去更新id = 1的age字段。

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update student set age = 10 where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到的行为是整个表都被锁住了。
结论:
|-- 非索引条件加锁会变成表锁。

间隙锁和Next-Key lock

除了行锁和表锁之外,InnoDB引入间隙锁,为了解决RR隔离级别下的不可重复读带来的幻读问题。
当使用范围条件查询而不是等值条件查询的时候,InnoDB就会给符合条件的范围索引加锁,在条件范围内不存在的记录就叫做“间隙”。
这里已经排除了非索引项的可能性,那么下面就需要讨论几个方面问题:
普通索引项是否会加间隙锁?
范围查询是否会加间隙锁?
唯一索引和主键索引是否会加间隙锁?
不存在的检索条件是否会加间隙锁?

再开始之前准备的数据

mysql> select * from student;
+----+-------+-----+---------+
| id | name  | age | address |
+----+-------+-----+---------+
|  1 | gary  |   1 | suzhou  |
|  2 | kerry |   5 | nantong |
|  5 | gary1 |  10 | sz      |
|  6 | test  |  15 | nt      |
|  7 | test1 |  20 | sz      |
+----+-------+-----+---------+
5 rows in set (0.00 sec)
Next-Key lock会在age字段分间隙 (-&,1], (1,5], (5,10], (10,15], (15,20], (20,+&)
普通索引项
  1. 通过普通索引,查询一个不存在的索引值,比如12,落在(10,15]的范围内,因为12不存在所以不会加行锁,那么间隙锁的范围就是(10,15)。
session 1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from student where age = 12 for update;
Empty set (0.00 sec)

session 2
mysql> update student set address = 'teest' where id = 5;  --age=10
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> update student set address = 'teest' where id = 6; --age=15
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> insert into student(name,age,address) values ('test2', 11, 'sz');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into student(name,age,address) values ('test2', 14, 'sz');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到age为10和15的行并没有锁,可以更新成功,而age为11和14的值是insert失败的。
结论:
|-- RR级别下,普通索引未命中,会加gap lock。
|-- RC级别,不存在Gap lock,所以不会加锁

  1. 通过普通索引,查询一个存在的索引值,比如15,落在(10,15]的范围内,这个时候就是锁定的(10, 20)。
mysql> update student set address = 'teest' where id = 5; -- age=10
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> update student set address = 'teest' where id = 6;  -- age=15
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update student set address = 'teest' where id = 7; -- age=20
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> insert into student(name,age,address) values ('test2', 11, 'sz');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into student(name,age,address) values ('test2', 19, 'sz');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到age为10和20的行是没有锁的,更新成功,而10和20之间都不能做更新和插入。
结论:
|-- RR级别下,普通索引命中,普通索引会加next-key lock,聚簇索引加X锁
|-- RC级别,对普通索引和聚簇索引都加X锁

唯一索引项
  1. 通过唯一索引,查询一个不存在的索引值,与普通索引类似。
    结论:
    |-- RR级别下,唯一索引未命中,只会在唯一索引加gap lock
    |-- RC级别,不存在Gap lock,所以不会加锁

  2. 通过唯一索引,查询存在的索引值,这里不会再加gap lock了,都会退化成record lock。
    结论:
    |-- RR级别,对唯一索引和聚簇索引都加X锁
    |-- RC级别,对唯一索引和聚簇索引都加X锁

聚簇索引
  1. 查询未命中
    |-- RR级别下,聚簇索引加gap lock
    |-- RC级别,不加锁
  2. 查询命中
    |-- RR级别下,聚簇索引加X锁
    |-- RC级别,聚簇索引加X锁

REFER:一个线上SQL死锁异常分析:深入了解事务和锁

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容