mysql 第一个事务 创建数据,第二个事务修改同一条数据 Lock wait timeout exceeded 怎么解决? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
linuxsteam
V2EX    数据库

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

  •  
  •   linuxsteam 2023 年 11 月 29 日 2795 次点击
    这是一个创建于 781 天前的主题,其中的信息可能已经有所发展或是发生改变。

    伪代码:

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

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

    第 1 条附言    2023 年 11 月 29 日
    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")); } } } 
    第 2 条附言    2023 年 11 月 29 日

    根据各位老哥回复的启发

    更改了业务代码,把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")); } } } 
    33 条回复    2024-03-20 19:03:03 +08:00
    cnhongwei
        1
    cnhongwei  
       2023 年 11 月 29 日
    那你不要在 B 中开新事务,而是记录 Log 的类中开新事务。
    missya
        2
    missya  
       2023 年 11 月 29 日
    A 和 B 方法是怎么调用的,A 中调用 B ?还是同时分别调用?
    SvenWong
        3
    SvenWong  
       2023 年 11 月 29 日
    看错误就是锁等待超时了,但是具体解决还是要看场景,比如你的 B 事务中对 TableA 的更新是否在单独线程,以及先后顺序,A 事务 insert TableA 之后,是否还有大量的代码操作,导致 A 事务迟迟无法提交等等
    linuxsteam
        4
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @missya A 调用 B ,对不起 没贴上,我附言了
    linuxsteam
        5
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @SvenWong
    A 事务 insert TableA 之后,是否还有大量的代码操作:
    没有了 就是调用事务 B ,因为事务 B 获取不到锁 就挂了
    dengkj
        6
    dengkj  
       2023 年 11 月 29 日
    尽量缩短事务 A 的执行时间,相关性不强的业务可以异步执行。
    linuxsteam
        7
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @cnhongwei 插入日志也不会报错。
    在抽出来插入日志的代码上新开事务 我理解没有实际作用呀
    bcllemon
        8
    bcllemon  
       2023 年 11 月 29 日
    A 和 B 是要一起完成, 就放到一个事物里。
    A 完成后,执行 B ,就先提交 A 事物,再执行 B 。
    SvenWong
        9
    SvenWong  
       2023 年 11 月 29 日
    @linuxsteam #5 哦,我漏看了一句代码,我的理解是:你的 A 方法里同步调用了 B 方法,但是 B 方法起了一个新事务,它执行 update 的时候,需要等待 insert 的那个锁,但是 insert 的锁现在还在 A 事务里没提交,但是 A 要等待 B 方法执行结束返回了,事务才能提交。

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

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

    调用 B 倒是可以,效果一样。就是可读性怪怪的。不过是 OK 的
    linuxsteam
        17
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @weiqlog 可行的,不过不敢调。怕影响太大了,对事物理解不是特别透彻。 来点其他问题 就得不偿失了
    linuxsteam
        18
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @bcllemon 说错了,我是 B 去掉事务了。A 和 B 加入一个事务也会 锁超时
    linuxsteam
        19
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @missya 就是这样的。
    之前就是异步调用 transactionB 。现在业务改了,想同步返回结果。所以才出现这个问题的
    linuxsteam
        20
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @SvenWong B 方法没法放到上面,因为上面新建的数据,然后 B 才能去更新
    SvenWong
        21
    SvenWong  
       2023 年 11 月 29 日
    @linuxsteam #16 那既然要同步,就没必要 Propagation.REQUIRES_NEW 了吧,在一个事务里面做就好了,一起成功一起失败
    kivmi
        22
    kivmi  
       2023 年 11 月 29 日
    @linuxsteam 既然是需要第三方的接口,为啥不先拿到数据,然后执行插入更新事务呢?
    vishun
        23
    vishun  
       2023 年 11 月 29 日
    @linuxsteam #18 同一个事务内也会超时?不可能吧。
    zhuzhibin
        24
    zhuzhibin  
       2023 年 11 月 29 日 via iPhone
    没人问隔离级别么? RR 还是 RC ?以及不太明白业务上为啥插入 id =1 的,立马又事物更新这行记录的其他字段,所以是期望同个事务内,或者能否描述下业务诉求
    kivmi
        25
    kivmi  
       2023 年 11 月 29 日
    @zhuzhibin 感觉它这个为啥使用事务,推测是它是先插入,然后推送数据到第三方,之后第三方修改状态,然后回传字段,感觉就没必要使用事务
    linuxsteam
        26
    linuxsteam  
    OP
       2023 年 11 月 29 日
    @zhuzhibin 当然是 Mysql 默认隔离级别,允许重复度。
    业务是新插入这条数据。
    然后再拿这条数据去请求第三方系统。(最早时候是异步请求第三方系统的,但是后来要求同步了)

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

    后来调用方要求同步返回 RPC 接口的实时状态,就只能这样了。
    补偿倒是没事,主要怕产生无效数据
    dyv9
        33
    dyv9  
       2024 年 3 月 20 日 via Android
    @linuxsteam 日志要用 RequiredsNew 事务设置 呀。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3526 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 37ms UTC 00:51 PVG 08:51 LAX 16:51 JFK 19:51
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86