记录一次数据库死锁;Deadlock found when trying to get lock;DeadlockLoserDataAccessException

本文详细介绍了MySQL中InnoDB存储引擎的死锁现象,包括死锁的产生原因、示例、检测与处理方法。探讨了如何通过优化事务设计和数据库操作来减少死锁的发生,确保数据库的稳定运行。

线上异常

org.springframework.dao.DeadlockLoserDataAccessException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
### The error may involve com.ncars.jxc.stock.mapper.OrderDeliverInfoMapper.updateByPrimaryKeySelective-Inline
### The error occurred while setting parameters
### SQL: update `stock_order_deliver_info`          SET `order_state`=?,                                                                                                                                                                                                                                                                                                                                               `update_user_id`=?,                                            `update_user_name`=?,                                                        `update_time`=?,                                           `deleted`=?          where id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
; ]; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:266)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:75)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:447)
	at com.sun.proxy.$Proxy94.update(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:295)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:59)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:53)
	at com.sun.proxy.$Proxy106.updateByPrimaryKeySelective(Unknown Source)
	at com.ncars.jxc.common.service.BaseService.updateByPrimaryKeySelective(BaseService.java:38)
	at com.ncars.jxc.stock.rest.impl.OrderDeliverInfoRestServiceImpl.updateStatus(OrderDeliverInfoRestServiceImpl.java:1336)
	at com.ncars.jxc.stock.rest.impl.OrderDeliverInfoRestServiceImpl.updateDeliverInfoState(OrderDeliverInfoRestServiceImpl.java:1284)
	at com.ncars.jxc.stock.rest.impl.OrderDeliverInfoRestServiceImpl$$FastClassBySpringCGLIB$$b9cb28e9.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:747)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)

14.7.5 InnoDB中的死锁

死锁是指由于每个事务都持有对方需要的锁而无法进行其他事务的情况。因为这两个事务都在等待资源变得可用,所以都不会释放它持有的锁。
当事务锁定多个表中的行时(通过诸如UPDATE或SELECT … FOR UPDATE之类的语句),但顺序相反,则会发生死锁。当此类语句锁定索引记录和间隙的范围时,由于时序问题,每个事务都获得了一些锁而没有获得其他锁,也会发生死锁。有关死锁的示例,请参见第14.7.5.1节“ InnoDB死锁的示例”。
为了减少发生死锁的可能性,请使用事务而不是LOCK TABLES语句。保持用于插入或更新数据的事务足够小,以使其长时间不保持打开状态;当不同的事务更新多个表或大范围的行时,在每个事务中使用相同的操作顺序(例如SELECT … FOR UPDATE);在SELECT … FOR UPDATE和UPDATE … WHERE语句中使用的列上创建索引。死锁的可能性不受隔离级别的影响,因为隔离级别更改了读取操作的行为,而死锁则是由于写入操作而发生的。有关避免和从死锁条件中恢复的更多信息,请参见第14.7.5.3节“如何最小化和处理死锁”。
如果确实发生了死锁,InnoDB会检测到该情况并回滚其中一个事务(受害方)。因此,即使您的应用程序逻辑正确,您仍必须处理必须重试事务的情况。要查看InnoDB用户事务中的最后一个死锁,请使用SHOW ENGINE INNODB STATUS命令。如果频繁的死锁凸显了事务结构或应用程序错误处理方面的问题,请在启用innodb_print_all_deadlocks设置的情况下运行,以将有关所有死锁的信息打印到mysqld错误日志中。有关如何自动检测和处理死锁的更多信息,请参见第14.7.5.2节“死锁检测和回滚”。

链接: https://dev.mysql.com/doc/refman/5.6/en/innodb-deadlocks.html.

14.7.5.1 InnoDB死锁的示例

