问题描述
mysql部署1主3从,昨天发现主库有大量报警错误:
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
定位根因
MySQLTransactionRollbackException是MySQL 处理并发事务时的典型错误。一般有死锁Deadlock和锁等待超时innodb_lock_wait_timeout两种;
当两个或多个事务相互等待对方持有的锁资源时,MySQL 会主动终止其中一个事务以打破僵局,从而抛出该异常,这一过程由 InnoDB 的死锁检测机制自动完成,无需人工干预。
分析死锁日志,定位冲突点:MySQL 会记录死锁详情到错误日志中,可通过以下命令查看最近一次死锁信息:
SHOW ENGINE INNODB STATUS;
在输出的 LATEST DETECTED DEADLOCK
部分,能看到:
- 参与死锁的事务 ID 和 SQL 语句。
- 事务持有和等待的锁类型(如
X-lock
排他锁)。 - 涉及的表和行记录(通过
space id
和page no
定位)。
LATEST DETECTED DEADLOCK
------------------------
2025-09-11 17:19:22 0x7ee2dbbdf700
*** (1) TRANSACTION:
TRANSACTION 922499728, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 24349657, OS thread handle 139512913655552, query id 4391620199 10.199.99.99 kms_mpmanage_st_pjab updating
/*id:3b7f6356*//*ip=10.199.99.99*/update srunningset report_time = 1757582355865,is_del = 0,graph_version = null,service_item = ‘aaaaa’,build_id = 9652,report_version = 2,config_version = 'config-202508112023',scene_sdk_version = '1.3.20',table_sdk_version = '2.4.31'where pid = 179529542 and report_time < 1757582355865
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 828 page no 8 n bits 200 index uniq_ip_app_scene_s of table `camp`.`srunning` trx id 922499728 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 922499723, ACTIVE 0 sec fetching rows
mysql tables in use 1, locked 1
27 lock struct(s), heap size 3520, 35 row lock(s), undo log entries 12
MySQL thread id 24348682, OS thread handle 139512814368512, query id 4391620202 10.199.99.99 kms_mpmanage_st_pjab updating
/*id:389d6f54*//*ip=10.199.99.99*/update srunningSET report_time = 1757582353855, is_del = 1 WHERE ( pid = 179529542 and report_time < 1757582353855 )
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 828 page no 8 n bits 200 index uniq_ip_app_scene_s of table `camp`.`srunning` trx id 922499723 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 828 page no 8 n bits 200 index uniq_ip_app_scene_s of table `camp`.`srunning` trx id 922499723 lock_mode X waiting
*** WE ROLL BACK TRANSACTION (1)
两个update更新同一表死锁是两个事务在该表中以不同顺序获取行锁,形成循环等待。
解决和避免
业务涉及事务的4个表都达到了500W+,且事务内同时查询插入或更新4个表,所以多次死锁
统一事务操作顺序
多个事务更新同一批行时,按相反或不同的顺序执行 UPDATE
,是单表死锁的最主要原因。
降低锁粒度或使用合理的索引
使用非主键索引、范围条件(>
, <
, BETWEEN
)等,导致 InnoDB 加锁范围扩大(如间隙锁、临键锁),增加交叉等待概率。
缩小事务范围减少锁持有时间
事务执行时间过长,长时间占用锁资源,提高了与其他事务形成循环等待的可能性。
数据表量级对性能有显著影响
- 小型表(10 万行以内)SELECT、INSERT、UPDATE基本无瓶颈,操作耗时主要受 SQL 语句写法影响
- 中型表(10 万~1000 万行):SELECT无索引的全表扫描开始变慢,有索引但索引设计不合理(如低基数索引)时,性能下降明显,复杂查询(多表关联、子查询)耗时增加,可能出现临时表或文件排序。INSERT:单条插入影响不大,但批量插入可能因索引维护(如 B+ 树分裂)耗时增加。若表有多个索引,写入性能下降更明显(每个索引都需更新)。UPDATE:基于非索引字段的更新需要全表扫描,耗时显著增加。高频更新可能导致行锁 / 表锁竞争,出现等待延迟。
- 大型表(1000 万~1 亿行):需要分库分表、数据归档等拆分
代码层面捕获异常并重试
死锁是临时性异常,重试通常可解决。在代码中捕获 MySQLTransactionRollbackException
,并设置合理的重试机制
锁类型
lock_type
:锁类型,常见值:RECORD
:行级锁(针对具体行记录)。TABLE
:表级锁(针对整个表)。
lock_mode
:锁模式,常见值:S
:共享锁(读锁,允许其他事务读,不允许写)。X
:排他锁(写锁,禁止其他事务读和写)。GAP
:间隙锁(锁定索引间隙,防止插入数据)。Next-Key
:临键锁(行锁 + 间隙锁的组合,默认的行锁模式)。
行级排他锁,UPDATE
/DELETE
/INSERT
时自动加锁,阻止其他事务修改该行。
行级共享锁,SELECT ... FOR SHARE
显式加锁,允许其他事务读,但阻止写。
排他间隙锁,锁定索引间隙,防止其他事务插入数据(如 UPDATE ... WHERE id > 10
可能触发)。
临键锁(默认行锁模式),锁定行及前面的间隙,防止幻读(REPEATABLE READ
隔离级别下默认)。