记一次数据库死锁问题发现与解决过程

本文记录了一次在saas商城项目中遇到的数据库死锁问题,涉及mybatis与java。死锁导致订单状态更新失败,表现为用户支付成功但订单显示未支付。通过分析MySQL的Deadlock日志,定位到事务间的资源争用,提出了解决方案,包括添加索引以实现行锁,以及优化事务处理和防止死锁的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

​在公司做saas商城中,用户下单中,遇跟新表状态导致数据库死锁产生,订单状态更新失败,结果导致用户支付成功,订单展示未支付,实际是用户支付成功,而订单状态未能跟新未支付成功状态.

graph TD
A[用户支付] --> B[微信支付]
B --> C[异步回调]
C --> D{通知支付是否成功}
D --> E[修改订单状态]

查询出现问题的原因,查找生产日志,

org.springframework.dao.DeadlockLoserDataAccessException:

Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

The error may exist in com/duobaoyu/dby/mall/databean/dal/mysql/order/OrderMapper.java (best guess)

The error may involve com.duobaoyu.dby.mall.databean.dal.mysql.order.OrderMapper.update-Inline

The error occurred while setting parameters

SQL: UPDATE dby_order SET status=?, payed=?, pay_time=?, transaction_id=?, update_time=?, updater=? WHERE deleted=0 AND (order_no = ?)

Cause: com.mysql.cj.jdbc.exceptions.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.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:271)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:91)
at org.mybatis.spring.SqlSessionTemplateSqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)atcom.sun.proxy.SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441) at com.sun.proxy.SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)atcom.sun.proxy.Proxy145.update(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.update(SqlSessionTemplate.java:288)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:64)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxyPlainMethodInvoker.invoke(MybatisMapperProxy.java:148)atcom.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)atcom.sun.proxy.PlainMethodInvoker.invoke(MybatisMapperProxy.java:148) at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89) at com.sun.proxy.PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)atcom.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)atcom.sun.proxy.Proxy171.update(Unknown Source)
at com.duobaoyu.dby.module.mall.service.order.impl.OrderServiceImpl.updateOrderByOrderNo(OrderServiceImpl.java:122)
at com.duobaoyu.dby.module.mall.service.order.impl.OrderServiceImplFastClassBySpringCGLIBFastClassBySpringCGLIBFastClassBySpringCGLIB76329b9e.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxyCglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)atorg.springframework.aop.framework.CglibAopProxyCglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxyCglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)atorg.springframework.aop.framework.CglibAopProxyCglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:123)
整理除简易关键信息

Deadlock found when trying to get lock; try restarting transaction

试图获取锁时发现死锁;尝试重新启动事务

尝试从数据库层面出发,找到数据库当时日志产生原因.

可以使用SHOW ENGINE INNODB STATUS命令来查看死锁信息,分析死锁产生的原因。

分析死锁日志,找到具体涉及到的表和事务,确定死锁产生的关键原因


LATEST DETECTED DEADLOCK

2023-04-18 12:06:03 139990029825792
*** (1) TRANSACTION:
TRANSACTION 156944871, ACTIVE 3 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 16 row lock(s), undo log entries 2
MySQL thread id 7739481, OS thread handle 139988366042880, query id 108771341 113.125.2.227 root updating
UPDATE dby_order SET status=3,
payed=1,
pay_time=‘2023-04-18 12:06:02.479’,
transaction_id=‘4200001842202304185871292083’,
update_time=‘2023-04-18 12:06:02.479’,
WHERE deleted=0
AND (order_no = 1648175966147006464)

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 3151 page no 4 n bits 96 index PRIMARY of table dby_mall_demo_1.0.0.dby_order trx id 156944871 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 37; compact format; info bits 0
0: len 8; hex 8000000000000009; asc ;;
1: len 6; hex 0000095aa550; asc Z P;;
2: len 7; hex 020000020c1859; asc Y;;
3: len 19; hex 31363438313732383739383237313238333230; asc 1648172879827128320;;
4: len 22; hex 31343131363438313732383831383239343337343431; asc 1411648172881829437441;;
5: len 0; hex ; asc ;;
6: len 8; hex 8000000000000002; asc ;;
7: SQL NULL;
8: len 6; hex 636f75706f6e; asc coupon;;
9: len 4; hex 80000002; asc ;;
10: len 8; hex 8000000000000001; asc ;;

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3151 page no 4 n bits 96 index PRIMARY of table dby_mall_demo_1.0.0.dby_order trx id 156944871 lock_mode X waiting
Record lock, heap no 24 PHYSICAL RECORD: n_fields 37; compact format; info bits 0
0: len 8; hex 8000000000000020; asc ;;
1: len 6; hex 0000095ac995; asc Z ;;
2: len 7; hex 81000008800121; asc !;;
3: len 19; hex 31363438313736303236353738353338343936; asc 1648176026578538496;;
4: SQL NULL;
5: len 0; hex ; asc ;;
6: len 8; hex 8000000000000009; asc ;;
7: SQL NULL;
8: len 6; hex 636f75706f6e; asc coupon;;
9: len 4; hex 80000001; asc ;;
10: len 8; hex 8000000000000001; asc ;;
11: len 8; hex 8000000000000000; asc ;;
12: len 1; hex 00; asc ;;
13: SQL NULL;
这个是事物一获取死锁失败,下面是事物二获取死锁失败,由于都是同一张表获取资源信息,导致死锁产生

