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

364 天前
 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 拥有自己的事务,并且进行手动回滚。(因为我想在哪怕报错的时候 也要进行一段日志记录的数据库插入操作)

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

2233 次点击
所在节点    数据库
33 条回复
cnhongwei
364 天前
那你不要在 B 中开新事务,而是记录 Log 的类中开新事务。
missya
364 天前
A 和 B 方法是怎么调用的,A 中调用 B ?还是同时分别调用?
SvenWong
364 天前
看错误就是锁等待超时了,但是具体解决还是要看场景,比如你的 B 事务中对 TableA 的更新是否在单独线程,以及先后顺序,A 事务 insert TableA 之后,是否还有大量的代码操作,导致 A 事务迟迟无法提交等等
linuxsteam
364 天前
@missya A 调用 B ,对不起 没贴上,我附言了
linuxsteam
364 天前
@SvenWong
A 事务 insert TableA 之后,是否还有大量的代码操作:
没有了 就是调用事务 B ,因为事务 B 获取不到锁 就挂了
dengkj
364 天前
尽量缩短事务 A 的执行时间,相关性不强的业务可以异步执行。
linuxsteam
364 天前
@cnhongwei 插入日志也不会报错。
在抽出来插入日志的代码上新开事务 我理解没有实际作用呀
bcllemon
364 天前
A 和 B 是要一起完成, 就放到一个事物里。
A 完成后,执行 B ,就先提交 A 事物,再执行 B 。
SvenWong
364 天前
@linuxsteam #5 哦,我漏看了一句代码,我的理解是:你的 A 方法里同步调用了 B 方法,但是 B 方法起了一个新事务,它执行 update 的时候,需要等待 insert 的那个锁,但是 insert 的锁现在还在 A 事务里没提交,但是 A 要等待 B 方法执行结束返回了,事务才能提交。

尝试把 B 方法放到异步操作去,或者把调用 B 方法,放到调用 A 的上层去
missya
364 天前
@linuxsteam 是不是执行 transactionA()后事务还未提交,其实当时 Id=1 的数据并没有实际保存到数据库中,然后紧接着 transactionB()又执行,而又是独立的事务,所以获取不到 Id=1 的数据(其实是事务 A 还在 lock 中)造成无法更新超时,可是尝试下异步调用 transactionB()看看结果如何
kivmi
364 天前
事务 A 改成手动提交,应该可以吧

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 提交事务
transactionManager.commit(status);
} catch (Exception ex) {
// 发生异常时回滚事务
transactionManager.rollback(status);
}
weiqlog
364 天前
设置数据库隔离级别为读未提交 Read Uncommitted 呢?
vczyh
364 天前
我觉的这是业务问题,既然 A 和 B 事务是分开的,那就可以说明他们不是强一致的,那就采用最终一致,即 A 提交后,再执行 B , 不然就把 AB 放到同一个事务,事务和业务是有关联的。
linuxsteam
363 天前
@dengkj A 不慢 B 慢(调用第三方接口)
linuxsteam
363 天前
@bcllemon 需要一起完成,并且同步的。
现在也改成一个事务了,但是就不能在 B 抛出异常交给 A 处理了(这样 B 的数据库操作就全部回滚了)
B 只能改成 手动回滚到某个点,然后 return 。感觉这么写不太好
linuxsteam
363 天前
@SvenWong B 之前就是异步的
但是业务想要同步,所以才这么写代码的。

调用 B 倒是可以,效果一样。就是可读性怪怪的。不过是 OK 的
linuxsteam
363 天前
@weiqlog 可行的,不过不敢调。怕影响太大了,对事物理解不是特别透彻。 来点其他问题 就得不偿失了
linuxsteam
363 天前
@bcllemon 说错了,我是 B 去掉事务了。A 和 B 加入一个事务也会 锁超时
linuxsteam
363 天前
@missya 就是这样的。
之前就是异步调用 transactionB 。现在业务改了,想同步返回结果。所以才出现这个问题的
linuxsteam
363 天前
@SvenWong B 方法没法放到上面,因为上面新建的数据,然后 B 才能去更新 😂

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

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

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

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

© 2021 V2EX