mysql 第一个事务 创建数据,第二个事务修改同一条数据 Lock wait timeout exceeded 怎么解决?

2023-11-29 09:14:04 +08:00
 linuxsteam

伪代码:

Class A {
        @Transactional(rollbackFor = Exception.class)
        public String transactionA() {
            Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
            try {

                // 省略可能报错的代码
                TableA a = new TableA();
                a.setId(1);

                tableAMapper.insert(a);
            }catch(Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);
            }finally {
                LogMapper.insert(new Log("time","method","request","response"));
            }
        }
    }


    Class B {
        @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
        public void transactionB() {
            Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
            try {
                // 省略可能报错的代码
                TableA a = new TableA();
                a.setId(1);
                a.status(2);

                tableAMapper.update(a);
            }catch(Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);
            }finally {
                LogMapper.insert(new Log("time","method","request","response"));
            }
        }
    }

TableA 表的 id 有主键约束,唯一索引。其他没有任何索引。

上面代码会产生 Lock wait timeout exceeded 的错误。 我通过检索资料,大概了解到问题原因,是事务 A 中tableAMapper.insert(a);触发了(行、排他锁),事务 B 里一直拿不到锁,导致超时。

但是我还想事务 B 拥有自己的事务,并且进行手动回滚。(因为我想在哪怕报错的时候 也要进行一段日志记录的数据库插入操作)

不知道有没有解决办法。或者其他思路?

2268 次点击
所在节点    数据库
33 条回复
SvenWong
2023-11-29 11:39:09 +08:00
@linuxsteam #16 那既然要同步,就没必要 Propagation.REQUIRES_NEW 了吧,在一个事务里面做就好了,一起成功一起失败
kivmi
2023-11-29 11:43:21 +08:00
@linuxsteam 既然是需要第三方的接口,为啥不先拿到数据,然后执行插入更新事务呢?
vishun
2023-11-29 11:49:20 +08:00
@linuxsteam #18 同一个事务内也会超时?不可能吧。
zhuzhibin
2023-11-29 11:54:36 +08:00
没人问隔离级别么? RR 还是 RC ?以及不太明白业务上为啥插入 id =1 的,立马又事物更新这行记录的其他字段,所以是期望同个事务内,或者能否描述下业务诉求
kivmi
2023-11-29 11:58:24 +08:00
@zhuzhibin 感觉它这个为啥使用事务,推测是它是先插入,然后推送数据到第三方,之后第三方修改状态,然后回传字段,感觉就没必要使用事务
linuxsteam
2023-11-29 14:23:25 +08:00
@zhuzhibin 当然是 Mysql 默认隔离级别,允许重复度。
业务是新插入这条数据。
然后再拿这条数据去请求第三方系统。(最早时候是异步请求第三方系统的,但是后来要求同步了)

然后请求第三方系统是单独一个代码 transtranB 。里面只有更新对应数据状态,记录日志的数据库操作。
linuxsteam
2023-11-29 14:24:26 +08:00
@kivmi 因为要通过事务保证异常数据不要落库,否则脏数据比较难受。而且业务所处部分是 数据处理。一切日志都要记录
linuxsteam
2023-11-29 14:35:28 +08:00
@vishun 啊,没有超时,我上午回答错了。是 B 的操作都被回滚了(日志表就没有记录)。我以为超时报错了。
vishun
2023-11-29 15:26:15 +08:00
别费这些劲了,要么就都放到同一个事务中,要么就 B 单独一个事务,当 B 单独一个事务时,必须保证 A 事务提交后再执行 B 相关代码,如果用 spring ,那么可以这样:
```
//A 相关代码
//A 结束后
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
//这里执行 B
}
}
);
```
linuxsteam
2023-11-29 16:02:21 +08:00
@vishun 谢谢,我去搜素下 这个是什么意思
lancelee01
2023-11-29 16:46:05 +08:00
看了一下上下文,大概意思是先落单,然后请求 RPC 接口,更新状态。这个在实际业务开发中,是不加事务的,都是补偿(定时任务搂落单的数据,重复一遍后面的流程)。规范一般要求事务的粒度必须是最细的,不能包含业务逻辑,尤其是 RPC 调用可能会超时,尤其金融相关,事务中基本只能有 2-3 行,数据组装好直接操作事务。
linuxsteam
2023-11-30 09:21:15 +08:00
@lancelee01 之前只有落单入库加事务,

后来调用方要求同步返回 RPC 接口的实时状态,就只能这样了。
补偿倒是没事,主要怕产生无效数据
dyv9
286 天前
@linuxsteam 日志要用 RequiredsNew 事务设置 呀。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/996113

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX