资讯动态

MySQL死锁详解:原理、检测机制与排查预防实战

发布时间:2026/10/14 15:53:20 来源:尧图企业网站定制
1. 什么是MySQL死锁先看一个能跑通的例子先说结论死锁不是“锁死不动”而是两个或多个事务互相持有对方需要的锁并且谁也不撒手最终形成循环等待。MySQL的InnoDB引擎检测到这种循环等待后会强制回滚其中一个事务让它释放所有锁另一个事务才能继续往前走。我用一个最简单的场景来演示。假设有两张表一张用户表一张订单表两张表各有一条数据。两个事务同时启动事务A先更新用户表的某一行事务B先更新订单表的某一行然后事务A回头去更新订单表那一行事务B回头去更新用户表那一行。此时A持有用户表的锁等订单表的锁B持有订单表的锁等用户表的锁两个事务互相等着对方释放资源死锁就出现了。直接上SQL。先建两张测试表CREATE TABLE t_user ( id int NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_order ( id int NOT NULL AUTO_INCREMENT, order_no varchar(50) DEFAULT NULL, user_id int DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;插入两条测试数据INSERT INTO t_user (id, name) VALUES (1, 张三), (2, 李四); INSERT INTO t_order (id, order_no, user_id) VALUES (1, ORD001, 1), (2, ORD002, 2);然后打开两个终端会话模拟两个并发事务。注意MySQL默认是自动提交的做实验前先把自动提交关掉-- 两个会话都执行 SET autocommit 0;会话1执行UPDATE t_user SET name 张三-改 WHERE id 1;会话2执行UPDATE t_order SET order_no ORD002-改 WHERE id 2;此时会话1持有t_user表id1这一行的行锁会话2持有t_order表id2这一行的行锁。接着两边继续操作会话1更新订单表UPDATE t_order SET order_no ORD001-改 WHERE id 1;会话2更新用户表UPDATE t_user SET name 李四-改 WHERE id 2;到这一步死锁几乎必然发生。InnoDB会在几百毫秒内检测到循环等待然后选择回滚其中一个事务另一个事务正常完成。这个例子虽然简单但它把死锁最核心的四个条件全凑齐了互斥、持有并等待、不可剥夺、循环等待。后面我会逐个条件拆开讲也会把排查方法、日志分析和各种真实场景下的死锁原因都铺开说清楚。2. 死锁产生的四个必要条件死锁不是凭空冒出来的它必须同时满足四个条件。我逐个解释顺便说明每个条件在MySQL里对应的实际表现。2.1 互斥条件资源一次只能被一个事务占用。MySQL的行锁、表锁、间隙锁天然就是互斥的一个事务给某一行加了排他锁另一个事务就不能再给这一行加任何写锁。这个条件在InnoDB里几乎无法避免因为数据库本身就需要锁来保证事务隔离性。所以我们在讨论死锁时基本默认互斥条件已经满足真正可以做文章的是后面三个条件。2.2 持有并等待一个事务已经持有至少一个资源同时又在等待获取新的资源。还是拿上面的例子说会话1已经持有t_user表id1的行锁又在等t_order表id1的行锁会话2则相反。这个条件在长事务里特别容易出问题。事务里执行的SQL越多、消耗的时间越长持有的锁就越多等待新锁的窗口期就越长碰上别的事务的概率自然水涨船高。2.3 不可剥夺事务持有的锁不能被其他事务强行抢走只能由持有者自己释放。MySQL的锁策略就是如此没有“锁超时抢占”这种机制顶多是在等待锁的时候有个innodb_lock_wait_timeout参数控制最长等待时间默认50秒。注意这个锁等待超时和死锁检测是两回事。死锁检测是主动发现循环等待锁等待超时是干等50秒后放弃两者触发机制不同。2.4 循环等待事务之间形成一个等待环路A等B、B等C、C等A或者更简单的两个事务互等。这个条件才是死锁检测机制真正盯着的目标。InnoDB的后台线程会维护一张事务等待图每条边代表“一个事务正在等待另一个事务持有的锁”一旦图中出现环就判定为死锁。2.5 一个容易被忽略的补充条件锁的粒度不一致实际工作中我见过很多死锁案例表面上看四个条件都满足但根本原因其实是两个事务加锁的粒度不对称。比如事务A先锁用户表再锁订单表事务B先锁订单表再锁用户表锁的获取顺序不一致非常容易撞车。如果所有事务都按照相同的顺序获取锁比如先用户表后订单表那么即使并发量很高也只是排队等待不会形成循环。这一点在后面讲预防策略时会反复提到。3. InnoDB死锁检测机制它是怎么发现死锁的死锁检测不是DBA手动盯出来的InnoDB内部有一套自动机制。我从原理和参数两个角度拆开讲。3.1 事务等待图InnoDB维护了一张事务等待图记录当前所有事务之间的锁等待关系。每当事务尝试获取一把被其他事务持有的锁时就会在等待图里加一条边。图中出现环说明事务之间形成了循环等待。比如事务A等待事务B持有的锁事务B等待事务A持有的锁图中就是一个长度为2的环。InnoDB检测到环之后会挑选一个代价最小的事务回滚释放它的全部锁另一个事务继续执行。3.2 死锁检测的触发时机死锁检测发生在事务请求锁被阻塞的瞬间。也就是说只有当某个事务真的因拿不到锁而进入等待状态时InnoDB才会检查等待图。如果所有事务都在顺畅执行、没有锁等待就不存在死锁检测的必要。这里有个性能上的细节频繁的死锁检测本身会耗费CPU资源。在高并发场景下如果大量事务同时竞争少量行锁每次锁冲突都会触发一次等待图遍历开销不小。所以InnoDB提供了一个开关参数innodb_deadlock_detect默认开启在极端高并发且死锁出现频率极低的情况下可以考虑关掉它来提升吞吐量。3.3 相关参数速览参数名默认值作用innodb_deadlock_detectON是否开启死锁检测关闭后只能等锁超时兜底innodb_lock_wait_timeout50单个事务等待锁的最长秒数超时报错回滚innodb_rollback_on_timeoutOFF锁等待超时后是否回滚整个事务默认只回滚出错的那条SQL需要特别说明的是innodb_rollback_on_timeout。默认情况下一条SQL因为锁等待超时报错事务并不会整体回滚只是这一条语句被回滚了。如果你在事务里已经执行了多条SQL前面的修改还在后面的操作还能继续。只有把这个参数设为ON超时后才会回滚整个事务。3.4 死锁与锁等待超时的区别很多人分不清这两个概念。死锁是循环等待InnoDB主动检测并立刻牺牲一个事务锁等待超时是单边等待一个事务等着另一个事务释放锁但对方没有形成环只是执行太慢或持有锁太久等待方干等了50秒后放弃。举个例子事务A更新某一行后迟迟不提交事务B也来更新同一行B会一直等。这时候并不存在死锁因为A没有在等B的锁。但B也不能无限等下去50秒后报Lock wait timeout exceeded错误。这种场景在慢SQL加长事务的叠加下非常常见。4. 实用排查手段如何定位死锁演示完死锁之后最核心的问题是线上真出死锁了我该怎么办。下面是我实际排查死锁时最常用的一套流程按步骤走基本都能定位到根因。4.1 查看最近一次死锁日志MySQL提供了专门查看死锁信息的命令SHOW ENGINE INNODB STATUS;输出内容里的LATEST DETECTED DEADLOCK段落记录的是最近一次死锁的完整信息包括两个事务分别执行了什么SQL、持有和等待哪些锁、最后谁被回滚了。实战技巧是这条命令只能看最近一次死锁日志如果你的系统死锁发生频繁后发生的会覆盖先发生的。如果需要留存证据建议死锁一旦发生就立刻执行SHOW ENGINE INNODB STATUS把输出完整存到本地文件里。4.2 开启死锁日志落盘如果希望死锁日志自动保存到MySQL错误日志文件里可以设置参数innodb_print_all_deadlocks ON这个参数开启后每次死锁都会完整记录到错误日志中不再被“最近一次”覆盖。线上环境我建议直接打开性能开销微乎其微但排查问题时的收益极大。查看错误日志位置SHOW VARIABLES LIKE log_error;找到日志文件后可以用grep直接搜索Deadlock关键字grep -n LATEST DETECTED DEADLOCK /var/log/mysql/error.log4.3 分析死锁日志的关键字段死锁日志的结构虽然看起来吓人但核心信息就那么几块。我拆开讲一下怎么看。死锁日志里会有两个事务的完整上下文每个事务部分包含事务ID和状态已经持有的锁列表HELD LOCKS正在等待的锁WAITING FOR THIS LOCK TO BE GRANTED最近执行的SQL语句拿到这些信息后先回答三个问题两个事务各持有什么锁各在等什么锁谁被选为受害者回滚了。把这三个问题理清楚死锁原因基本就浮出水面了。4.4 实时监控锁等待情况死锁发生是瞬时行为如果不在现场事后看日志是最有效的。但如果想实时观察当前有没有锁等待可以用下面这组查询。查看当前有哪些事务正在运行SELECT * FROM information_schema.INNODB_TRX;查看当前锁等待关系SELECT * FROM information_schema.INNODB_LOCK_WAITS;查看当前所有锁SELECT * FROM information_schema.INNODB_LOCKS;我通常在排查时先用INNODB_TRX看哪些事务处于RUNNING状态且持续时间长再用INNODB_LOCK_WAITS看它们之间是否存在等待关系。如果发现有事务一直在等结合INNODB_TRX里的trx_query字段能直接定位是哪条SQL卡住了。注意不同MySQL版本的INNODB_LOCKS表字段略有差异8.0版本中部分信息也迁移到了performance_schema.data_locks表中字段名不同但排查思路一致。5. 死锁日志实战解读手把手带你看一份完整日志理论讲太多不如直接看一份真实日志。下面是我之前在测试环境复现的一个死锁案例日志已经脱敏但结构完整我一行一行带大家读。5.1 死锁日志原文------------------------ LATEST DETECTED DEADLOCK ------------------------ 2024-06-15 14:32:18 0x7f9a3c1b2700 *** (1) TRANSACTION: TRANSACTION 4521098, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 8, OS thread handle 140210206723328, query id 982 localhost root updating update t_order set order_no ORD001-改 where id 1 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 7 page no 4 n bits 72 index PRIMARY of table test.t_user trx id 4521098 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 8 page no 5 n bits 80 index PRIMARY of table test.t_order trx id 4521098 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 *** (2) TRANSACTION: TRANSACTION 4521099, ACTIVE 10 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 9, OS thread handle 140210207823328, query id 985 localhost root updating update t_user set name 李四-改 where id 2 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 8 page no 5 n bits 80 index PRIMARY of table test.t_order trx id 4521099 lock_mode X locks rec but not gap Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 7 page no 4 n bits 72 index PRIMARY of table test.t_user trx id 4521099 lock_mode X locks rec but not gap Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 *** WE ROLL BACK TRANSACTION (2)5.2 日志逐段解析第一部分显示的是事务1的信息。TRANSACTION 4521098正处于ACTIVE状态已经运行12秒正在等待锁。它执行的SQL是update t_order但注意它当前HOLDS的锁是t_user表上的WAITING FOR的锁是t_order表上的。这就形成了一个交叉事务先改了用户表又回头改订单表。第二部分是事务2的信息。TRANSACTION 4521099运行了10秒执行的SQL是update t_user但HOLDS的锁是t_order表上的WAITING FOR的锁是t_user表上的。最后一行WE ROLL BACK TRANSACTION (2)告诉你InnoDB选择了事务2作为受害者将其整体回滚。事务1则继续执行。5.3 日志里的lock_mode X locks rec but not gap怎么理解看日志时经常会碰到这类描述。lock_mode X是排他锁locks rec说的是行锁but not gap表示不是间隙锁。这三者组合起来的意思是这是个普通的行级排他锁只锁住了具体的一行记录没有锁间隙。如果日志里出现lock_mode X locks gap before rec说明是间隙锁如果是lock_mode X locks rec but not gap就是记录锁还有lock_mode X locks rec and gap代表临键锁同时锁住记录和间隙。搞清楚这个才能判断死锁是否由间隙锁引起。5.4 为什么死锁日志能帮你复现问题日志里其实已经隐含了复现路径。还是上面这个例子两条SQL交互执行事务1执行update t_user where id 1持有t_user行1的锁事务2执行update t_order where id 2持有t_order行2的锁事务1执行update t_order where id 1等待事务2释放t_order行2的锁但t_order行2和行1不是同一行这里其实没有直接冲突等一下这个例子里事务1改的是t_order的id1事务2改的是t_order的id2为什么会产生锁冲突这个问题问得好。关键在于索引。如果t_order表上有个索引而UPDATE语句的WHERE条件通过索引扫描命中了额外的记录锁定的范围会扩大。比如t_order表上如果有user_id的普通索引且user_id值相同或相邻gap锁就会锁住区间。日志里虽然显示locks rec but not gap但如果执行计划走了user_id索引锁的其实是索引记录主键只是回表去定位。所以死锁复现不能只看SQL表面还得看执行计划。6. 真实场景下的几类高频死锁原因看完整份日志后我把实际工作中碰到最多的几类死锁原因总结一下。这些不是教科书上的四条必要条件而是工程现场更容易踩到的坑。6.1 不同事务以相反顺序更新多张表这是最经典的一类。两个事务都涉及用户表和订单表但更新顺序不一致一个先用户后订单一个先订单后用户。前面演示的例子就是这类。如果业务逻辑里每次都按照相同的顺序获取锁死锁基本可以消灭大半。比如统一约定先操作主表再操作从表或者在代码里对所有涉及的表按名称排序后依次加锁。6.2 间隙锁造成的死锁间隙锁是InnoDB在可重复读隔离级别下为了阻止幻读而引入的锁机制。它锁住的不是具体某一行而是一个范围区间。两个事务在同一个范围区间插入数据可能各自持有间隙锁又互相等待对方的间隙锁释放。具体来说事务A执行SELECT ... WHERE id BETWEEN 1 AND 5 FOR UPDATE锁住了这个区间的间隙事务B执行INSERT INTO ... VALUES (3)需要在同一间隙插入数据等待A释放间隙锁。如果此时事务B又持有另一个区间的间隙锁而A也需要往那个区间插入数据死锁就产生了。避免间隙锁死锁最直接的方式是降低隔离级别为读已提交或者改造SQL让锁尽量落在记录上而不是间隙上。6.3 外键和二级索引带来的额外锁外键约束会在插入或更新时自动检查关联表获取额外的锁。二级索引也会让锁的范围扩大因为InnoDB的锁既加在主键索引上也加在二级索引记录上。我之前排查过一个案例一个订单表上有user_id的普通索引两个事务同时更新同一条用户记录又各自插入关联订单结果其中一个事务在插入时对user_id索引的相同区间加了间隙锁与另一个事务的间隙锁冲突触发了死锁。6.4 批量操作和范围更新一条UPDATE ... WHERE ...范围条件可能锁住大量行如果两条范围更新的区间有重叠锁竞争会明显加剧。比如事务A执行UPDATE t_order SET status 1 WHERE id BETWEEN 1 AND 1000事务B执行UPDATE t_order SET status 2 WHERE id BETWEEN 800 AND 1500。两个事务都要更新800到1000这一段B必须等A提交后才能拿到这一段的锁。如果A在更新前还读了另外的表B也在读就可能形成循环。遇到这类场景可以把大事务拆成小批量每批只处理几百行提交后再处理下一批锁持有时间短了死锁概率会明显下降。6.5 唯一键冲突引发死锁插入场景里有个不那么直观的死锁源同一张表上两个事务同时插入相同唯一键值的记录。A插入成功但未提交持有该唯一键的锁B尝试插入相同键值的记录唯一键检查发现冲突等待A提交或回滚。此时C也插入同键值记录多个事务都卡在这个唯一键上加上各自其他操作就可能形成环。这类死锁的日志里经常能看到locks gap before rec的信息也就是间隙锁和插入意向锁在打架。6.6 长事务叠加慢SQL长事务本身不会直接导致死锁但它扩大了死锁的窗口期。事务持有的锁越多、时间越长与其他事务产生交叉等待的可能性越大。很多死锁案例的根源其实是某个慢SQL把事务拖长了其他事务等不及又抢着改同一批数据结果撞在了一起。所以我在排查死锁时除了看死锁日志本身还会去查有没有慢SQL、有没有异常长事务。往往把慢SQL优化掉死锁也跟着消失了。7. 死锁发生后的业务处理策略死锁只能尽量降低频率不敢说彻底杜绝。所以业务代码里必须考虑死锁后的处理策略不能只靠数据库层硬扛。7.1 数据库自动回滚的机制InnoDB检测到死锁后会立刻回滚其中一个事务。被回滚的事务会收到一个错误码在MySQL里是ERROR 1213对应消息为Deadlock found when trying to get lock; try restarting transaction。业务代码如果直接忽略这个错误被回滚的事务里已经执行的操作等于白做后续逻辑也会基于错误前提继续跑数据一致性就成了问题。所以任何写操作都应该捕获这个错误码并做补偿。7.2 Java侧的处理方式Java项目里如果用的是Spring的声明式事务死锁异常会在抛出的DataAccessException中体现。我习惯在业务代码中捕获到这个异常后做重试。重试的关键是重试的不是单条SQL而是整个事务方法。比如Override public void updateOrders() { int retryCount 0; int maxRetry 3; while (retryCount maxRetry) { try { doUpdateOrder(); // 被Spring事务代理的方法 return; } catch (DeadlockLoserDataAccessException e) { retryCount; if (retryCount maxRetry) { throw e; } // 短暂休眠让其他事务先走完 Thread.sleep(50L * retryCount); } } }注意一个细节必须是重试整个事务方法而不是在同一个事务里重试那条SQL。因为死锁发生后事务已经处于回滚状态事务上下文可能已失效继续在同一个事务里执行SQL会收到意想不到的结果。7.3 重试窗口和退避策略死锁重试不能无限进行也不能太激进。我常用的策略是最多重试3次每次重试前等待的时间逐渐拉长比如50毫秒、100毫秒、200毫秒。这样做的目的是给占据锁的另一个事务留出足够的提交时间。如果重试3次还是失败基本可以判定业务上存在锁竞争过度的结构性问题需要从业务逻辑和SQL设计层面解决而不是靠重试硬扛。7.4 悲观锁换成乐观锁的思考有些场景下死锁的根源是过度依赖数据库行锁。如果业务允许可以用乐观锁替代悲观锁。具体做法是给表加一个version字段更新时带上版本号条件UPDATE t_order SET order_no 新值, version version 1 WHERE id 1 AND version 10;受影响行数为0说明版本不对业务层可以重新读取数据再试。这种方式不持有数据库行锁自然没有死锁问题。但要注意乐观锁适合并发冲突不激烈的场景如果同一行数据被高频更新乐观锁的重试成本反而更高。8. SQL层面的死锁预防手段前面讲的都是事后处理和宏观思路这一节给到SQL和表结构层面的具体手段照着做就能把死锁概率压下去。8.1 固定加锁顺序所有事务按照同样的顺序访问表。比如系统中同时涉及用户表和订单表统一写成先更新用户表再更新订单表。这样即使两个事务并发执行也只是排队等待不会形成环。这个约定需要代码规范来保证。可以在代码评审阶段加入这个检查点也可以在数据库层通过触发器或存储过程强制但后者实现成本高一般团队用代码规范就够了。8.2 让索引尽量精准UPDATE和DELETE语句的WHERE条件如果没有走索引InnoDB会锁住全表的记录锁范围呈几何级数扩大。给WHERE条件涉及的字段加合适的索引让SQL只锁住必要的行是减少锁冲突最基础的手段。建索引有个原则选择性高的列放在最前面。比如订单表里status字段只有几个取值单独建索引没有意义但order_no作为唯一业务编号建索引后定位单行非常精准。8.3 控制事务大小一个大事务锁定上千行数据执行10秒期间其他事务全被挡在外面容易引发连锁效应。拆分事务是常见优化比如批量更新1万行拆成每批500行批间提交。每个事务的锁持有时间短了死锁概率自然降低。拆分事务要留意业务一致性。如果一批操作必须原子完成就不能拆。比如转账必须同时扣款和入账拆成两个事务反而引入不一致风险。分拆的边界要以业务规则为第一优先级。8.4 隔离级别的取舍可重复读REPEATABLE READ是MySQL默认隔离级别间隙锁在这个级别下才会启用。如果业务能接受读已提交READ COMMITTED的语义把隔离级别往下调一档间隙锁造成的死锁会大幅减少。修改隔离级别的SQLSET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;注意隔离级别改动会影响事务语义读已提交下同一事务里两次查询可能读到不同数据。业务需要先确认能否接受这种变化不能为了消除死锁引入新的数据一致性问题。8.5 利用等价条件缩小锁范围有时候一条SQL的WHERE条件写法不同加锁范围截然不同。比如要更新某用户的所有订单如果user_id走的是普通索引InnoDB会锁住该用户订单的所有索引记录和主键记录如果先查出订单主键ID列表再用主键ID精确更新锁范围会精确很多。但要注意先查后改本身是两条SQL中间有窗口期并发下还是可能拼出问题。这个思路更适合做锁范围评估而不是一刀切的万能药。9. 高频问题排查速查表这一节把常见死锁场景、典型日志特征和对应处理建议整理成速查表现场排查时可以对着看比自己从零分析快得多。死锁场景日志/现象特征排查方向处理建议多表更新顺序不一致两个事务各自持有对方后续要更新的表上的锁检查业务代码中事务内SQL执行顺序统一表加锁顺序间隙锁冲突日志出现lock_mode X locks gap before rec检查隔离级别和范围查询a降低隔离级别或改造SQL唯一键冲突日志出现duplicate entry或insert intention锁检查并发插入的幂等性业务侧做唯一性预判插入失败重试二级索引范围更新日志里锁记录同时出现在主键索引和二级索引查看执行计划是否走索引、锁范围是否过大优化索引或分批更新长事务叠加事务ACTIVE时间很长锁持有数较多排查事务内SQL耗时和业务逻辑拆分事务优化慢SQL批量操作大批量UPDATE/DELETE语句锁大量行分析影响行数和执行计划分批处理控制每批事务规模10. 我踩过的一些坑和心得做数据库相关工作好几年死锁排查的案子接触了不少说几个印象最深的体会。第一个坑是死锁日志不是万能的。SHOW ENGINE INNODB STATUS只能看最近一次死锁如果系统里死锁发生频繁很多历史日志会被覆盖。所以我后面每接一个死锁排查任务第一步就是把innodb_print_all_deadlocks打开让日志先落盘再慢慢分析。宁可日志多占点磁盘也比出事之后日志被覆盖、无从下手强。第二个坑是不要把一切都推给数据库。有一年排查一个订单系统的死锁日志看了很久发现全是同一张表上的间隙锁冲突。后来深入业务才发现真正的问题是业务代码里一个大事务包了几十个子操作中间还有远程调用事务时间长达十几秒锁一直攥在手里不放。优化掉这个长事务后死锁频率直接降了一个数量级。数据库层面只是症状表现根子还在应用层设计。第三个心得是死锁重试一定要做但不能把重试当成万能药。我见过有团队在代码里加了死锁重试后死锁倒是被吞掉了但数据却发生了重复入库原因就是重试逻辑被放在了事务内部第一次事务部分执行、部分回滚状态已经乱了重试时又叠加了一遍。重试必须放在事务边界之外整个事务方法重新发起才能保证数据正确。第四个心得是死锁排查是门手艺活日志要会读业务要会问。光看日志能定位现象但要根治还得问清楚业务为什么要在同一时间改同一批数据是不是可以错峰是不是可以合并是不是可以用异步队列串行化。很多时候业务上稍微调整一下处理方式数据库的死锁问题就消失了根本不需要动一行SQL。如果你正在被死锁问题折磨我的建议是先搭好日志和监控把死锁现场完整记录下来然后用本文前面讲的方法逐段分析找出锁冲突的交叉点。大多数死锁问题都能通过调整事务顺序、缩小锁范围、拆分长事务这三板斧解决。真正需要改架构才能解决的死锁其实很少见。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