06. 锁机制与死锁处理

深入数据库锁机制:行锁、表锁、意向锁、间隙锁、Next-Key Lock,以及死锁检测算法与预防策略。

1. 锁粒度

锁类型粒度并发度开销代表
表锁整张表最低最低MyISAM
页锁一页(通常 4KB/8KB)中等中等BDB
行锁单行记录最高最高InnoDB

InnoDB 默认使用行锁,但在特定场景下会退化为表锁(如未命中索引的全表扫描)。


2. InnoDB 锁类型

2.1 锁模式

缩写兼容性适用操作
共享锁S与 S 兼容,与 X 冲突SELECT … LOCK IN SHARE MODE
排他锁X与任何锁冲突INSERT/UPDATE/DELETE/FOR UPDATE
意向共享锁IS表级锁,表示将获取 S 锁自动加
意向排他锁IX表级锁,表示将获取 X 锁自动加

意向锁的作用:避免加行锁时逐行检查表锁冲突。加 S/X 行前,先加 IS/IX 表锁,快速判断是否有表锁冲突。

2.2 锁算法

Record Lock(记录锁):
  锁定单行记录
  SELECT * FROM users WHERE id = 1 FOR UPDATE;
  → 在 id=1 的记录上加 X 锁

Gap Lock(间隙锁):
  锁定一个范围,但不包括记录本身
  SELECT * FROM users WHERE age > 20 AND age < 30 FOR UPDATE;
  → 锁定 (20, 30) 之间的间隙,阻止插入 age=25 的记录
  → 解决幻读问题

Next-Key Lock(临键锁):
  Record Lock + Gap Lock
  锁定记录及其前面的间隙
  → 是 InnoDB RR 级别的默认锁算法
  
  示例:id 值为 10, 20, 30
    对 id=20 加 Next-Key Lock → 锁定 (10, 20](间隙 10-20 + 记录 20)

3. 死锁

3.1 死锁示例

事务 A                    事务 B
  │                        │
  └──→ 获取 lock X(记录 a)  │
                           └──→ 获取 lock X(记录 b)
  └──→ 请求 lock X(记录 b)  │  ← 阻塞
                           └──→ 请求 lock X(记录 a)  ← 阻塞
                           
  循环等待 → 死锁!

3.2 死锁检测与处理

InnoDB 死锁检测(wait-for graph):
  检测事务间的等待关系图,发现环即判定死锁

处理策略:
  1. 选择回滚代价最小的事务(通常修改行数少的)
  2. 回滚该事务,释放锁
  3. 客户端收到 ERROR 1213 (40001): Deadlock found
  4. 应用层应捕获并重试
-- 查看死锁日志
SHOW ENGINE INNODB STATUS;
-- 在 LATEST DETECTED DEADLOCK 段中查看最近死锁详情

-- 查看当前锁等待
SELECT * FROM information_schema.INNODB_TRX;       -- 活跃事务
SELECT * FROM information_schema.INNODB_LOCKS;      -- 当前锁
SELECT * FROM information_schema.INNODB_LOCK_WAITS; -- 锁等待关系

3.3 死锁预防

策略说明
固定加锁顺序所有事务按相同顺序获取锁(如先小 id 后大 id)
降低事务粒度事务内操作尽量少,持有锁时间尽量短
NOWAIT / SKIP LOCKEDFOR UPDATE NOWAIT 锁不住立即失败;SKIP LOCKED 跳过已锁行
应用层重试捕获死锁异常后自动重试(建议指数退避)
// 应用层死锁重试
template.executeWithRetry(() -> {
    jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId);
    jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId);
}, 3, Duration.ofMillis(100));  // 最多重试3次,间隔100ms

4. 锁相关的 SQL 提示

-- 显式加读锁
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;

-- 显式加写锁
SELECT * FROM orders WHERE id = 1 FOR UPDATE;

--  NOWAIT(锁不住立即报错)
SELECT * FROM orders WHERE id = 1 FOR UPDATE NOWAIT;

-- SKIP LOCKED(跳过被锁的行,用于队列场景)
SELECT * FROM orders WHERE status = 'pending' 
  ORDER BY id FOR UPDATE SKIP LOCKED LIMIT 10;

-- 查看锁信息
SHOW STATUS LIKE 'Innodb_row_lock%';
-- Innodb_row_lock_waits:锁等待次数
-- Innodb_row_lock_time_avg:平均锁等待时间

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「database」更多文章

  1. 缓存架构演进之路:从单机 Redis 到亿级分布式多级缓存体系
  2. Redis 7.x 重大新特性与架构升级深度解析
  3. Redis 消息队列深度对比:Pub/Sub、Streams 与 Kafka/RabbitMQ 选型指南