排它锁(X锁)和共享锁(S锁).
所谓X锁,是事务T对数据A加上X锁时,只允许事务T读取和修改数据A
所谓S锁,是事务T对数据A加上S锁时,其他事务只能再对数据A加S锁,而不能加X锁,直到T释放A上的S锁
若事务T对数据对象A加了S锁,则T就可以对A进行读取,但不能进行更新(S锁因此又称为读锁),在T释放A上的S锁以前,其他事务可以再对A加S锁,但不能加X锁,从而可以读取A,但不能更新A.

以下示例说明了锁定请求将导致死锁时如何发生错误。该示例涉及两个客户端A和B。
首先,客户端A创建一个包含一行的表,然后开始事务。在事务中,A通过在共享模式下选择该行来获得该行的S锁:

mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i    |
+------+
|    1 |
+------+

接下来,客户端B开始事务并尝试从表中删除该行:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;

删除操作需要X锁。无法授予该锁,因为它与客户端A持有的S锁不兼容,因此该请求进入针对行和客户端B块的锁请求队列中。
最后,客户端A还尝试从表中删除该行:

mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

由于客户端A需要X锁才能删除行,因此在此处发生死锁。但是,不能授予该锁定请求,因为客户端B已经具有X锁定请求,并且正在等待客户端A释放其S锁定。由于B事先要求X锁,因此A持有的S锁也不能升级为X锁。结果,InnoDB为其中一个客户端生成一个错误并释放其锁。客户端返回此错误:

ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

届时,可以授予对另一个客户端的锁定请求,并从表中删除该行。

链接: https://dev.mysql.com/doc/refman/5.6/en/innodb-deadlock-example.html.

14.7.5.3 如何最小化和处理死锁

本节以第14.7.5.2节“死锁检测和回滚”中有关死锁的概念性信息为基础。它说明了如何组织数据库操作以最大程度地减少死锁和应用程序中所需的后续错误处理。
死锁是事务数据库中的经典问题,但是它们没有危险,除非它们如此频繁以至于您根本无法运行某些事务。通常,您必须编写应用程序,以便在由于死锁而使事务回滚时,它们始终准备重新发出事务。
InnoDB使用自动行级锁定。即使在仅插入或删除单行的事务中,也可能会遇到死锁。这是因为这些操作并不是真正的“原子”操作;它们会自动对插入或删除的行的(可能是多个)索引记录设置锁定。
您可以使用以下技术来处理死锁并减少发生死锁的可能性:
在任何时候,发出SHOW ENGINE INNODB STATUS命令来确定最新死锁的原因。这可以帮助您调整应用程序以避免死锁。
如果频繁出现死锁警告引起关注,请通过启用innodb_print_all_deadlocks配置选项来收集更多的调试信息。有关每个死锁的信息,不仅是最新的死锁,还记录在MySQL错误日志中。完成调试后,请禁用此选项。
如果由于死锁而失败,请始终准备重新发出事务。死锁并不危险。请再试一次。
保持交易小巧且持续时间短,以使交易不易发生冲突。
进行一系列相关更改后立即提交事务,以减少冲突的发生。特别是,不要长时间未提交事务而使交互式mysql会话保持打开状态。
如果使用锁定读取(SELECT … FOR UPDATE或SELECT … LOCK IN SHARE MODE),请尝试使用较低的隔离级别,例如READ COMMITTED。
修改事务中的多个表或同一表中的不同行集时,每次都要以一致的顺序执行这些操作。然后,事务形成定义明确的队列,并且不会死锁。例如,将数据库操作组织到应用程序中的函数中,或调用存储的例程,而不是在不同位置编码多个类似的INSERT,UPDATE和DELETE语句序列。
将选择好的索引添加到表中。然后,您的查询需要扫描更少的索引记录,并因此设置更少的锁。使用EXPLAIN SELECT来确定MySQL服务器认为哪个索引最适合您的查询。
使用更少的锁定。如果您有能力允许SELECT从旧快照返回数据,请不要在其中添加FOR UPDATE或LOCK IN SHARE MODE子句。在这里使用READ COMMITTED隔离级别是件好事,因为同一事务中的每个一致读取都从其自己的新快照读取。
如果没有其他帮助,请使用表级锁序列化事务。将LOCK TABLES与事务表(例如InnoDB表)一起使用的正确方法是,先以SET autocommit = 0(不是START TRANSACTION)后跟LOCK TABLES开始事务,并且在明确提交事务之前不调用UNLOCK TABLES。例如,如果您需要写入表t1并从表t2中读取,则可以执行以下操作:

SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;

表级锁可防止对表的并发更新,从而避免死锁,但代价是对繁忙系统的响应速度较慢。
序列化事务的另一种方法是创建一个仅包含一行的辅助“信号量”表。在访问其他表之前,让每个事务更新该行。这样,所有事务都以串行方式发生。注意,在这种情况下,InnoDB即时死锁检测算法也适用,因为序列化锁是行级锁。对于MySQL表级锁,必须使用超时方法来解决死锁。

链接: https://dev.mysql.com/doc/refman/5.6/en/innodb-deadlocks-handling.html.

14.7.5.2 死锁检测和回滚

InnoDB自动检测事务死锁并回滚一个或多个事务以打破死锁。 InnoDB会尝试选择小的事务以进行回滚,其中事务的大小由插入,更新或删除的行数决定。
如果innodb_table_locks = 1(默认值)且autocommit = 0,则InnoDB知道表锁,并且它上面的MySQL层知道行级锁。否则,在涉及由MySQL LOCK TABLES语句设置的表锁或由InnoDB以外的存储引擎设置的锁的情况下,InnoDB无法检测到死锁。通过设置innodb_lock_wait_timeout系统变量的值来解决这些情况。
当InnoDB对事务执行完全回滚时,将释放该事务设置的所有锁。但是,如果由于错误而仅回滚单个SQL语句,则可以保留该语句设置的某些锁。之所以会发生这种情况,是因为InnoDB以某种格式存储行锁,使得以后无法知道哪个语句设置了哪个锁。
如果SELECT在事务中调用存储的函数,并且函数内的语句失败,则该语句将回滚。此外,如果此后执行ROLLBACK,则整个事务都会回滚。
如果InnoDB Monitor输出的“最新检测到的DEADLOCK”部分包含一条消息,指出“锁表等待太深或长时间搜索-对于图形,我们将回滚后续事务”,这表明等待等待的事务数list已达到限制200。超过200个事务的等待列表将被视为死锁,并且尝试检查等待列表的事务将回退。如果锁定线程必须查看等待列表上的事务拥有的1,000,000个以上的锁,也可能发生相同的错误。
有关组织数据库操作以避免死锁的技术,请参见第14.7.5节“ InnoDB中的死锁”。

链接: https://dev.mysql.com/doc/refman/5.6/en/innodb-deadlock-detection.html.

