什么是死锁?死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象,若无外力干预,这些事务都将无法进行下去。死锁的产生必须满足以下四个条件:
条件名称 | 描述 | 简单解释 |
---|---|---|
互斥条件 (Mutual Exclusion) | 一个资源每次只能被一个事务持有。 | 锁是独占的,不能共享。 |
请求与保持条件 (Hold and Wait) | 一个事务在持有至少一个资源的同时,又请求新的资源(该资源已被其他事务持有)。 | 占着一个,还想要另一个。 |
不剥夺条件 (No Preemption) | 事务已获得的资源在未使用完之前,不能被其他事务强行剥夺。 | 已经拿到手的锁,别人不能抢走。 |
循环等待条件 (Circular Wait) | 多个事务之间形成一种首尾相接的循环等待资源关系。 | 事务A等事务B,事务B又在等事务A。 |
死锁发生以后,只有部分或完全回滚其中一个事务,才能打破死锁。多数情况下只需要重新执行因死锁回滚的事务即可。举个例子:
先准备一张表并查看其结构:
准备两个DOS窗口,分别命名A窗口、B窗口:
A窗口开启事务并执行:
BEGIN; UPDATE dotcpp_user SET hobby = '刷题' WHERE id = 1;
随即在B窗口里开启事务并执行:
BEGIN; UPDATE dotcpp_user SET hobby = '刷网课' WHERE id = 2;
由于id不同,非同行修改行为,不会出现锁等待。
为了看到死锁现象,于是你返回A窗口并执行:
UPDATE dotcpp_user SET hobby = '刷网课' WHERE id = 2;
快速来到B窗口执行(只有50s,手速跟得上吧):
UPDATE dotcpp_user SET hobby = '刷题' WHERE id = 1;
发生报错:
通过:
SHOW ENGINE INNODB STATUS;
得到(仅部分展示):
2025-09-17 16:28:02 0x4af0 *** (1) TRANSACTION: TRANSACTION 2427, ACTIVE 693 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 3 row lock(s), undo log entries 1 MySQL thread id 42, OS thread handle 21172, query id 237 localhost ::1 Dotcpp updating UPDATE dotcpp_user SET hobby = '?????' WHERE id = 2 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 4 page no 4 n bits 72 index PRIMARY of table `dotcpp`.`dotcpp_user` trx id 2427 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000000097b; asc {;; 2: len 7; hex 010000012b0151; asc + Q;; 3: len 12; hex 646f74637070557365723031; asc dotcppUser01;; 4: len 6; hex e588b7e9a298; asc ;; *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 4 page no 4 n bits 72 index PRIMARY of table `dotcpp`.`dotcpp_user` trx id 2427 lock_mode X locks rec but not gap waiting Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000002; asc ;; 1: len 6; hex 00000000097c; asc |;; 2: len 7; hex 02000001270151; asc ' Q;; 3: len 12; hex 646f74637070557365723032; asc dotcppUser02;; 4: len 9; hex e588b7e7bd91e8afbe; asc ;;
由于交叉执行,窗口A、B尝试获取对方已经持有的那条记录的锁,从而形成了循环等待,即死锁。
如何检测死锁呢?
查看开闭状态参数:innodb_deadlock_detect;
默认情况是开启的。
ON:打开死锁检测(默认):用少量性能开销换取应用的稳定性和可预测性。系统会自动处理死锁,自动滚回其中某一事务,应用程序主要处理重试逻辑。
OFF:关闭死锁检测:用应用系统的复杂性和潜在故障风险来换取极致的性能。数据库不再处理死锁,依赖超时机制并通过锁等待来处理。
锁等待之前我们提到过,MySQL默认是50s,可以通过参数%innodb_lock_wait%进行查看:
要进行修改的话,参数是:innodb_lock_wait_timeout
举个例子,50s不符合我的习惯,60s就行了,我就局部设置一下为60s就好了:
SET session innodb_lock_wait_timeout= 60;
实际工作中,我们要尽量锁等待情况的发生,下面提供四种方法有效的方法:
核心要点 | 方法 |
---|---|
顺序访问 | 并发操作多表或多行时,约定以相同顺序进行访问。 |
及时提交 | 事务结束后立即提交或回滚,缩短锁持有时间。 |
一次锁定 | 在同一事务中,一次性获取所有需要的资源锁。 |
锁粒度升级 | 对易死锁场景,使用表级锁替代行级锁。 |
C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:
一点编程也不会写的:零基础C语言学练课程
解决困扰你多年的C语言疑难杂症特性的C语言进阶课程
从零到写出一个爬虫的Python编程课程
只会语法写不出代码?手把手带你写100个编程真题的编程百练课程
信息学奥赛或C++选手的 必学C++课程
蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程