MySQL 锁机制

4 阅读4分钟

MySQL 锁机制是 InnoDB 引擎实现高并发、事务安全的核心,主要通过全局锁、表级锁、行级锁三级粒度,配合共享/排他锁、意向锁、间隙锁、临键锁等类型,解决并发事务下的脏读、不可重复读、幻读问题。下面从分类、原理、算法、实战、死锁五个维度全面详解。


一、锁的核心分类(按粒度)

1. 全局锁(Global Lock)

  • 作用:锁定整个 MySQL 实例,全库只读。
  • 命令FLUSH TABLES WITH READ LOCK (FTWRL)
  • 场景全库逻辑备份(mysqldump)。
  • 特点:极重,阻塞所有 DML/DDL,谨慎使用

2. 表级锁(Table-Level Lock)

锁定整张表,开销小、加锁快、并发低。

(1) 表读锁/表写锁

  • 命令
    LOCK TABLES t READ;  -- 读锁:其他事务可读,不可写
    LOCK TABLES t WRITE; -- 写锁:独占,阻塞所有读写
    
  • 引擎:MyISAM 默认;InnoDB 支持但少用。

(2) 元数据锁 MDL(Metadata Lock)

  • 隐式自动加锁,无需手动操作。
    • 读锁:查询(SELECT)时加,兼容其他读锁
    • 写锁:DDL(ALTER、DROP)时加,互斥所有锁
  • 高危场景:长查询持有 MDL 读锁 → DDL 被阻塞 → 后续所有查询被堵 → 连接池打满、业务雪崩
  • 规避DDL 必须避峰;使用 gh-ost/pt-online-schema-change 在线改表。

(3) 意向锁(Intention Lock)

  • InnoDB 为行锁+表锁共存设计的表级辅助锁
    • 意向共享锁 IS:事务准备对某些行加 S 锁。
    • 意向排他锁 IX:事务准备对某些行加 X 锁。
  • 作用:快速判断表上是否有行锁冲突,避免全表扫描检查
  • 兼容性
    • IS/IX 之间互相兼容
    • IS 与表 X 锁互斥;IX 与表 X 锁互斥

3. 行级锁(Row-Level Lock)

InnoDB 核心,锁定单行/索引范围,粒度最细、并发最高。

  • 依赖索引无索引 → 全表锁(行锁退表锁)
  • 基础类型
    • 共享锁 S(Shared Lock)
      • 允许多事务同时读,阻塞 X 锁
      • 语法:SELECT ... LOCK IN SHARE MODE;
    • 排他锁 X(Exclusive Lock)
      • 独占读写,阻塞 S 和 X
      • 自动加锁:UPDATE/DELETE/INSERT。
      • 手动:SELECT ... FOR UPDATE;

二、InnoDB 行锁三大算法(RR 隔离级别默认)

1. 记录锁 Record Lock

  • 锁定单个索引记录
  • 触发唯一索引 + 精准等值查询(命中)
    SELECT * FROM user WHERE id=10 FOR UPDATE; -- 仅锁 id=10
    

2. 间隙锁 Gap Lock

  • 锁定索引之间的“空隙”(不包含记录)
  • 作用防止幻读(禁止区间插入)。
  • 示例(id=1,3,5):
    SELECT * FROM user WHERE id BETWEEN 1 AND 5 FOR UPDATE;
    -- 锁间隙:(1,3)、(3,5)
    
  • 特性:间隙锁之间兼容,仅与插入意向锁互斥。

3. 临键锁 Next-Key Lock

  • 记录锁 + 间隙锁(左开右闭区间)
  • InnoDB 默认行锁算法
  • 示例(id=1,3,5):
    SELECT * FROM user WHERE id>3 FOR UPDATE;
    -- 锁:(3,5]、(5,+∞)
    
  • 作用彻底解决 RR 级别幻读

4. 插入意向锁 Insert Intention Lock

  • 插入前加的间隙锁,表示“打算在某间隙插入”。
  • 互斥:与同间隙的间隙锁/临键锁冲突。

三、锁与事务隔离级别

隔离级别锁行为解决问题存在问题
读未提交 RU基本不加锁脏读、不可重复读、幻读
读已提交 RC仅记录锁,无间隙锁脏读不可重复读、幻读
可重复读 RR(默认)记录+间隙+临键锁全生效脏读、不可重复读、幻读
串行化 Serializable全表加锁所有问题并发极低

四、加锁规则(高频考点)

  1. 最小原则:唯一索引等值命中 → 降级为记录锁(关闭间隙)。
  2. 范围原则:范围查询(>、<、BETWEEN)→ 临键锁全区间
  3. 无索引原则:WHERE 未用索引 → 全表每一行加 X 锁(表锁效果)。
  4. 扫描原则:锁加在扫描过的索引上,不只是匹配行。
  5. 死锁原则:行锁会产生死锁;表锁不会。

五、死锁与排查

1. 死锁成因

  • 互斥、请求与保持、不可剥夺、循环等待四大条件同时满足。
  • 典型场景
    • T1:锁 A → 等待 B
    • T2:锁 B → 等待 A → 循环死锁

2. 死锁处理

  • 超时机制innodb_lock_wait_timeout(默认 50s)。
  • 死锁检测:InnoDB 主动检测,回滚代价最小事务

3. 排查命令

-- 查看锁等待
SHOW ENGINE INNODB STATUS;
-- 查看当前锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;

4. 死锁预防

  • 顺序访问:所有事务按相同顺序更新数据。
  • 快进快出:事务越小越好,及时 COMMIT/ROLLBACK。
  • 避免长查询:减少 MDL 与行锁持有时间。
  • 合理索引:防止行锁变表锁。

六、实战要点(避坑)

  1. FOR UPDATE 必须走索引,否则全表锁。
  2. RR 级别默认临键锁,范围查询易锁大量间隙。
  3. MDL 写锁是 DDL 最大风险,务必避峰。
  4. UPDATE/DELETE 无索引 = 全表锁
  5. 高并发写:拆小事务、用唯一索引、减少锁范围。

七、总结

  • 全局锁:备份用,极重。
  • 表锁/MDL:并发低,DDL 高危。
  • InnoDB 行锁
    • S/X 基础 + IS/IX 表级协调。
    • 记录/间隙/临键三算法(RR 默认临键锁)。
    • 索引是行锁灵魂
  • 核心目标并发与一致性平衡,解决脏读/不可重复读/幻读。