【Java高阶面经:数据库篇】12. MySQL锁机制全解:从行锁到死锁优化的深度指南
一、MySQL锁机制核心原理
1.1 锁的分类与底层逻辑
1.1.1 按粒度分类
锁类型 | 作用范围 | 实现方式 | 典型场景 |
---|---|---|---|
行锁 | 单行/多行记录 | 依赖索引(如SELECT ... FOR UPDATE ) | 高并发更新场景 |
表锁 | 整张表 | 无索引或显式锁命令(如LOCK TABLES ) | DDL操作、全表扫描 |
页锁 | 数据页(16KB) | 介于行锁与表锁之间(BDB引擎) | 较少使用(InnoDB默认行锁) |
1.1.2 按兼容性分类
- 共享锁(S锁):允许并发读,互斥写(如
SELECT ... LOCK IN SHARE MODE
)。 - 排它锁(X锁):独占写,互斥读写(如
SELECT ... FOR UPDATE
)。 - 意向锁(IS/IX锁):表级锁标志,预示行锁意图(如事务申请行锁前自动加IS锁)。
锁兼容性矩阵:
操作\锁类型 | S锁 | X锁 | IS锁 | IX锁 |
---|---|---|---|---|
S锁 | 兼容 | 互斥 | 兼容 | 互斥 |
X锁 | 互斥 | 互斥 | 互斥 | 互斥 |
IS锁 | 兼容 | 互斥 | 兼容 | 兼容 |
IX锁 | 互斥 | 互斥 | 兼容 | 兼容 |
二、行锁变表锁的六大核心场景
2.1 场景一:索引失效导致全表扫描
2.1.1 触发条件
- WHERE条件未命中索引,InnoDB扫描全表,行锁数量超过阈值时升级为表锁。
- 典型案例:
-- user_id字段无索引 UPDATE orders SET status='paid' WHERE user_id=100; -- 执行计划:type=ALL(全表扫描),触发表锁
2.1.2 诊断与优化
- 诊断步骤:
EXPLAIN
查看执行计划,确认type=ALL
或key=NULL
。SHOW INDEX FROM orders
检查索引是否存在。
- 优化方案:
-- 添加索引 ALTER TABLE orders ADD INDEX idx_user_id(user_id); -- 避免隐式类型转换(如user_id为INT,传入字符串) UPDATE orders SET status='paid' WHERE user_id=100; -- 避免单引号
2.2 场景二:显式表锁命令干扰
2.2.1 触发条件
- 手动执行
LOCK TABLES
命令,覆盖行锁逻辑,强制使用表锁。 - 典型案例:
BEGIN; LOCK TABLES orders WRITE; -- 显式表锁 UPDATE orders SET amount=200 WHERE id=5; -- 受表锁限制,无法并发 UNLOCK TABLES;
2.2.2 替代方案
- 推荐实践:
-- 使用事务+行锁替代显式表锁 BEGIN; SELECT * FROM orders WHERE id=5 FOR UPDATE; -- 行锁 UPDATE orders SET amount=200; COMMIT;
- 禁止操作:避免在InnoDB中使用
LOCK TABLES
,改用事务控制锁粒度。
2.3 场景三:间隙锁范围扩大
2.3.1 触发条件
- 隔离级别:
REPEATABLE READ
(默认)下,范围查询触发间隙锁(Gap Lock),锁住索引区间。 - 典型案例&#x