Mysql 死锁引发的@Transactional 数据回滚

探讨Spring框架中使用@Transactional注解时遇到的死锁问题,分析高并发环境下事务回滚的原因及解决方案。

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

Spring框架中我们经常使用 @Transactional 注解来做事务,但是事务并不能保证有效性;

以下是我遇到的问题,不一定完全正确,可以做个参考:

在一个类上标记了 @Transactional,使得该类下的所有方法都以默认的事务方式运行。

@Transactional
public class test(){
    // 往A表中插入数据
    public void A(){

    }

    // 往B表中插入数据
    public void B(){

    }
}

在一个方法中分别调用这个方法:分别对这个方法进行try catch异常,防止因为异常回滚所有数据

@Service
public class TestAnother{

    @Autowired
    private Test test;
    
    public void C(){
        try{
            test.A();
        }catch(Exception e){
            e.printStackTrace();
        }
        try{
            test.B();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

在正常情况下,这个方法是没有问题的,但是在线上的时候,由于请求量较大,也就是我们常说的高并发环境:

在B方法中,假如我们有一句SQL:delete from users where status = ‘test’;

在users表中给status加了一个索引。

问题来了:

在一般情况下,由于是串行逻辑,所以不会有影响。

但是在高并发情况下,由于我们需要delete语句,需要行级锁,因为status是一个非聚集索引,所以需要给范围性的数据上行级锁,也就是利用了 next-key lock。(InnoDB实现的RR通过next-key lock机制避免了幻读现象。这部分我也不是特别确定),而在并发环境下,由于上一个方法的锁未释放,下一个方法又进来了。

比如: 第一个线程进来的时候需要删除0-10的数据,这时候加锁加到了第5个,而第二个线程这个时候也进来了,比如随机加了其他的锁,这时候也需要拿5的锁,但是没有拿到,需要等待线程1释放锁,而第一个线程可能刚好需要第二个线程的随机锁,导致两个线程互相等待拿锁,从而导致死锁

 

话说回来,如果 @Transactional 遇到死锁会怎么样呢?

我在本地模拟了死锁的条件,本地SQL执行了一个start Transactional,但是一直不提交。

用POSTMAN在线上发了一个请求,线上的请求中虽然A方法执行完成了,但是卡在了B方法迟迟拿不到锁,最后导致了获取锁超时。下面是通过数据库查看的最近一次死锁的信息:


=====================================
2019-09-07 06:28:38 7fe01c931700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 24 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 8912 srv_active, 0 srv_shutdown, 516445 srv_idle
srv_master_thread log flush and writes: 524528
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 24855
OS WAIT ARRAY INFO: signal count 25085
Mutex spin waits 14574, rounds 408115, OS waits 13345
RW-shared spins 10346, rounds 338033, OS waits 11257
RW-excl spins 216, rounds 7866, OS waits 240
Spin rounds per wait: 28.00 mutex, 32.67 RW-shared, 36.42 RW-excl
------------
TRANSACTIONS
------------
Trx id counter 690061
Purge done for trx's n:o < 690050 undo n:o < 0 state: running but idle
History list length 1343
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0, not started
MySQL thread id 18225, OS thread handle 0x7fe01c931700, query id 686481 172.17.0.1 root init
show engine innodb status
---TRANSACTION 690050, not started
MySQL thread id 18223, OS thread handle 0x7fdf6331b700, query id 686305 172.17.0.1 root
---TRANSACTION 690060, not started
MySQL thread id 18203, OS thread handle 0x7fe01cabd700, query id 686456 172.17.0.1 root
---TRANSACTION 690058, ACTIVE 32 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 2
MySQL thread id 18202, OS thread handle 0x7fe01c1b7700, query id 686341 172.17.0.1 root update
INSERT INTO spot_account_flows (flowType, refType, refId, fromUserId, fromAccountId, toUserId, toAccountId, currency, amount, description, createdAt) VALUES ('TRADE_CLEAR', 'CLEARING', 0, 100000, 102950, 100000, 108015, 'BTC', 1, '', 1567837686558)
------- TRX HAS BEEN WAITING 32 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 643 page no 97 n bits 144 index `PRIMARY` of table `ex`.`spot_account_flows` trx id 690058 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

------------------
---TRANSACTION 690056, ACTIVE 36 sec
67 lock struct(s), heap size 13864, 8195 row lock(s), undo log entries 23
MySQL thread id 18224, OS thread handle 0x7fdf63bdf700, query id 686331 172.17.0.1 root
--------
FILE I/O
--------
I/O thread 0 state: waiting for completed aio requests (insert buffer thread)
I/O thread 1 state: waiting for completed aio requests (log thread)
I/O thread 2 state: waiting for completed aio requests (read thread)
I/O thread 3 state: waiting for completed aio requests (read thread)
I/O thread 4 state: waiting for completed aio requests (read thread)
I/O thread 5 state: waiting for completed aio requests (read thread)
I/O thread 6 state: waiting for completed aio requests (write thread)
I/O thread 7 state: waiting for completed aio requests (write thread)
I/O thread 8 state: waiting for completed aio requests (write thread)
I/O thread 9 state: waiting for completed aio requests (write thread)
Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
4606 OS file reads, 96239 OS file writes, 65171 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 22 merges
merged operations:
 insert 29, delete mark 421, delete 364
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 276671, node heap has 26 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 668395471
Log flushed up to   668395471
Pages flushed up to 668395471
Last checkpoint at  668395471
0 pending log writes, 0 pending chkp writes
33363 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 137363456; in additional pool allocated 0
Dictionary memory allocated 959373
Buffer pool size   8191
Free buffers       1028
Database pages     7137
Old database pages 2614
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 3270, not young 25362
0.00 youngs/s, 0.00 non-youngs/s
Pages read 3915, created 13555, written 48527
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 7137, unzip_LRU len: 0
I/O sum[16]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Main thread process no. 1, id 140600574822144, state: sleeping
Number of rows inserted 385622, updated 20256, deleted 79, read 13788081
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.50 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

而此时查数据库发现,A方法执行的事务也被回滚了。

原因就是:因为当前线程被数据库死锁卡在了获取锁的情况下,当前请求不能完全结束,导致 A 方法的事务不能提交,最后抛出的异常虽然是B方法的,但是A方法由于整个方法未能正确结束,所以事务未能正确提交,而MYSQL事务的默认超时时间是50s。

可以通过此命令 show variables like 'innodb_lock_wait_timeout';

也就是说如果50s未能commit事务,那么当前事务将被自动回滚。

这也就导致了为什么A方法并没有报异常。

说到底导致了A方法没有异常却回滚了是因为服务超时了。

解决方案:

1.数据库事务默认为自动提交,我们可以手动设置为手动提交。

2.方法拆分,使其不在一个线程内即可,这样A方法就不会因为B方法超时而回滚。

3.update或者insert或者delete语句使用主键索引,这样可以避免 next-key lock 使其产生范围锁。这样就不会产生排他锁而导致线程之间死锁。

 

因为对MYSQL的了解并没有那么深入,错误欢迎指出。

<think>嗯,用户的问题是关于在使用MySQL@Transactional注解时,最后一条SQL执行时间过长的原因和解决方案。首先,我需要回想一下事务处理的相关知识,以及@Transactional注解的工作原理。 用户提到最后一条SQL执行慢,这可能涉及到事务的机制。事务中的SQL语句通常是在提交时一起执行的,但具体执行顺序和时机可能因数据库和配置不同而有所变化。比如,某些情况下,前面的SQL可能已经执行,但最后一条可能因为竞争、数据量增大或者索引问题导致变慢。 接下来要考虑事务超时设置。@Transactional注解可以设置超时时间,如果最后一条SQL执行时间超过这个限制,就会回滚并抛出异常。用户可能需要检查是否设置了合理的超时时间,或者是否存在超时导致的问题。 然后想到竞争的问题。事务中的操作可能会持有,尤其是最后一条SQL可能等待其他事务释放资源,导致延迟。例如,更新操作的行或表,如果其他事务长时间占用,就会阻塞当前事务的执行。 网络或资源瓶颈也是一个可能的原因。最后一条SQL执行时,可能遇到网络延迟、数据库连接池不足或服务器负载过高,从而影响执行速度。需要检查系统资源使用情况,确认是否存在瓶颈。 数据量增大也可能导致执行变慢。如果事务中的操作累积了大量数据,最后一条SQL可能需要处理更多的数据,比如批量插入或更新,这时如果没有优化,执行时间就会增。 索引缺失或统计信息不准确也可能影响SQL性能。需要分析执行计划,确认是否缺少必要的索引,或者统计信息是否需要更新,导致查询优化器选择了低效的执行路径。 解决方案方面,首先应该优化SQL语句本身,检查是否有复杂的查询可以简化,或者是否可以利用索引。然后,调整事务的超时时间,确保有足够的时间完成操作,避免不必要的回滚。拆分大事务也是一个有效的方法,将长时间运行的事务分成多个小事务,减少的持有时间和资源占用。 使用批量操作和异步处理可能帮助提高效率,尤其是处理大量数据时。监控数据情况,及时处理死锁或长时间持有的,也能缓解执行慢的问题。检查数据库连接池配置,确保资源充足,避免连接不足导致的延迟。此外,更新统计信息和重建索引可以保持查询计划的准确性,提升执行效率。 最后,用户可能需要具体的步骤来实施这些解决方案,比如如何分析执行计划,如何设置超时参数,或者如何监控状态。需要将这些步骤详细列出,帮助用户一步步排查和解决问题。</think>### MySQL @Transactional 注解下最后一条 SQL 执行慢的原因及解决方案 #### 一、可能原因分析 1. **事务提交机制** 事务中的 SQL 语句在提交前会统一执行,但某些情况下(如隐式提交设置或数据库驱动实现差异),最后一条 SQL 可能需要等待事务提交时执行。如果该 SQL 涉及大量数据操作或复杂计算,会导致延迟[^1][^2]。 2. **竞争** 若最后一条 SQL 涉及行、表或间隙(如 `UPDATE`、`SELECT ... FOR UPDATE`),可能因其他事务占用资源而阻塞。例如: ```sql UPDATE users SET balance = balance - 100 WHERE id = 1; -- 持有行 ``` 3. **事务超时设置** `@Transactional(timeout = N)` 限制了整个事务的执行时间。如果最后一条 SQL 执行时剩余时间不足,会触发超时回滚[^2]。 4. **数据量或索引问题** 最后一条 SQL 可能涉及全表扫描或未命中索引,例如: ```sql SELECT * FROM orders WHERE create_time > '2023-01-01'; -- 无索引时效率低 ``` 5. **网络或资源瓶颈** 事务末尾可能因数据库连接池耗尽、网络延迟或磁盘 I/O 过载导致延迟。 --- #### 二、解决方案 ##### 1. 优化 SQL 性能 - **添索引**:针对 WHERE 条件、JOIN 字段、ORDER BY 字段建立索引。 ```sql CREATE INDEX idx_create_time ON orders(create_time); ``` - **分析执行计划**:使用 `EXPLAIN` 检查 SQL 执行路径。 ```sql EXPLAIN SELECT * FROM orders WHERE user_id = 100; ``` ##### 2. 调整事务配置 - **设置合理超时**:根据 SQL 执行时间调整 `@Transactional(timeout)`,例如: ```java @Transactional(timeout = 30) // 单位:秒 ``` - **拆分大事务**:将长事务拆分为多个小事务,减少持有时间。 ##### 3. 减少竞争 - **缩小定范围**:使用精确条件(如主键)代替范围查询。 - **隔离级别调整**:在 `@Transactional` 中指定较低的隔离级别(需评估一致性风险): ```java @Transactional(isolation = Isolation.READ_COMMITTED) ``` ##### 4. 监控与资源优化 - **检查状态**:使用 `SHOW ENGINE INNODB STATUS` 分析等待情况。 - **优化连接池**:调整最大连接数(如 HikariCP 的 `maximumPoolSize`)。 ##### 5. 异步处理 - 将耗时操作移到事务外,通过消息队列异步执行: ```java @Transactional public void processOrder(Order order) { orderDao.save(order); // 事务内操作 messageQueue.sendAsync(order); // 异步处理 } ``` --- #### 三、验证步骤 1. **日志分析** 启用 MySQL 慢查询日志(配置 `long_query_time`)定位具体 SQL: ```sql SET GLOBAL slow_query_log = 'ON'; ``` 2. **事务监控** 使用 `SHOW PROCESSLIST` 查看当前事务状态: ```sql SHOW FULL PROCESSLIST; -- 观察 State 列是否为 "Waiting for table metadata lock" ``` 3. **性能测试** 通过 JMeter 或自定义脚本模拟高并发场景,观察事务末尾 SQL 的响应时间变化。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值