1. 行锁(record lock)
定义
- record lock 是 innodb 在事务中对索引记录加的锁,用于保护某一行数据不被其他事务修改。
- 它是基于索引的锁,如果没有索引,innodb 会退化为表锁。
作用
- 防止其他事务修改或删除当前事务正在处理的行。
- 保证事务的隔离性(尤其是
repeatable read和serializable隔离级别)。
触发场景
- 常见于
select ... for update或update、delete操作。 - 必须通过索引定位行,否则会锁住更多数据(甚至全表)。
例子
假设有表:
create table user ( id int primary key, name varchar(50), age int ) engine=innodb;
事务 a:
begin; select * from user where id=5 for update;
- innodb 会在
id=5这一行的索引记录上加 record lock。 - 事务 b 如果执行:
update user set age=30 where id=5;
会被阻塞,直到事务 a 提交或回滚。
2. 间隙锁(gap lock)
定义
- gap lock 是 innodb 在事务中对索引记录之间的间隙加的锁。
- 它锁住的是索引之间的空隙,而不是具体的行。
- 主要用于防止幻读(phantom read)。
作用
- 阻止其他事务在某个索引范围内插入新记录。
- 常见于范围查询(
between、>、<)或repeatable read隔离级别下的select ... for update。
触发场景
- 范围查询并加锁时(
for update或lock in share mode)。 - 在
repeatable read下,innodb 会自动加 gap lock 来防止幻读。
例子
假设表中有数据:
id: 1, 5, 10
事务 a:
begin; select * from user where id between 5 and 10 for update;
- innodb 会对
id=5和id=10加 record lock。 - 同时会对
(5,10)之间的空隙加 gap lock。 - 事务 b 如果执行:
insert into user values (7, 'tom', 25);
会被阻塞,因为 (5,10) 的间隙被锁住。
3. 区别总结
| 锁类型 | 锁住对象 | 作用 | 常见场景 |
|---|---|---|---|
| record lock | 某一行的索引记录 | 防止该行被修改或删除 | 精确匹配索引的 update、delete、select ... for update |
| gap lock | 索引记录之间的空隙 | 防止在范围内插入新记录 | 范围查询加锁,防止幻读 |
✅ 总结:
- record lock:锁住已有的行,防止修改或删除。
- gap lock:锁住索引间的空隙,防止插入新行。
- 两者经常一起出现,比如范围查询时会同时锁住已有行和间隙,这种组合锁叫 next-key lock。
begin; select * from user where id between a and b for update;
如果a和b距离很大是否直接锁住a和b中间所有行
是的,
在 innodb 的 repeatable read 隔离级别下,执行
begin; select * from user where id between a and b for update;
会触发 next-key lock(record lock + gap lock 的组合),它会锁住范围内所有匹配的行,并且锁住这些行之间的间隙,防止其他事务在这个范围内插入新数据。
1. 锁定范围的规则
假设 id 是主键索引,表中数据如下:
id: 1, 5, 10, 20, 50, 100
如果执行:
select * from user where id between 5 and 50 for update;
innodb 会:
- 对
id=5、id=10、id=20、id=50加 record lock(行锁) - 对
(5,10)、(10,20)、(20,50)这些间隙加 gap lock - 还会对
(50, 下一条记录)的间隙加锁(防止插入 51、60 等)
2. 如果 a 和 b 很远
如果 a=5,b=1000000,而表中有很多行:
- 是的,innodb 会锁住
id=5到id=1000000范围内所有匹配的行和它们之间的间隙。 - 这意味着这个范围内的更新、删除、插入都会被阻塞,直到事务提交或回滚。
- 如果范围特别大,可能会造成大范围锁定,严重影响并发性能。
3. 注意事项
- 基于索引:行锁和间隙锁都是基于索引的,如果
id没有索引,innodb 会退化成表锁,锁住整张表。 - 范围越大,锁的行和间隙越多,并发冲突风险越高。
- 如果只想锁住已存在的行而不锁间隙,可以使用
read committed隔离级别(gap lock 不会生效)。
4. 直观示意
假设:
id: 1, 5, 10, 20, 50, 100
执行:
select * from user where id between 5 and 50 for update;
锁定范围:
[5]====(gap)====[10]====(gap)====[20]====(gap)====[50]====(gap)====
- [x] 表示 record lock(行锁)
- (gap) 表示 gap lock(间隙锁)
我建议,如果你的 a 和 b 之间范围特别大,可以考虑:
- 拆分查询,减少一次性锁定的范围
- 使用更精确的条件,避免锁住无关数据
- 降低隔离级别(如
read committed)减少 gap lock
为什么5前面没有被锁住?
在执行
begin; select * from user where id between 5 and 50 for update;
的时候,为什么 id=5 前面的间隙(比如 (1,5))没有被锁住?
核心原因:锁定范围是由查询条件决定的
innodb 的 next-key lock(行锁 + 间隙锁)只会锁住满足查询条件的记录以及这些记录之间的间隙。
- 查询条件是
id between 5 and 50 - 范围的起点是
id=5 - 所以它不会去锁
(1,5)这个间隙,因为这个间隙里的值(比如 2、3、4)不满足条件,不会影响当前事务的结果集。
锁定范围的精确规则
在 repeatable read 隔离级别下:
- record lock:锁住满足条件的记录(这里是
id=5, 10, 20, 50)。 - gap lock:锁住这些记录之间的间隙,以及最后一条记录到下一条记录之间的间隙。
- 不锁条件范围外的间隙,因为它们不会导致幻读。
例子对比
假设表数据:
id: 1, 5, 10, 20, 50, 100
查询:
select * from user where id between 5 and 50 for update;
锁定范围:
[5]====(gap)====[10]====(gap)====[20]====(gap)====[50]====(gap)====
[x]= record lock(gap)= gap lock(1,5)不锁,因为不在条件范围内。
如果是:
select * from user where id >= 5 for update;
锁定范围:
[5]====(gap)====[10]====(gap)====[20]====(gap)====[50]====(gap)====[100]====(gap)====
- 依然不会锁
(1,5),因为条件是>=5。
如果是:
select * from user where id > 1 for update;
锁定范围:
(1,5)====[5]====(gap)====[10]====(gap)====[20]====(gap)====[50]====(gap)====[100]====(gap)====
- 这时
(1,5)会被锁,因为它在条件范围内(>1)。
✅ 总结:
- innodb 只锁条件范围内的记录和间隙,不会锁条件范围外的间隙。
between 5 and 50的起点是 5,所以 5 前面的间隙不会被锁。- 如果条件包含更小的值(比如
>1),那么 5 前面的间隙就会被锁。
到此这篇关于mysql 中的行锁(record lock) 和 间隙锁(gap lock)详解的文章就介绍到这了,更多相关mysql行锁和间隙锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论