MySQLNotes

第 18 章:Lock 与 Latch

zjc 于 2026-01-18 发布

这是《MySQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Lock 保护事务之间的数据一致性,生命周期通常到提交或回滚;Latch 保护 MySQL 和 InnoDB 内部临界区,生命周期极短。两者都表现为“等待”,但排查方式完全不同。

18.1 Lock 与 Latch 的区别

项目 Lock Latch
保护对象 表、行、区间、元数据 内存临界区、页、哈希表、互斥量
生命周期 事务级别或语句级别 微秒或毫秒级
是否参与死锁检测 InnoDB 行锁和表锁参与 通常以自旋等待为主
常见表现 lock wait timeout、deadlock mutex wait、性能下降
排查工具 data_locksinnodb_status Performance Schema wait events

用户日常说的“数据库锁”多数指 Lock,但性能毛刺常常来自 Latch。

18.2 锁的模式

常见锁模式:

说明
IS 意向共享锁
IX 意向排他锁
S 共享锁
X 排他锁
AUTO-INC 自增锁
RECORD 记录相关锁,包含记录、间隙、Next-Key
TABLE 表级锁
METADATA LOCK 元数据锁

兼容矩阵简化:

已持有 / 请求 S X
S 兼容 冲突
X 冲突 冲突

意向锁用于快速判断表级操作是否与行级锁冲突,不需要扫描所有行锁。

18.3 共享锁与排他锁

共享读:

START TRANSACTION;

SELECT *
FROM orders
WHERE id = 1
FOR SHARE;

COMMIT;

排他读:

START TRANSACTION;

SELECT *
FROM orders
WHERE id = 1
FOR UPDATE;

COMMIT;

区别:

  1. FOR SHARE 允许其他事务也加 S 锁;
  2. FOR SHARE 阻塞其他事务的 X 锁;
  3. FOR UPDATE 阻塞其他 S 和 X 锁;
  4. 二者都是当前读;
  5. 都受执行计划和索引影响。

如果 WHERE 没有有效索引,锁范围可能扩大,执行时间也可能变长。

18.4 意向锁

事务准备给行加 S 锁前,先在表上标记 IS;准备给行加 X 锁前,先标记 IX。

作用:

表级操作只需要检查意向锁
不需要逐行判断是否存在行锁

例如:

LOCK TABLES orders WRITE;

如果 orders 上存在 IX,则该请求需要等待。

18.5 表级锁

显式表锁:

LOCK TABLES orders READ;
UNLOCK TABLES;
LOCK TABLES orders WRITE;
UNLOCK TABLES;

生产 OLTP 很少使用 LOCK TABLES,常见问题:

  1. 锁粒度过大;
  2. 阻塞面广;
  3. 与事务语义混淆;
  4. 备份工具或管理操作误用;
  5. 忘记 UNLOCK。

如果需要一致性备份,InnoDB 应优先使用 MVCC 快照工具,如 mysqldump --single-transaction 或物理备份。

18.6 元数据锁 MDL

MDL 保护表结构稳定性:

操作 通常需要
SELECT MDL 读锁
DML MDL 读锁
ALTER TABLE MDL 写锁

风险场景:

1. 长查询持有 MDL 读锁;
2. ALTER 请求 MDL 写锁并排队;
3. 后续所有新 SELECT 在 MDL 队列后排队;
4. 表看起来完全不可访问。

查看 MDL:

SELECT *
FROM performance_schema.metadata_locks
WHERE OBJECT_SCHEMA = 'shop'
  AND OBJECT_NAME = 'orders';

MySQL 8.0 支持设置 DDL 等待时间:

SET SESSION lock_wait_timeout = 10;

ALTER TABLE orders
  ADD COLUMN remark VARCHAR(255),
  ALGORITHM=INPLACE,
  LOCK=NONE;

若超时失败,避免后续请求被长期堵塞。

18.7 记录锁、间隙锁与 Next-Key Lock

InnoDB 行级锁实际作用对象是索引记录及其间隙:

范围
Record Lock 索引记录本身
Gap Lock 索引记录之间的开区间
Next-Key Lock 记录 + 前面的间隙,左开右闭
Insert Intention Lock 插入前等待间隙冲突释放

示例索引值:

10, 20, 30

可能存在:

(-∞, 10), (10, 20), (20, 30), (30, +∞)

第 19 章会用两个会话详细实验。

18.8 自增锁

查看模式:

SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
模式 含义
0 传统模式,语句级 AUTO-INC 表锁
1 连续模式,简单插入用轻量互斥,批量插入可能用表锁
2 交错模式,并发最高,插入 ID 可能交错

