伪代码:
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 拥有自己的事务,并且进行手动回滚。(因为我想在哪怕报错的时候 也要进行一段日志记录的数据库插入操作)
不知道有没有解决办法。或者其他思路?
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);
//调用B
transactionB();
}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"));
}
}
}
根据各位老哥回复的启发
更改了业务代码,把transactionB的操作,根据返回状态,拿到了transactionA中执行了。就是代码难看了点,功能实现了。就不知道有没有更好的办法
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);
//调用B
transactionB();
}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 boolean transactionB() {
Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
// 省略可能报错的代码
return true;
}catch(Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);
return false;
}finally {
LogMapper.insert(new Log("time","method","request","response"));
}
}
}
1
cnhongwei 2023-11-29 09:31:52 +08:00
那你不要在 B 中开新事务,而是记录 Log 的类中开新事务。
|
2
missya 2023-11-29 09:40:39 +08:00
A 和 B 方法是怎么调用的,A 中调用 B ?还是同时分别调用?
|
3
SvenWong 2023-11-29 09:43:08 +08:00
看错误就是锁等待超时了,但是具体解决还是要看场景,比如你的 B 事务中对 TableA 的更新是否在单独线程,以及先后顺序,A 事务 insert TableA 之后,是否还有大量的代码操作,导致 A 事务迟迟无法提交等等
|
4
linuxsteam OP @missya A 调用 B ,对不起 没贴上,我附言了
|
5
linuxsteam OP |
6
dengkj 2023-11-29 10:08:08 +08:00
尽量缩短事务 A 的执行时间,相关性不强的业务可以异步执行。
|
7
linuxsteam OP @cnhongwei 插入日志也不会报错。
在抽出来插入日志的代码上新开事务 我理解没有实际作用呀 |
8
bcllemon 2023-11-29 10:22:06 +08:00
A 和 B 是要一起完成, 就放到一个事物里。
A 完成后,执行 B ,就先提交 A 事物,再执行 B 。 |
9
SvenWong 2023-11-29 10:23:00 +08:00
@linuxsteam #5 哦,我漏看了一句代码,我的理解是:你的 A 方法里同步调用了 B 方法,但是 B 方法起了一个新事务,它执行 update 的时候,需要等待 insert 的那个锁,但是 insert 的锁现在还在 A 事务里没提交,但是 A 要等待 B 方法执行结束返回了,事务才能提交。
尝试把 B 方法放到异步操作去,或者把调用 B 方法,放到调用 A 的上层去 |
10
missya 2023-11-29 10:24:04 +08:00
@linuxsteam 是不是执行 transactionA()后事务还未提交,其实当时 Id=1 的数据并没有实际保存到数据库中,然后紧接着 transactionB()又执行,而又是独立的事务,所以获取不到 Id=1 的数据(其实是事务 A 还在 lock 中)造成无法更新超时,可是尝试下异步调用 transactionB()看看结果如何
|
11
kivmi 2023-11-29 10:38:33 +08:00
事务 A 改成手动提交,应该可以吧
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { // 提交事务 transactionManager.commit(status); } catch (Exception ex) { // 发生异常时回滚事务 transactionManager.rollback(status); } |
12
weiqlog 2023-11-29 10:40:27 +08:00
设置数据库隔离级别为读未提交 Read Uncommitted 呢?
|
13
vczyh 2023-11-29 10:50:14 +08:00
我觉的这是业务问题,既然 A 和 B 事务是分开的,那就可以说明他们不是强一致的,那就采用最终一致,即 A 提交后,再执行 B , 不然就把 AB 放到同一个事务,事务和业务是有关联的。
|
14
linuxsteam OP @dengkj A 不慢 B 慢(调用第三方接口)
|
15
linuxsteam OP @bcllemon 需要一起完成,并且同步的。
现在也改成一个事务了,但是就不能在 B 抛出异常交给 A 处理了(这样 B 的数据库操作就全部回滚了) B 只能改成 手动回滚到某个点,然后 return 。感觉这么写不太好 |
16
linuxsteam OP |
17
linuxsteam OP @weiqlog 可行的,不过不敢调。怕影响太大了,对事物理解不是特别透彻。 来点其他问题 就得不偿失了
|
18
linuxsteam OP @bcllemon 说错了,我是 B 去掉事务了。A 和 B 加入一个事务也会 锁超时
|
19
linuxsteam OP @missya 就是这样的。
之前就是异步调用 transactionB 。现在业务改了,想同步返回结果。所以才出现这个问题的 |
20
linuxsteam OP @SvenWong B 方法没法放到上面,因为上面新建的数据,然后 B 才能去更新 😂
|
21
SvenWong 2023-11-29 11:39:09 +08:00
@linuxsteam #16 那既然要同步,就没必要 Propagation.REQUIRES_NEW 了吧,在一个事务里面做就好了,一起成功一起失败
|
22
kivmi 2023-11-29 11:43:21 +08:00
@linuxsteam 既然是需要第三方的接口,为啥不先拿到数据,然后执行插入更新事务呢?
|
23
vishun 2023-11-29 11:49:20 +08:00
@linuxsteam #18 同一个事务内也会超时?不可能吧。
|
24
zhuzhibin 2023-11-29 11:54:36 +08:00 via iPhone
没人问隔离级别么? RR 还是 RC ?以及不太明白业务上为啥插入 id =1 的,立马又事物更新这行记录的其他字段,所以是期望同个事务内,或者能否描述下业务诉求
|
25
kivmi 2023-11-29 11:58:24 +08:00
@zhuzhibin 感觉它这个为啥使用事务,推测是它是先插入,然后推送数据到第三方,之后第三方修改状态,然后回传字段,感觉就没必要使用事务
|
26
linuxsteam OP @zhuzhibin 当然是 Mysql 默认隔离级别,允许重复度。
业务是新插入这条数据。 然后再拿这条数据去请求第三方系统。(最早时候是异步请求第三方系统的,但是后来要求同步了) 然后请求第三方系统是单独一个代码 transtranB 。里面只有更新对应数据状态,记录日志的数据库操作。 |
27
linuxsteam OP @kivmi 因为要通过事务保证异常数据不要落库,否则脏数据比较难受。而且业务所处部分是 数据处理。一切日志都要记录
|
28
linuxsteam OP @vishun 啊,没有超时,我上午回答错了。是 B 的操作都被回滚了(日志表就没有记录)。我以为超时报错了。
|
29
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 } } ); ``` |
30
linuxsteam OP @vishun 谢谢,我去搜素下 这个是什么意思
|
31
lancelee01 2023-11-29 16:46:05 +08:00
看了一下上下文,大概意思是先落单,然后请求 RPC 接口,更新状态。这个在实际业务开发中,是不加事务的,都是补偿(定时任务搂落单的数据,重复一遍后面的流程)。规范一般要求事务的粒度必须是最细的,不能包含业务逻辑,尤其是 RPC 调用可能会超时,尤其金融相关,事务中基本只能有 2-3 行,数据组装好直接操作事务。
|
32
linuxsteam OP |
33
dyv9 283 天前 via Android
@linuxsteam 日志要用 RequiredsNew 事务设置 呀。
|