数据库锁
锁有哪些类型:全局锁、表级锁、行级锁、共享锁、排他锁、记录锁、间隙锁等。
- 全局锁
全局锁会限制整个数据库实例中的部分操作,典型命令是:
FLUSH TABLES WITH READ LOCK; - 表级锁
常见类型包括:
- 表锁:LOCK TABLES 显式加锁;MyISAM 等存储引擎也大量使用表级锁。
- 意向锁:InnoDB 的 IS、IX,用于表示事务准备在表中的记录上加共享锁或排他锁。
- 元数据锁(MDL):保护表结构等元数据,防止并发 DDL 与相关 SQL 发生不安全的冲突。
- AUTO-INC 锁:用于协调自增值分配,具体机制取决于 MySQL 配置和语句类型。
例如:
ALTER TABLE orders ADD COLUMN remark VARCHAR(100);
这类 DDL 可能需要获取 MDL。若有长期事务持有相关元数据锁,DDL 就可能一直等待;后续访问该表的 SQL 也可能受到影响。
优化思路: 上线 DDL 前检查长事务,选择合适的在线变更方式,并监控 MDL 等待。
3. 行级锁
InnoDB 的行级锁通常围绕索引记录实现,而不是简单地对 SQL 逻辑上涉及的每一行加锁。
例如:
UPDATE account
SET balance = balance - 100
WHERE id = 1;
如果 id 是主键,这条 SQL 通常只需要锁定对应的索引记录及相关必要资源,竞争范围较小。
如果条件没有合适索引,执行器可能需要扫描大量记录,导致更多记录被检查、锁定,或者在扫描期间产生更大范围的锁竞争。
优化核心:让 SQL 通过合适的索引定位尽可能少的记录,并缩短持锁时间。
主要用途是某些需要全局一致性视图的备份场景。
- 产生:执行全局读锁命令。
- 影响:限制相关表的写操作;具体行为还受其他锁和操作类型影响。
- 优化:优先使用支持一致性快照的在线备份方案,减少全局锁持有时间。
按锁定范围分类:Record、Gap、Next-Key
这是排查 InnoDB 死锁和锁等待时最重要的一部分。InnoDB 行级锁实际锁定的是索引记录及其相关范围。
MySQL开发者区
+1
- 记录锁(Record Lock)
锁定某条索引记录,不锁定其前面的间隙。
例如:
CREATE TABLE account (
id BIGINT PRIMARY KEY,
balance DECIMAL(10, 2)
) ENGINE = InnoDB;
SELECT *
FROM account
WHERE id = 100
FOR UPDATE;
假设 id = 100 存在,且使用主键进行唯一值查找,在常见情况下只需要对该记录加排他记录锁。
此时:
- 其他事务不能直接修改或删除这条记录。
- 其他事务仍可以操作不冲突的其他记录。
- 普通一致性读通常仍能通过 MVCC 读取符合快照规则的版本。
常见场景: 按主键更新用户、订单、账户等单条数据。
- 间隙锁(Gap Lock)
锁定索引记录之间的间隙,主要用于阻止其他事务向特定范围插入新记录。
假设索引中存在以下值:
10、20、30
事务 A 执行:
START TRANSACTION;
SELECT *
FROM account
WHERE id BETWEEN 10 AND 20
FOR UPDATE;
在默认 REPEATABLE READ 隔离级别下,范围锁定可能涉及 Next-Key Lock 和边界间隙。事务 B 如果尝试插入范围内的新记录,可能因为间隙锁而等待。
INSERT INTO account (id, balance)
VALUES (15, 100);
这里的关键不是 id = 15 原本有没有记录,而是它是否落入了被锁定的索引间隙。
间隙锁的主要目的,是防止范围内出现影响并发事务正确性的幻读相关插入行为。
3. Next-Key Lock(临键锁)
Next-Key Lock = Record Lock + Gap Lock。
也就是:
- 锁住某条索引记录。
- 同时锁住该记录之前的索引间隙。
假设索引值为 10、20、30,那么针对记录 20 的一个 Next-Key Lock,可以覆盖 (10, 20]。
三种锁的区别
Record Lock
10
20
30
只锁定记录 20。
Gap Lock
10
间隙 (10, 20)
20
阻止在对应间隙内插入新记录,但不单纯因为 Gap Lock 就禁止对边界记录本身进行所有操作。
Next-Key Lock
10
(10, 20]
30
同时锁定间隙和记录 20。
示意图用于理解锁定范围。实际锁范围取决于索引、查询条件、执行计划、隔离级别和记录是否存在。
- 插入意向锁(Insert Intention Lock)
事务插入新记录前,InnoDB 会获取插入意向间隙锁。
它不是简单地说“同一个间隙只能有一个事务插入”。例如,索引值为 10、20 时,两个事务分别插入 12 和 15,在没有其他冲突锁的情况下,可以并发推进。
但是,如果其他事务持有覆盖该位置的冲突间隙锁或 Next-Key Lock,插入就可能等待。
锁为什么产生:SQL 语句、事务隔离级别、索引使用情况、锁的获取顺序等。
锁怎么优化:通过执行计划、事务分析、锁等待信息和业务代码定位根因,而不是简单地给 SQL 加索引或缩短超时时间。
第一步:获取死锁现场
MySQL 可以执行:
SHOW ENGINE INNODB STATUS;
重点查看 LATEST DETECTED DEADLOCK,分析:
- 涉及哪些事务、表和索引。
- 每个事务执行了什么 SQL。
- 每个事务持有哪些锁、正在等待什么锁。
- 哪个事务最终被选为牺牲者并回滚。
注意:该命令主要展示最近一次被记录的死锁,不代表全部历史死锁。
第二步:定位具体 SQL 和锁冲突
不要只看表名,还要看:
- index PRIMARY 或其他二级索引。
- lock_mode、locks rec but not gap 等锁信息。
- RECORD LOCKS 对应的索引记录。
- HOLDS THE LOCK(S) 和 WAITING FOR THIS LOCK TO BE GRANTED。
判断是记录锁、间隙锁、Next-Key Lock,还是不同索引之间的锁冲突。
第三步:反查业务代码调用链
从 SQL 找到对应的 Mapper、Service、事务入口,梳理完整执行顺序:
- 哪些方法开启了事务?
- 事务中执行了哪些 SQL?
- 是否涉及多个表、多个索引?
- 是否存在循环更新、批量处理?
- 是否调用了远程接口,导致事务持续时间过长?
特别注意:死锁 SQL 不一定是根因,往往是多个事务执行顺序交叉造成的。
第四步:分析死锁形成原因
重点检查以下问题:
- 加锁顺序不一致:事务 A 先锁表记录 X,再锁 Y;事务 B 先锁 Y,再锁 X。
- 事务过大:一次事务修改大量记录,持锁时间长、锁冲突概率高。
- 索引不合理:查询没有合适索引,扫描和加锁的记录范围扩大。
- 范围查询冲突:在特定隔离级别下,范围扫描可能涉及间隙锁或 Next-Key Lock。
- 索引与业务操作交叉:更新二级索引字段、唯一键冲突检查等引发额外锁竞争。
- 并发访问热点:多个事务同时更新同一批数据。
第五步:制定治理方案
根据根因选择措施,而不是一律加锁:
- 统一多条 SQL、多个资源的加锁顺序。
- 补充合适的索引,缩小扫描和锁定范围。
- 拆分大事务,缩短事务持锁时间。
- 避免事务中执行耗时的远程调用、文件处理等操作。
- 对批量更新按统一顺序处理记录。
- 对高频热点业务考虑串行化、队列化或乐观锁等方案。
第六步:验证与防止复发
- 使用并发测试复现原有冲突场景。
- 检查死锁频率是否下降。
- 观察 SQL 耗时、锁等待时间和事务持续时间。
- 检查应用是否正确处理死锁异常并重试。
MySQL 检测到死锁后,通常会回滚其中一个事务。应用需要对可重试的事务进行有限次数重试,并确保重试不会导致重复扣款、重复写入等业务问题。

浙公网安备 33010602011771号