<think> 嗯,用户这次问的是MySQL死锁错误的解决方案。从引用内容看,用户已经收集了两条相关参考资料:一个是具体的报错信息,另一个提到了死锁检测的伪代码方案。用户明确表示“上述引用仅供参考”,说明ta需要更全面的解决方案而非简单重复引用内容。 用户可能是个中高级开发者,因为ta能精准定位到数据库死锁这种相对深入的问题。考虑到死锁问题通常出现在高并发场景,用户可能正在处理生产环境的紧急故障,语气里透着焦虑——毕竟这是会阻塞业务流程的严重错误。 关于解决思路,需要分层展开:首先要理解死锁本质(多事务循环等待资源),然后提供三种层级的方案:应急处理、规避策略、深度优化。特别要注意引用中的伪代码给出了“重试机制”的线索,这在实际开发中确实是最常用方案。 技术细节上需要强调几个关键点:innodb_lock_wait_timeout的合理设置、如何降低事务粒度、索引优化的具体方法。还要提醒用户,某些方案如死锁检测虽然有效但可能带来性能损耗。 最后要补充排查工具,因为用户真正需要的是自主解决问题的能力。show engine innodb status的输出解析必须包含,这是诊断死锁的黄金标准。额外注意事项里的事务重试策略特别重要——很多框架其实内置了这类机制,用户可能不知道。 相关问题的设计要覆盖后续可能性:用户解决当前死锁后,可能想预防复发(索引优化)、处理复杂场景(嵌套事务)、或者需要监控方案(性能分析)。这三个衍生方向都很有实战价值。 </think> 以下是解决 MySQL "Deadlock found when trying to get lock" 错误的系统性方案,结合了事务优化、锁机制调整和代码逻辑改进: --- ### **一、核心解决方案** #### 1. **事务重试机制**(最常用) ```java int retryCount = 0; while (retryCount < MAX_RETRY) { try { // 执行事务操作 executeTransaction(); break; // 成功则退出 } catch (DeadlockException e) { retryCount++; Thread.sleep(50 * retryCount); // 指数退避等待 } } ``` > **原理**:死锁通常瞬时发生,重试可使事务重新获取资源锁[^1] #### 2. **降低事务粒度** - **拆分大事务**:将 `UPDATE 10,000 rows` 拆分为多个小事务(如每100行一次提交) - **调整执行顺序**:确保所有事务按**相同顺序**访问资源(如先更新表A再更新表B) - **避免热点更新**:高并发时减少对同一行的频繁更新(如计数器改用 `SELECT ... FOR UPDATE`) #### 3. **锁优化策略** | 策略 | 操作方法 | |---------------------|--------------------------------------------------------------------------| | **缩短锁持有时间** | 将非DB操作(业务逻辑)移出事务范围 | | **降低隔离级别** | `SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;` | | **索引优化** | 确保WHERE条件列均有索引,避免全表扫描(`EXPLAIN` 分析查询) | | **使用行锁提示** | `SELECT ... FOR UPDATE NOWAIT` / `SELECT ... LOCK IN SHARE MODE SKIP LOCKED` | --- ### **二、高级调优方案** #### 1. **死锁检测与诊断** ```sql SHOW ENGINE INNODB STATUS; -- 查看最新死锁记录 ``` 关注输出中的 `LATEST DETECTED DEADLOCK` 部分,明确: - 冲突的SQL语句 - 被阻塞的资源(行/索引) - 事务等待链 #### 2. **参数调优** ```ini # my.cnf 配置 innodb_lock_wait_timeout=30 # 缩短锁超时(默认50秒) innodb_deadlock_detect=ON # 确保死锁检测开启(默认ON) innodb_print_all_deadlocks=ON # 记录所有死锁到错误日志 ``` #### 3. **应用层设计** - **乐观锁机制**:使用版本号控制更新 ```sql UPDATE table SET col=new_val, version=version+1 WHERE id=123 AND version=current_version; ``` - **队列处理**:对同一资源的操作串行化(如Redis队列) --- ### **三、临时应急措施** ```sql -- 终止阻塞进程(需SUPER权限) SHOW PROCESSLIST; KILL [blocking_thread_id]; ``` --- ### **排查流程图** ```mermaid graph TD A[出现死锁错误] --> B{分析 SHOW ENGINE INNODB STATUS} B --> C[定位冲突SQL及资源] C --> D[优化SQL/索引] D --> E[缩短事务粒度] E --> F[引入重试机制] F --> G[测试验证] G --> H{是否解决?} H -->|否| I[考虑队列/乐观锁] ``` > **关键预防点**:事务中避免**交叉申请锁**(如事务1锁A后锁B,事务2锁B后锁A)[^2] --- **相关问题** 1. 如何通过 `SHOW ENGINE INNODB STATUS` 解析MySQL死锁日志? 2. 在高并发场景下,如何设计数据库操作避免死锁? 3. MySQL的锁升级机制在什么情况下会触发?如何规避? [^1]: 重试机制是应对瞬时死锁的有效方案,需配合退避算法 [^2]: 规范资源访问顺序可破坏死锁的循环等待条件
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值