MySQL——行锁说明

73 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情

行锁(Row Lock)

行锁也称为记录锁,就是锁住一行记录。

MySQL服务器并没有实现行锁机制,行锁只在存储引擎层实现,InnoDB支持行锁,MyISAM不支持。

行锁优缺点

优点:锁定粒度小,发生锁冲突的概率低,并发度高

缺点:加锁开销大,加锁慢,容易出现死锁

1、记录锁

记录锁(类型名称:LOCK_REC_NOT_GAP)就是把一条记录锁上。

比如我们把ID值为3的记录加一个记录锁,仅仅是锁住了ID为3的记录,对周围数据没有影响。

记录锁类型:

记录锁和表级锁一样也有S锁和X锁(规则一样),称为S型记录锁和X型记录锁:

select ... lock in share mode; -- 加S锁
select ... for update; -- 加X锁
  • 当一个事务获取了一条记录的s型记录锁后,其他事务也可以继续获取该记录的s型记录锁,但不可以继续获取X型记录锁;

  • 当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的s型记录锁,也不可以继续获取X型记录锁。

2、间隙锁

间隙锁(类型名称:Gap Locks),简称gap锁,是Innodb在可重复读(RR)隔离级别下为了解决幻读问题时引入的锁机制,

在这里插入图片描述

gap锁的提出仅仅是为了==防止插入幻影记录==。上图中给一个事务给id=8的记录加上了gap锁,意味着不允许别的事务在id为8的记录前边的间隙插入新纪录,即id=3到id=8这个区间是不允许插入新纪录的。当有另一个事务想在id=3到id=8这个区间插入新纪录,会被阻塞,直到拥有这个gap锁的事务提交后,另一个事务才能插入记录。

gap锁虽然也有共享gap锁和独占gap锁的分类,但它们的作用是相同的。而且对某一条记录加了gap锁(不管是共享gap锁还是独占gap锁),并不会限制其他事物对这条记录加记录锁或者继续加gap锁。

如何产生间隙锁:

  • 指定查询某一条记录时,如果这条记录不存在,会产生间隙锁,如果记录存在,则只会产生记录锁。这条记录到前一条记录的间隙区间不允许添加记录。
  • 对于查找某一范围内的查询语句,会产生间隙锁,,间隙区间不允许添加记录

测试数据:

mysql> select * from student;
+----+--------+--------+
| id | name   | class  |
+----+--------+--------+
|  1 | 张三   | 一班   |
|  4 | 李四   | 一班   |
|  7 | 王五   | 二班   |
|  10 | 钱七   | 三班   |
+----+--------+--------+
5 rows in set (0.00 sec)

隐藏间隙区间:

(1,4),(4,7),(7,10)

测试同一条记录添加多个间隙锁:

事务A给不存在记录(id=13)加上独占间隙锁:

mysql> begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student where id = 13 for update;-- 独占间隙锁
Empty set (0.00 sec)

事务B可以在存在gap锁的基础上再加上共享间隙锁:

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

mysql> select * from student where id = 13 lock in share mode;-- 共享间隙锁
Empty set (0.00 sec)

测试添加

事务A给不存在记录(id=13)加上独占间隙锁:

mysql> begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student where id = 10 for update;-- 独占间隙锁
Empty set (0.00 sec)

事务B插入id=11的记录:隐藏间隙区间为(10,13),不允许添加记录,所以会阻塞

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

mysql> insert into student values(11,'神明','二班');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

3、临键锁

有时候我们既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新记录,所以InnoDB就提出了一种称之为Next-Key Locks的锁,官方的类型名称为:LOCK_ORDINARY,我们也可以简称为next-key锁。Next-KeyLocks是在存储引擎innodb、事务级别在可重复读的情况下使用的数据库锁,innodb默认的锁就是Next-Keylocks。

next-key锁本质就是一个记录锁和gap锁的合体,它既能保护该条记录,有能阻止被的事务将新的记录插入被保护记录前边的间隙,临键锁是个左开右闭的区间比如(16, 18]。

测试

数据:

mysql> select * from student;
+----+--------+--------+
| id | name   | class  |
+----+--------+--------+
|  1 | 张三   | 一班   |
|  3 | 李四   | 一班   |
|  7 | 赵六   | 三班   |
| 15 | 皓明   | 三班   |
| 20 | 神明   | 二班   |
+----+--------+--------+

事务A 临键锁的区间为(3,7],其中记录锁的记录为id=7,gap锁的间隙区间为(3,7)

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

mysql> select * from student where id <=7 and id > 3 for update;
+----+--------+--------+
| id | name   | class  |
+----+--------+--------+
|  7 | 赵六   | 三班   |
+----+--------+--------+
1 row in set (0.00 sec)

测试临键锁中记录锁的效果:

事务B给id为7的记录加上S型记录锁,被阻塞,事务A的记录锁生效

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

mysql>  select * from student where id = 7 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

测试临键锁中gap锁的效果:

事务C在间隙区间为(3,7]中添加一条记录,被阻塞,gap锁生效

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

mysql> insert into student values(6,'王五','二班');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

4、插入意向锁

我们说一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了gap锁( next-key临键锁也包含gap锁),如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在在等待。InnoDB就把这种类型的锁命名为 Insert Intention Locks,官方的类型名称为:LOCK_INSERT_INTENTION,我们称为插入意向锁。

==插入意向锁是一种Gap锁,不是意向锁,在插入一条记录行前由insert操作时产生。==该锁用以表示插入意向,多个事务插入相同的索引间隙时,只要不是插入到相同的位置,则不需要进行锁等待。==插入意向锁锁定了索引之间的间隙,但是插入意向锁之间没有互相阻塞。==

假设存在两条值分别为4和7的记录,两个不同的事务分别试图插入值为5和6的两条记录,每个事务在获取插入行上独占的(排他)锁前,都会获取(4,7)之间的间隙锁,但是因为数据行之间并不冲突,所以两个事务之间并不会产生冲突(阻塞等待)。总结来说,插入意向锁的特性可以分成两部分:

(1)插入意向锁是一种特殊的间隙锁—一间隙锁可以锁定开区间内的部分记录。

(2)插入意向锁之间互不排斥,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待。

注意,虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁是表锁而插入意向锁是行锁。