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

本文涉及的产品
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Mysql 死锁引发的@Transactional 数据回滚

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数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
8天前
|
SQL 关系型数据库 MySQL
MySQL 事务回滚。在执行删除、更新等操作时,防止误操作
MySQL 事务回滚。在执行删除、更新等操作时,防止误操作
20 2
|
2天前
|
关系型数据库 MySQL 数据库
MySQL 复制A的表结构和数据到表B
在MySQL中复制表A至表B可通过不同方法实现。一种是先用`CREATE TABLE B LIKE A;`复制结构,再用`INSERT INTO B SELECT * FROM A;`填充数据。另一种更简便的方法是直接使用`CREATE TABLE B AS SELECT * FROM A;`一次性完成结构和数据的复制。还有一种高级方法是通过`SHOW CREATE TABLE A;`获取表A的创建语句,手动调整后创建表B,如有需要再用`INSERT INTO ... SELECT`复制数据。注意权限问题、跨数据库复制时需指定数据库名,以及大表复制时可能影响性能。
|
3天前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
3天前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
5天前
|
消息中间件 数据采集 关系型数据库
大数据-业务数据采集-FlinkCDC 读取 MySQL 数据存入 Kafka
大数据-业务数据采集-FlinkCDC 读取 MySQL 数据存入 Kafka
22 1
|
5天前
|
数据采集 关系型数据库 MySQL
大数据-业务数据采集-FlinkCDC The MySQL server is not configured to use a ROW binlog_format
大数据-业务数据采集-FlinkCDC The MySQL server is not configured to use a ROW binlog_format
15 1
|
7天前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。
|
8天前
|
固态存储 关系型数据库 MySQL
"惊!20亿数据秒速入MySQL,揭秘数据库极速插入的黑科技,你不可不知的绝密技巧!"
【8月更文挑战第11天】面对20亿级数据量,高效插入MySQL成为挑战。本文探讨优化策略:合理设计数据库减少不必要的字段和索引;使用批量插入减少网络往返;优化硬件如SSD和内存及调整MySQL配置;并行处理加速插入;附Python示例代码实现分批导入。这些方法将有效提升大规模数据处理能力。
24 2
|
12天前
|
SQL 关系型数据库 MySQL
“震撼揭秘!Flink CDC如何轻松实现SQL Server到MySQL的实时数据同步?一招在手,数据无忧!”
【8月更文挑战第7天】随着大数据技术的发展,实时数据同步变得至关重要。Apache Flink作为高性能流处理框架,在实时数据处理领域扮演着核心角色。Flink CDC(Change Data Capture)组件的加入,使得数据同步更为高效。本文介绍如何使用Flink CDC实现从SQL Server到MySQL的实时数据同步,并提供示例代码。首先确保SQL Server启用了CDC功能,接着在Flink环境中引入相关连接器。通过定义源表与目标表,并执行简单的`INSERT INTO SELECT`语句,即可完成数据同步。
35 1
|
1天前
|
canal 关系型数据库 MySQL
"揭秘阿里数据同步黑科技Canal:从原理到实战,手把手教你玩转MySQL数据秒级同步,让你的数据处理能力瞬间飙升,成为技术界的新晋网红!"
【8月更文挑战第18天】Canal是一款由阿里巴巴开源的高性能数据同步系统,它通过解析MySQL的增量日志(Binlog),提供低延迟、可靠的数据订阅和消费功能。Canal模拟MySQL Slave与Master间的交互协议来接收并解析Binary Log,支持数据的增量同步。配置简单直观,包括Server和Instance两层配置。在实战中,Canal可用于数据库镜像、实时备份等多种场景,通过集成Canal Client可实现数据的消费和处理,如更新缓存或写入消息队列。
8 0