MySQL 8.0 默认是 2。主从使用 STATEMENT Binlog 时,模式 2 可能导致复制不确定;ROW 格式下更安全。

查看自增值:

SHOW TABLE STATUS LIKE 'orders'\G

自增 ID 不保证连续,也不保证不重用,业务不要依赖它表达顺序号或编号连续性。

18.9 外键锁

虽然互联网业务常不使用外键,但如果表存在外键,InnoDB 会执行引用检查,可能锁父表或子表相关行。

查看外键:

SELECT *
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = 'shop';

外键风险:

  1. 写入额外检查;
  2. 父子表锁交互;
  3. 批量导入变慢;
  4. DDL 和迁移复杂;
  5. 死锁概率上升。

不使用外键时,要通过应用校验和定时对账补偿。

18.10 查看锁

MySQL 8.0 使用 Performance Schema:

SELECT
  ENGINE_TRANSACTION_ID,
  THREAD_ID,
  OBJECT_SCHEMA,
  OBJECT_NAME,
  INDEX_NAME,
  LOCK_TYPE,
  LOCK_MODE,
  LOCK_DATA
FROM performance_schema.data_locks;

查看锁等待:

SELECT
  requesting.ENGINE_TRANSACTION_ID AS requesting_trx,
  blocking.ENGINE_TRANSACTION_ID AS blocking_trx,
  requesting.OBJECT_NAME AS table_name,
  requesting.LOCK_TYPE,
  requesting.LOCK_MODE,
  requesting.LOCK_DATA
FROM performance_schema.data_lock_waits w
JOIN performance_schema.data_locks requesting
  ON requesting.ENGINE_LOCK_ID = w.REQUESTING_ENGINE_LOCK_ID
JOIN performance_schema.data_locks blocking
  ON blocking.ENGINE_LOCK_ID = w.BLOCKING_ENGINE_LOCK_ID;

查看 InnoDB 死锁:

SHOW VARIABLES LIKE 'innodb_print_all_deadlocks';
SHOW ENGINE INNODB STATUS\G

18.11 Latch 与互斥量

常见 Latch:

对象 说明
buffer pool mutex 保护缓冲池结构
page latch 保护数据页
lock system mutex 保护锁哈希表
dict mutex 保护数据字典
log mutex 保护日志写入结构
purge coordinator Purge 调度相关

查看等待:

SELECT
  EVENT_NAME,
  COUNT_STAR,
  SUM_TIMER_WAIT / 1000000000 AS wait_ms
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE EVENT_NAME LIKE 'wait/synch/%'
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

常见原因:

  1. 高并发热点页;
  2. Buffer Pool 过小;
  3. 自增或索引热点;
  4. 长时间持有内部锁;
  5. 高频元数据操作;
  6. 版本缺陷;
  7. 资源不足。

处理方向:

  1. 升级小版本修复;
  2. 降低热点;
  3. 拆分写入位置;
  4. 调整 Buffer Pool;
  5. 收集 Performance Schema;
  6. 找数据库专家或厂商分析。

18.12 锁等待参数

查看超时:

SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

会话级设置:

SET SESSION innodb_lock_wait_timeout = 10;

默认通常为 50 秒。在线应用应设置更短超时并做好重试,避免请求堆积。

跳过锁等待:

SELECT *
FROM task_queue
WHERE status = 'PENDING'
LIMIT 1
FOR UPDATE SKIP LOCKED;

不等待:

SELECT *
FROM task_queue
WHERE id = 1
FOR UPDATE NOWAIT;

SKIP LOCKED 适合任务队列抢占;NOWAIT 适合明确告诉用户资源忙。

18.13 锁设计原则

  1. 锁范围尽量精确;
  2. 关联条件必须有索引;
  3. 多行更新使用稳定顺序;
  4. 事务尽量短;
  5. 避免锁内做外部调用;
  6. 热点数据拆分或异步化;
  7. 幂等键降低重复写入;
  8. 死锁要有重试策略;
  9. 管理操作要评估 MDL;
  10. 监控锁等待、长事务和死锁。

本章小结

Lock 保护事务数据,粒度包括表、元数据、记录、间隙和 Next-Key;Latch 保护数据库内部临界区。排查业务锁等待使用 data_locksdata_lock_waits 和 InnoDB 状态;排查性能毛刺则要关注 Performance Schema 的同步等待。锁优化的本质是缩小锁范围、缩短持有时间和消除热点冲突。

思考题

  1. Lock 和 Latch 有什么区别?
  2. 意向锁的作用是什么?
  3. MDL 为什么可能让一个 ALTER 影响所有 SELECT?
  4. SKIP LOCKEDNOWAIT 分别适合什么场景?
  5. 如何区分行锁等待和 Latch 等待?