当前位置: 首页 > news >正文

MySQL 中的 行锁(Record Lock) 和 间隙锁(Gap Lock)

1. 行锁(Record Lock)

定义

  • Record Lock 是 InnoDB 在事务中对索引记录加的锁,用于保护某一行数据不被其他事务修改。
  • 它是基于索引的锁,如果没有索引,InnoDB 会退化为表锁

作用

  • 防止其他事务修改或删除当前事务正在处理的行。
  • 保证事务的隔离性(尤其是 REPEATABLE READ 和 SERIALIZABLE 隔离级别)。

触发场景

  • 常见于 SELECT ... FOR UPDATE 或 UPDATEDELETE 操作。
  • 必须通过索引定位行,否则会锁住更多数据(甚至全表)。

例子

假设有表:

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某一行的索引记录防止该行被修改或删除精确匹配索引的 UPDATEDELETESELECT ... 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 LockRecord 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=5id=10id=20id=50 加 Record Lock(行锁)
  • 对 (5,10)(10,20)(20,50) 这些间隙加 Gap Lock
  • 还会对 (50, 下一条记录) 的间隙加锁(防止插入 51、60 等)

2. 如果 a 和 b 很远

如果 a=5b=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(间隙锁)

我建议,如果你的 ab 之间范围特别大,可以考虑:

  • 拆分查询,减少一次性锁定的范围
  • 使用更精确的条件,避免锁住无关数据
  • 降低隔离级别(如 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 隔离级别下:

  1. Record Lock:锁住满足条件的记录(这里是 id=5, 10, 20, 50)。
  2. Gap Lock:锁住这些记录之间的间隙,以及最后一条记录到下一条记录之间的间隙。
  3. 不锁条件范围外的间隙,因为它们不会导致幻读。

例子对比

假设表数据:

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 前面的间隙就会被锁。

http://www.dtcms.com/a/548546.html

相关文章:

  • 【Docker】P1 Docker 基础入门指南
  • 【OD刷题笔记】- API集群负载统计
  • 韩城市网站建设wordpress 手工网站
  • Java—常见API(String、ArrayList)
  • 【STM32项目开源】STM32单片机医疗点滴控制系统
  • 游戏类网站备案需要前置审批吗怎么制作图片表格
  • AWS EC2 服务器弹性伸缩:基于 CPU 使用率创建伸缩组,实现资源动态调整
  • srt服务器,推拉流
  • Rust API 设计中的零成本抽象原则:从原理到实践的平衡艺术
  • Work-Stealing 调度算法:Rust 异步运行时的核心引擎
  • 服务器恶意进程排查:从 top 命令定位到病毒文件删除的实战步骤
  • 【案例实战】初探鸿蒙开放能力:从好奇到实战的技术发现之旅
  • 服务器启动的时候就一个对外的端口,如何同时连接多个客户端?
  • LVS负载均衡集群理论详解
  • 三维重建【0-E】3D Gaussian Splatting:相机标定原理与步骤
  • Flutter---ListTile列表项组件
  • Spring Boot入门篇:快速搭建你的第一个Spring Boot应用
  • 《算法通关指南数据结构和算法篇(1)--- 顺序表相关算法题》
  • ReentrantLock 加锁与解锁流程详解(源码分析,小白易懂)
  • 鸿蒙Flutter三方库适配指南:06.插件适配原理
  • Linux 防火墙实战:用 firewalld 配置 External/Internal 区域,实现 NAT 内网共享上网
  • Java 学习29:方法
  • Kafka 全方位详细介绍:从架构原理到实践优化
  • Obsidian 入门教程(二)
  • [测试工具] 如何把离线的项目加入成为git项目的新分支
  • 让数据导入导出更智能:通用框架+验证+翻译的一站式解决方案
  • 今天我们学习Linux架构keepalived实现LVS代理双击热备
  • [Linux]内核队列实现详解
  • 【Spring Cloud】Spring Cloud Config
  • MySQL | 数据查询DQL语言:分组统计