资讯动态

线上MySQL死锁排查实战:从死锁日志定位到索引优化

发布时间:2026/9/3 2:15:14 来源:尧图企业网站定制
Java 面试遇到“线上 MySQL 出现死锁怎么办”时如果只回答“重启服务”基本等于主动暴露对数据库事务和锁机制的理解停留在表面。线上 MySQL 死锁不是不能处理而是很多人不知道如何从死锁日志里定位事务顺序、SQL 语句、锁索引最后只能选择重启应用或切换流量等死锁再次出现。真正的处理链路应该是先取证再定位再修复再预防。数据库死锁不是数据库“坏掉”而是并发事务在争夺行锁时形成了循环等待。InnoDB 默认会检测死锁并回滚其中一个事务所以业务方往往只是看到某一条 SQL 抛出异常却不知道这个异常背后有另一个事务正在正常提交。处理死锁的关键不是停止数据库而是读懂死锁日志找出事务之间的资源申请顺序再决定改代码、改索引还是改事务边界。1. 先搞清楚死锁在 MySQL 里到底是怎么发生的1.1 死锁不是“卡住”而是锁等待形成环MySQL 默认使用 InnoDB 存储引擎InnoDB 在事务执行 DML 语句时会根据索引给记录加锁。以最简单的行锁为例事务 A 更新了 id1 的记录会持有 id1 这一行的排他锁事务 B 更新 id2 的记录会持有 id2 这一行的排他锁。如果接下来事务 A 想更新 id2它会等待事务 B 释放锁同时事务 B 想更新 id1它会等待事务 A 释放锁。两个事务互相等待谁也不会先释放自己已经持有的锁这就形成了死锁。死锁形成需要满足四个条件互斥、持有并等待、不可剥夺、循环等待。数据库里的行锁天然具备互斥和不可剥夺的特性因此真正需要控制的是“持有并等待”和“循环等待”。事务只有一条 UPDATE 时通常不会死锁因为只持有一把锁死锁往往发生在事务里包含多条 SQL并且多条 SQL 加锁的记录顺序不一致的场景。这里要注意不要把所有长时间等待都当成死锁。死锁是多个事务互相等待最终由 InnoDB 的死锁检测机制发现并回滚一个事务而“锁等待超时”是一个事务在等待另一个事务释放锁等待时间超过了innodb_lock_wait_timeout并不是循环等待。线上排查时先区分这两种情况处理思路是完全不同的。1.2 为什么重启服务不能解决根因服务重启后应用与数据库之间的连接断开连接池里的连接被重建应用侧未提交的事务也会被数据库回滚。此时数据库里临时持有的锁会释放于是数据库看起来“恢复正常”。但问题是死锁的真正来源不在服务的进程状态而在于业务代码的业务逻辑事务里多条 SQL 的加锁顺序不一致更新条件没有走索引导致锁范围扩大事务内包含外部调用或长时间计算锁持有时间过长隔离级别、间隙锁和插入意向锁产生冲突。这些条件不会因为服务重启而改变。流量恢复后同样的并发请求会以同样的 SQL 顺序再次执行死锁自然就会重新出现。重启服务如果发生在死锁日志还没有被完整抓取之前还会丢失最关键的现场证据。正确做法是先保存SHOW ENGINE INNODB STATUS和业务日志再决定下一步动作。所以面试时可以这样说重启服务只能作为应急手段不能作为解决方案。解决问题的前提是拿到死锁日志定位到具体的两个事务和它们申请的锁然后再调整事务顺序、索引或事务边界。这个回答既能体现有生产经验也能直接提醒面试官“我没有盲目重启”。2. 线上发现死锁后先按这条链路取证2.1 从业务日志里找错误码和事务线索线上数据库出现死锁后大多数情况是业务接口报错而不是数据库完全不可用。Java 应用使用 Spring 管理事务时如果底层 SQL 被 InnoDB 回滚应用日志里通常会出现类似下面的异常org.springframework.dao.DeadlockLoserDataAccessException: ### Error updating database. Cause: java.sql.SQLException: Deadlock found when trying to get lock; try restarting transaction这条异常里有两个关键信息异常类型是DeadlockLoserDataAccessException错误信息是“Deadlock found when trying to get lock; try restarting transaction”。出现这个异常时数据库已经选出一个事务进行回滚另一个事务正常提交。此时不要只盯着异常去改 SQL还要记录下异常发生的时间点、调用来源、执行的具体方法。如果系统接入链路追踪可以把这个时间点的 traceId、userId、订单号、账户号等字段一起拉出来。这些信息比“哪条 SQL 死锁”更接近根因因为死锁通常取决于两个请求的到达顺序而非单条 SQL 本身。2.2 用 SHOW ENGINE INNODB STATUS 查看最近一次死锁MySQL 提供了一个非常直接的排查入口SHOW ENGINE INNODB STATUS。在 MySQL 客户端执行这条命令返回结果里有一大段文本其中最需要注意的是LATEST DETECTED DEADLOCK部分它记录了最近一次检测到的死锁详情。SHOW ENGINE INNODB STATUS\G也可以只查看其中的相关段落SHOW ENGINE INNODB STATUS\G输出内容类似------------------------ LATEST DETECTED DEADLOCK ------------------------ 2024-06-01 10:23:45 0x7f... *** (1) TRANSACTION: TRANSACTION 35543, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136 ... *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 13 page no 4 n bits 72 index uk_account_no of table test.t_account trx id 35543 lock_mode X locks rec but not gap waiting ... *** (2) TRANSACTION: TRANSACTION 35544, ACTIVE 9 sec starting index read ... *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 13 page no 4 n bits 72 index uk_account_no of table test.t_account trx id 35544 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: ... *** WE ROLL BACK TRANSACTION (2)这段日志里的核心信息是事务 1 在等待uk_account_no上的排他锁事务 2 持有uk_account_no上的排他锁同时也在等待另一把锁最后一行WE ROLL BACK TRANSACTION (2)表示 InnoDB 选择回滚事务 2让事务 1 继续执行。日志中出现的lock_mode X表示排他锁locks rec but not gap表示这是一个记录锁而不是间隙锁。如果在日志中看到lock_mode X locks gap before rec或insert intention说明还涉及间隙锁或插入意向锁这通常和当前事务隔离级别、查询条件范围有关。需要注意的是SHOW ENGINE INNODB STATUS默认只保留最近一次死锁信息。如果死锁发生频率很高下一次死锁会把上一次的日志覆盖掉。生产环境建议开启下面的参数把所有死锁都打印到 MySQL 错误日志里。SET GLOBAL innodb_print_all_deadlocks ON;不过这是一个动态变量线上重启后可能失效最好同时写入 MySQL 配置文件并确认当前版本支持该参数。2.3 用 performance_schema 查当前锁等待现场如果死锁发生时某个请求仍然处于等待状态或者频繁出现锁等待可以借助performance_schema中的锁表来查看当前锁关系。MySQL 8.0 提供data_locks和data_lock_waits两张表前者记录当前持有和等待的锁后者记录锁等待关系。先看当前有哪些锁等待SELECT * FROM performance_schema.data_lock_waits\G再查看某个表上的锁信息SELECT * FROM performance_schema.data_locks WHERE OBJECT_SCHEMA test AND OBJECT_NAME t_account\G同时可以查看事务状态SELECT trx_id, trx_state, trx_started, trx_wait_started, trx_mysql_thread_id, trx_query FROM information_schema.innodb_trx\G把锁表、等待关系、事务表关联起来可以快速定位“谁在阻塞谁”。下面的 SQL 示例在 MySQL 8.0 中通常可用SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, l.OBJECT_SCHEMA, l.OBJECT_NAME, l.INDEX_NAME, l.LOCK_TYPE, l.LOCK_MODE FROM performance_schema.data_lock_waits w JOIN performance_schema.data_locks l ON w.REQUEST_ENGINE_LOCK_ID l.ENGINE_LOCK_ID JOIN information_schema.innodb_trx r ON w.REQUESTING_TRX_ID r.trx_id JOIN information_schema.innodb_trx b ON w.BLOCKING_TRX_ID b.trx_id\G如果环境里有sys库还可以直接使用sys.innodb_lock_waits视图它返回的信息更直观包含等待会话和阻塞会话、等待时长、SQL 片段等。不同 MySQL 版本的字段名会有些差异实际使用前先执行一次DESC sys.innodb_lock_waits确认列名。2.4 生产环境排查顺序清单线上遇到死锁时按下面这个顺序操作能最大程度保留证据并快速定位问题。先执行SHOW ENGINE INNODB STATUS保存完整的LATEST DETECTED DEADLOCK输出不要只截图异常。记录业务日志中死锁异常的时间点、接口方法、traceId、事务关联字段。执行SHOW PROCESSLIST查看当前会话状态重点关注State为updating或locked的会话。查询information_schema.innodb_trx找出执行时间较长或等待时间较长的事务。查询performance_schema.data_locks和data_lock_waits确认锁等待关系。对死锁日志中涉及的 SQL 执行EXPLAIN确认是否走索引、锁住的行数是否过大。根据事务顺序和 SQL 顺序分析根因最后再决定是否需要在应用层重试或调整事务边界。这套清单在生产环境非常实用。死锁的发生往往是瞬间的如果不第一时间保存日志等处理完其他故障再回来看可能已经查不到原始现场。3. 用最小示例复现一次死锁并逐行读懂日志3.1 建表和初始化数据为了不依赖复杂业务系统可以在本地 MySQL 环境里用一张账户表复现死锁。这样的最小示例也能帮助理解死锁日志。CREATE TABLE t_account ( id int NOT NULL AUTO_INCREMENT, account_no varchar(32) NOT NULL, balance decimal(12,2) NOT NULL DEFAULT 0.00, PRIMARY KEY (id), UNIQUE KEY uk_account_no (account_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; INSERT INTO t_account (account_no, balance) VALUES (1001, 100.00), (1002, 200.00), (1003, 300.00);这里使用唯一键uk_account_no是为了让WHERE account_no ?能精确命中单条记录方便观察行锁而不是表锁。如果这张表上没有这个唯一索引UPDATE语句可能需要扫描多行并加更多记录锁死锁日志会复杂很多。3.2 两个会话按相反顺序更新制造死锁死锁常见成因之一是同一批业务数据被多个事务以不同顺序更新。下面用两个 MySQL 终端模拟两个事务。Session ASTART TRANSACTION; UPDATE t_account SET balance balance - 10 WHERE account_no 1001;Session BSTART TRANSACTION; UPDATE t_account SET balance balance - 10 WHERE account_no 1002;此时事务 A 持有1001这行的锁事务 B 持有1002这行的锁。接下来在 Session A 中执行UPDATE t_account SET balance balance 10 WHERE account_no 1002;这条 SQL 会等待事务 B 释放1002上的锁因此 Session A 进入锁等待状态。此时再在 Session B 中执行UPDATE t_account SET balance balance 10 WHERE account_no 1001;事务 B 申请1001上的锁而1001被事务 A 持有于是两个事务形成循环等待。InnoDB 的死锁检测会自动发现这个循环并选择一个事务回滚。手动执行时如果两个事务的第二步执行得很快有可能在 InnoDB 死锁检测介入之前就触发锁等待超时为了稳定复现可以在事务 A 和事务 B 之间人为停顿几秒例如在 Session A 的第二步之前手动等待再执行 Session B 的第二步。复现成功后其中一个会话会收到类似这样的报错ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction另一个会话的 SQL 则能正常执行。这个现象说明死锁发生后数据库已经自动处理了应用要做的不是重启而是捕获这个 1213 错误并重试。3.3 读懂死锁日志的关键段落复现后再次执行SHOW ENGINE INNODB STATUS重点看LATEST DETECTED DEADLOCK。以下是一个简化后的日志结构*** (1) TRANSACTION: TRANSACTION 35543, ACTIVE 12 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s) ... *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 13 page no 4 n bits 72 index uk_account_no of table test.t_account trx id 35543 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 35544, ACTIVE 9 sec starting index read ... *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 13 page no 4 n bits 72 index uk_account_no of table test.t_account trx id 35544 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 13 page no 4 n bits 72 index uk_account_no of table test.t_account trx id 35544 lock_mode X locks rec but not gap waiting *** WE ROLL BACK TRANSACTION (2)这段日志显示事务 35543 和事务 35544 都在更新t_account表事务 35543 在等待uk_account_no索引上的排他锁事务 35544 持有uk_account_no索引上的排他锁同时也在等待另一条索引记录上的排他锁最终 InnoDB 选择回滚事务 35544。日志里的index uk_account_no很关键它告诉你死锁发生在哪个索引上。lock_mode X是排他锁locks rec but not gap表示锁定的是单条记录不包含间隙。如果日志中出现gap相关描述说明间隙锁也参与了死锁。3.4 用数据字典视图辅助分析在 MySQL 8.0 中死锁发生后如果还有事务处于等待状态可以直接查询performance_schema中的锁等待关系。下面的 SQL 可以把等待者、阻塞者、表名、索引名、锁类型都列出来SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, l.OBJECT_SCHEMA, l.OBJECT_NAME, l.INDEX_NAME, l.LOCK_TYPE, l.LOCK_MODE FROM performance_schema.data_lock_waits w JOIN performance_schema.data_locks l ON w.REQUEST_ENGINE_LOCK_ID l.ENGINE_LOCK_ID JOIN information_schema.innodb_trx r ON w.REQUESTING_TRX_ID r.trx_id JOIN information_schema.innodb_trx b ON w.BLOCKING_TRX_ID b.trx_id\G如果只关心当前哪些事务在等待哪些事务在阻塞也可以直接查询sys.innodb_lock_waitsSELECT * FROM sys.innodb_lock_waits\Gsys库视图把等待时间和 SQL 都处理得更可读适合快速定位问题会话。但要注意sys库在 MySQL 8.0 默认存在MySQL 5.7 的一些安装方式可能没有完整初始化版本差异需要在环境里先确认。4. 解决死锁的正确姿势从根因到机制4.1 先判断是“真死锁”还是“锁等待超时”拿到报错信息后第一步不是改代码而是判断到底遇到了哪一种情况。错误类型报错内容触发条件处理方向死锁Deadlock found when trying to get lock; try restarting transaction多个事务互相持有对方需要的锁形成循环等待保存死锁日志调整事务顺序、索引、隔离级别应用层重试锁等待超时Lock wait timeout exceeded; try restarting transaction一个事务持有锁时间太长另一个事务等待超过阈值查innodb_trx找到阻塞事务优化长事务这两个问题都可能导致接口超时但排查路径差别很大。锁等待超时通常和“长事务”关系更大比如某个事务忘了提交或者事务里包含了外部接口调用导致锁一直被占着。死锁则更像“并发请求顺序交叉”需要看的是多个事务之间的锁申请顺序。4.2 按事务顺序、索引、范围锁三个方向排查定位根因时可以从下面三个方向逐一排查。第一个方向是事务顺序。多个事务对同一组记录做写操作时如果访问顺序不一致很容易形成循环等待。转账场景里账户 A 转给账户 B代码先更新 A 再更新 B另一个请求 B 转给 A也先更新 B 再更新 A两个请求就会形成交叉。解决办法是在事务里对账户号进行排序保证所有事务都先更新小账户号再更新大账户号。第二个方向是索引。UPDATE语句的WHERE条件如果没有合适的索引InnoDB 会先扫描很多行并对扫描到的记录加锁。锁的范围越大不同事务之间发生冲突的概率越高。可以用EXPLAIN查看 SQL 的执行计划EXPLAIN SELECT * FROM t_account WHERE account_no 1001 FOR UPDATE;如果type列是ALL说明是全表扫描可能存在大量记录锁如果type列是const或ref说明走了索引锁定的记录更精确。死锁排查时优先对死锁日志中涉及的 SQL 逐个执行EXPLAIN找到扫描行数多的语句。第三个方向是范围锁。在默认的REPEATABLE READ隔离级别下InnoDB 不仅会对命中的记录加锁还可能在索引间隙上加间隙锁防止其他事务向这个范围插入数据。间隙锁和插入意向锁组合在一起也可能产生死锁。如果业务对“不可重复读”和“幻读”并不敏感可以考虑将隔离级别改为READ COMMITTED它能降低间隙锁带来的死锁概率。但这个改动要经过业务评估不能为了消灭死锁而引入新的数据一致性问题。4.3 InnoDB 死锁检测与相关参数怎么调InnoDB 默认会启用死锁检测相关参数主要有以下几个。innodb_deadlock_detect控制是否启用死锁检测默认是ON。当 InnoDB 检测到死锁后会立刻回滚其中一个事务让另一个事务继续执行。这个机制在处理低频死锁时非常好用但在极端高并发场景下死锁检测本身也会消耗 CPU。如果大量线程都在等待同一把热点锁表面看是锁等待实际上每个请求都要参与一次死锁检测会放大开销。因此某些场景会关闭死锁检测但这意味着死锁只能靠innodb_lock_wait_timeout超时回退可能导致几十秒的等待生产环境要非常慎重。innodb_lock_wait_timeout控制锁等待超时时间默认是 50 秒。高并发业务里50 秒过长会让请求线程长时间挂起调小后等待锁的事务会更快超时但事务反复失败的问题也会暴露得更明显。这个值需要结合业务接口的响应时间要求来设置不建议直接无脑调大或调小。innodb_print_all_deadlocks建议在排查阶段设为ON这样每一次死锁都会记入 MySQL 错误日志而不是只保留最近一次。开启后要注意错误日志量死锁频繁时日志文件增长会很快。参数默认值作用生产建议innodb_deadlock_detectON自动检测死锁并回滚一个事务一般保持默认确认瓶颈后才考虑关闭且要同时评估锁等待超时成本innodb_lock_wait_timeout50 秒等待锁的超时时间根据接口超时时间调小避免请求长时间堆积innodb_print_all_deadlocksOFF把每次死锁写入错误日志排查阶段打开问题修复后按日志量决定是否保留4.4 应用层如何处理死锁异常数据库回滚了其中一个事务之后应用程序不能假装什么都没发生必须捕获死锁异常并重试。如果是一张转账接口Spring 事务方法里的代码可能是这样的Transactional public void transfer(String fromAccount, String toAccount, BigDecimal amount) { accountDao.decreaseBalance(fromAccount, amount); accountDao.increaseBalance(toAccount, amount); }调用方可以在外层捕获 Spring 的DeadlockLoserDataAccessException并重试public void transferWithRetry(String fromAccount, String toAccount, BigDecimal amount) { int retryTimes 0; while (true) { try { transferService.doTransfer(fromAccount, toAccount, amount); return; } catch (DeadlockLoserDataAccessException ex) { if (retryTimes 3) { throw ex; } try { Thread.sleep(ThreadLocalRandom.current().nextLong(50, 200)); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw new RuntimeException(retry interrupted, ie); } } } }这里有两个关键点。第一重试必须发生在原事务被回滚之后不能在同一个事务方法内部 catch 异常后继续写 SQL否则后续操作仍然在已经标记为 rollback-only 的事务中。第二重试次数一定要限制并保留原始异常栈避免死锁持续发生时无限重试把下游压垮。还要考虑重试的幂等性。如果接口本身不具备幂等性比如“扣款成功但增加余额失败”这种组合操作重试前需要确认上一次事务到底有没有提交成功。更稳妥的做法是在业务表里增加幂等键或事务流水号重试时先查流水是否存在。4.5 一个转账死锁的修复案例这里用一个常见案例串起上面的排查思路。某个转账接口上线后高峰期频繁出现死锁日志显示两个事务都在uk_account_no上互相等待。通过保存的SHOW ENGINE INNODB STATUS发现事务 A 先更新账户1001再更新1002事务 B 先更新1002再更新1001。两根更新顺序交叉形成死锁。修复方式是让所有事务使用一致的更新顺序。最简单的方法是在进入事务前对两个账户号排序public void transfer(String accountA, String accountB, BigDecimal amount) { if (accountA.compareTo(accountB) 0) { String tmp accountA; accountA accountB; accountB tmp; } // 先更新 accountA再更新 accountB }这个改动很小但已经消除了“相反顺序更新”这个根因。同时给account_no建立唯一索引后UPDATE语句从全表扫描变成了精确命中单条记录锁范围也大幅缩小。上线后观察一段时间死锁告警消失。这个案例说明死锁修复通常不是靠重启服务而是靠调整代码执行顺序、索引和事务边界。5. 如何在 Java 面试中回答好这道题5.1 回答层级先讲排查再讲修复最后讲预防如果面试官问“线上 MySQL 出现死锁怎么办”不要停留在“重启服务”这一层。一个好的回答可以分四层。第一层说明第一动作不是重启而是保留现场。执行SHOW ENGINE INNODB STATUS查看LATEST DETECTED DEADLOCK确认死锁涉及的事务、SQL、表、索引。如果应用日志里出现了DeadlockLoserDataAccessException先记录发生时间和调用链。第二层说明定位方法。对比两个事务的锁申请顺序看是否出现“事务 A 持有 X 等待 Y事务 B 持有 Y 等待 X”。再通过EXPLAIN检查 SQL 是否走索引通过performance_schema.data_lock_waits查看当前阻塞关系。第三层说明修复方向。统一事务内访问记录的顺序压缩事务时间减少事务内无关操作优化UPDATE条件索引必要时在评估后调整隔离级别。第四层说明应用层保障。捕获死锁异常并进行有限次重试设置重试次数上限接入监控和告警避免死锁问题被日志淹没。这样的回答已经覆盖了“是什么、为什么、怎么做、怎么查”比单纯背八股更能体现实战经验。5.2 常见追问和速查表面试官听完回答后可能会继续追问下面的问题。追问回答要点死锁和锁等待有什么区别死锁是多个事务互相持有并等待对方锁形成循环锁等待是一个事务等待另一个事务释放锁不形成循环。死锁由 InnoDB 自动检测并回滚锁等待由innodb_lock_wait_timeout控制超时。死锁日志可以从哪里看SHOW ENGINE INNODB STATUS的LATEST DETECTED DEADLOCK开启innodb_print_all_deadlocks后可以看 MySQL 错误日志。如何避免死锁固定事务访问顺序、减少事务持有锁的时间、优化索引、合理设计批量更新语句必要时降低隔离级别。能不能关闭死锁检测一般不关闭。关闭后死锁只能靠锁等待超时回退可能导致请求长时间阻塞。只有在确认死锁检测造成明显性能瓶颈时经过评估才能考虑。为什么SELECT ... FOR UPDATE也会死锁FOR UPDATE会加排他锁多个事务对同一批记录按不同顺序加锁同样会形成循环等待。准备面试时最好能把这些要点背成自己的语言再结合一个实际项目案例讲清楚。没有真实案例的话也可以用上面那个转账账户排序的例子关键在于把逻辑讲通。5.3 从一道面试题延伸到生产排查体系这道题考察的不只是“死锁”一个知识点还牵扯到事务隔离级别、行锁、间隙锁、索引、事务边界、异常处理和监控告警。学习时建议做一次完整练习在本地数据库建两张表开启两个事务复现死锁再按照上面的排查步骤看日志、定位原因、修改代码、验证效果。面试前可以按下面这份清单自测能解释 InnoDB 行锁和SELECT ... FOR UPDATE的关系能说出REPEATABLE READ和READ COMMITTED在加锁上的主要差别能读懂死锁日志中的LOCK WAIT、HOLDS THE LOCK、WE ROLL BACK TRANSACTION能说出innodb_lock_wait_timeout和innodb_deadlock_detect的作用能写出应用层捕获死锁异常并重试的伪代码或小段 Java 代码能结合一个具体场景说明如何统一事务访问顺序。把这几个问题解决后再面对“线上 MySQL 出现死锁怎么办”就不再是背答案而是真的知道从哪里着手处理。线上数据库问题里死锁属于比较常见但隐蔽性高的一种。它的特点是事故发生后数据库通常还能继续工作只是个别事务被回滚业务侧表现为偶发异常。如果只靠重启服务解决下次流量高峰大概率还会复现。真正有效的做法是把死锁日志、事务 SQL、索引执行计划和应用调用链串起来找到那个交叉点再通过代码顺序、索引或事务边界调整消灭根因。这也是这道面试题想考察的候选人是不是具备“恢复现场、定位根因、系统性预防”的生产问题处理能力。

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

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

免费获取报价