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

 更新时间:2025年11月01日 10:54:37   作者:大G的笔记本  
InnoDB中的行锁(RecordLock)和间隙锁(GapLock)是用于保证事务隔离性的两种锁机制,本文给大家介绍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 前面的间隙就会被锁。

到此这篇关于MySQL 中的行锁(Record Lock) 和 间隙锁(Gap Lock)详解的文章就介绍到这了,更多相关mysql行锁和间隙锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Win10下免安装版MySQL8.0.16的安装和配置教程图解

    Win10下免安装版MySQL8.0.16的安装和配置教程图解

    这篇文章主要介绍了Win10下免安装版MySQL8.0.16的安装和配置 ,本文通过图文并茂的形式给大家介绍的非常详细,具有一定的参考解决价值,需要的朋友可以参考下
    2019-06-06
  • MyBatis 动态SQL全面详解

    MyBatis 动态SQL全面详解

    MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦
    2021-09-09
  • mysql如何为大表新增字段或索引

    mysql如何为大表新增字段或索引

    解决MySQL大表增加或增加索引操作超时的问题,可以通过建新表、加字段或索引、导数据到新表、改表名等步骤来实现,可以分批导入数据以防止超时,同时,还介绍了如何查询表中的所有字段以及在IDEA中设置执行超时时间的方法
    2025-03-03
  • 详解mysql中的字符集和校验规则

    详解mysql中的字符集和校验规则

    这篇文章主要介绍了mysql中的字符集和校验规则的相关资料,帮助大家更好的理解和学习MySQL,感兴趣的朋友可以了解下
    2020-12-12
  • MySQL Semisynchronous Replication介绍

    MySQL Semisynchronous Replication介绍

    这篇文章主要介绍了MySQL Semisynchronous Replication介绍,本文讲解了Semisynchronous Replication 定义、,需要的朋友可以参考下
    2015-05-05
  • MySQL数据库查询性能优化策略

    MySQL数据库查询性能优化策略

    这篇文章主要介绍了MySQL数据库查询性能优化的策略,帮助大家的工作学习提高MySQL数据库的性能,感兴趣的朋友可以了解下
    2020-08-08
  • mysql同步问题之Slave延迟很大优化方法

    mysql同步问题之Slave延迟很大优化方法

    这篇文章主要介绍了mysql同步问题之Slave延迟很大优化方法,需要的朋友可以参考下
    2016-05-05
  • mysql如何分别按年/月/日/周分组统计数据详解

    mysql如何分别按年/月/日/周分组统计数据详解

    我们在用Mysql抽取数据时候,经常需要按照天、周、月等不同的粒度对数据进行分组统计,下面这篇文章主要给大家介绍了关于mysql如何分别按年/月/日/周分组统计数据的相关资料,需要的朋友可以参考下
    2022-12-12
  • MySQL中SHOW TABLE STATUS的使用及说明

    MySQL中SHOW TABLE STATUS的使用及说明

    这篇文章主要介绍了MySQL中SHOW TABLE STATUS的使用及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • MySQL性能突然下降的原因

    MySQL性能突然下降的原因

    这篇文章主要介绍了MySQL性能突然下降的原因,帮助大家更好的了解和维护数据库,感兴趣的朋友可以了解下
    2020-10-10

最新评论