在关系数据库(如 MySQL、PostgreSQL、Oracle、SQL Server 等)中,死锁通常出现在多个事务并发访问相同资源(行、表、索引)并以不同顺序加锁时。完全避免死锁很难,但可以通过设计和运维手段大幅减少死锁发生概率,或在发生后快速恢复。
下面从原理 → 预防策略 → 实战建议三个层面说明。
死锁通常同时满足:
数据库无法消除前三条,但可以打破“循环等待”。
所有事务以相同顺序访问表和行
❌ 容易死锁:
事务1:先锁 A,再锁 B
事务2:先锁 B,再锁 A
✅ 推荐:
所有事务都:先 A,后 B
实际做法:
-- 按主键顺序更新,减少死锁
UPDATE account SET balance = balance - 100 WHERE id IN (1,2,3);
锁持有越久,冲突概率越大。
建议:
COMMIT 或 ROLLBACKBEGIN;
UPDATE ...;
COMMIT; -- 越快越好
优先使用行锁而非表锁。
LOCK TABLES
SELECT ... FOR UPDATE(无索引)
WHERE 条件走索引(否则可能升级为表锁)没有索引 = 锁很多行甚至全表
✅ 建议:
UPDATE / DELETE / SELECT FOR UPDATE 的字段必须建索引-- 不好(可能锁全表)
UPDATE user SET status=1 WHERE DATE(create_time)='2024-01-01';
-- 好(走索引)
UPDATE user SET status=1 WHERE create_time BETWEEN '2024-01-01' AND '2024-01-02';
不同隔离级别锁行为不同:
| 隔离级别 | 死锁风险 | 说明 |
|---|---|---|
| READ UNCOMMITTED | 低 | 很少用 |
| READ COMMITTED | 中 | 推荐(Oracle 默认) |
| REPEATABLE READ | 高 | MySQL 默认,易锁范围 |
| SERIALIZABLE | 最高 | 几乎串行 |
✅ 很多系统使用:
典型场景:
优化方式:
UPDATE ... SET counter = counter + 1(原子操作)SELECT * FROM t; -- 不加锁
SELECT * FROM t WHERE id=1 FOR UPDATE;
避免不必要的:
SELECT ... LOCK IN SHARE MODE
数据库通常自带死锁检测:
可调参数(示例):
innodb_lock_wait_timeout = 5 # 秒
应用层要:
✅ 固定加锁顺序
✅ 事务尽量小
✅ 索引必须合理
✅ 避免长事务
✅ 减少热点竞争
✅ 捕获死锁并重试
✅ 使用合适隔离级别
关系数据库防止死锁的核心是:统一访问顺序 + 缩短加锁时间 + 合理索引 + 控制事务粒度。
如果你愿意,我可以:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。