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 LOCKED | FOR 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:平均锁等待时间
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。