*** (2) TRANSACTION:
TRANSACTION 156944789, ACTIVE 1 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 12 lock struct(s), heap size 1128, 6 row lock(s), undo log entries 6
MySQL thread id 7730543, OS thread handle 139988096612096, query id 108771441 113.125.2.227 root updating
UPDATE dby_order SET channel_order_no=‘1411648176031256813570’,
status=2,
update_time=‘2023-04-18 12:06:02.638’,
updater=‘9’
WHERE deleted=0
AND (order_no = 1648176026578538496)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 3151 page no 4 n bits 96 index PRIMARY of table dby_mall_demo_1.0.0.dby_order trx id 156944789 lock_mode X locks rec but not gap
Record lock, heap no 24 PHYSICAL RECORD: n_fields 37; compact format; info bits 0
0: len 8; hex 8000000000000020; asc ;;
1: len 6; hex 0000095ac995; asc Z ;;
2: len 7; hex 81000008800121; asc !;;
3: len 19; hex 31363438313736303236353738353338343936; asc 1648176026578538496;;
4: SQL NULL;
5: len 0; hex ; asc ;;
6: len 8; hex 8000000000000009; asc ;;
7: SQL NULL;
8: len 6; hex 636f75706f6e; asc coupon;;
9: len 4; hex 80000001; asc ;;
10: len 8; hex 8000000000000001; asc ;;
11: len 8; hex 8000000000000000; asc ;;
12: len 1; hex 00; asc ;;
13: SQL NULL;
14: len 4; hex 80000001; asc ;;
15: SQL NULL;
16: SQL NULL;
17: SQL NULL;
18: len 1; hex 81; asc ;;
19: SQL NULL;
20: len 1; hex 39; asc 9;;
21: len 5; hex 99afe4c181; asc ;;
22: len 1; hex 39; asc 9;;
23: len 5; hex 99afe4c181; asc ;;
24: len 1; hex 00; asc ;;
25: SQL NULL;
26: SQL NULL;
27: SQL NULL;
28: SQL NULL;
29: SQL NULL;
30: SQL NULL;
31: SQL NULL;
32: SQL NULL;
33: SQL NULL;
34: SQL NULL;
35: SQL NULL;
36: len 2; hex 7b7d; asc {};;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3151 page no 4 n bits 96 index PRIMARY of table dby_mall_demo_1.0.0.dby_order trx id 156944789 lock_mode X waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 37; compact format; info bits 0
0: len 8; hex 8000000000000009; asc ;;
1: len 6; hex 0000095aa550; asc Z P;;
2: len 7; hex 020000020c1859; asc Y;;
3: len 19; hex 31363438313732383739383237313238333230; asc 1648172879827128320;;
4: len 22; hex 31343131363438313732383831383239343337343431; asc 1411648172881829437441;;
5: len 0; hex ; asc ;;
6: len 8; hex 8000000000000002; asc ;;
7: SQL NULL;
8: len 6; hex 636f75706f6e; asc coupon;;
9: len 4; hex 80000002; asc ;;
10: len 8; hex 8000000000000001; asc ;;
11: len 8; hex 8000000000000000; asc ;;
12: len 1; hex 00; asc ;;
13: SQL NULL;
14: len 4; hex 80000001; asc ;;
15: SQL NULL;
16: SQL NULL;
17: SQL NULL;
18: len 1; hex 81; asc ;;
19: SQL NULL;
20: len 1; hex 32; asc 2;;
21: len 5; hex 99afe4bd5f; asc _;;
22: len 1; hex 32; asc 2;;
23: len 5; hex 99afe4bd5f; asc _;;
24: len 1; hex 00; asc ;;
25: SQL NULL;
26: SQL NULL;
27: SQL NULL;
28: SQL NULL;
29: SQL NULL;
30: SQL NULL;
31: SQL NULL;
32: SQL NULL;
33: SQL NULL;
34: SQL NULL;
35: SQL NULL;
36: len 2; hex 7b7d; asc {};;

*** WE ROLL BACK TRANSACTION (1)

TRANSACTIONS
解决方案,由于同一时间更新一个表,跟新的字段没有使用到索引的时候,会把默认锁住当前表,在更新的字段上加上索引,只会锁住当前行,也就是实现行锁.

预防措施

1 定期检查和优化数据库性能,例如索引优化,查询语句优化等。

2 避免长时间的事务操作,尽量将事务操作时间控制在较短的范围内。

3 对于高并发场景,考虑使用分布式锁等技术来避免死锁的发生。

4 对于异步回调等场景,确保回调接口的高可用性和稳定性,避免回调接口出现异常导致死锁的发生。

总结

在生产环境中,死锁是一个常见的问题,解决死锁需要综合考虑数据库和应用程序两个方面。在预防死锁的发生时,需要对数据库性能进行优化,

以及在应用程序中添加死锁检测和处理机制。同时,对于高并发场景,需要使用分布式锁等技术来避免死锁的发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值