V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dengji85
V2EX  ›  程序员

有一段业务代码,不知道会不会死锁

  •  
  •   dengji85 · 2023-02-07 18:32:52 +08:00 · 982 次点击
    这是一个创建于 443 天前的主题,其中的信息可能已经有所发展或是发生改变。

    线上环境偶尔一个待办无法处理,服务器 cpu100%,原因时数据库死锁了,因为逻辑是这样

    有 2 条 id 分别为 1 ,2 的待办数据
    处理 1 后,自动启一个线程更新 2
    处理 2 后,自动启一个线程更新 1
    因为处理比较耗时,用户可能反复重试,看起来会有死锁,但是自动更新是异步,不在一个事务里,这种情况会产生死锁吗?
    
    service{
        update(){
        //事务
        //耗时较长
        }
    }
    
    WebControllerMethod(){
        update(id:1)
        ThreadPoolTaskExecutor.execute(() -> {
            update(id:2)
        }
    }
    
    5 条回复    2023-02-08 15:36:46 +08:00
    googlefans
        1
    googlefans  
       2023-02-07 19:01:27 +08:00
    去问问 chatgpt
    xmarsman
        2
    xmarsman  
       2023-02-08 09:46:28 +08:00
    chatgpt 的回复如下:
    ----------------------------------------------------------------------------
    上面的代码存在以下问题:

    update 方法没有返回值,但却使用了 return 语句。
    ThreadPoolTaskExecutor.execute 中没有对 update 方法进行调用,而是直接把 update 当作了一个代码块,这是错误的。
    缺少括号,在 ThreadPoolTaskExecutor.execute 后面的代码是无法正常编译的。
    Jtyczc
        3
    Jtyczc  
       2023-02-08 12:03:46 +08:00 via Android
    增加一个中间状态,处理中,然后开始处理前,判断一下异步更新是否处理中,如果处理中就告诉用户先等待,如果没有,去更新。
    tairan2006
        4
    tairan2006  
       2023-02-08 13:35:03 +08:00
    update(id:1)和 update(id:2)会有并发,如果你只是加行锁,两边应该没啥关联,当然具体的情况需要看业务逻辑。

    如果用户反复尝试,你应该第二次直接报错,而不是走到业务逻辑里。可以通过 redis 搞个分布式锁,或者显式的 select for update 加锁(MySQL 的话)。
    lookStupiToForce
        5
    lookStupiToForce  
       2023-02-08 15:36:46 +08:00   ❤️ 1
    不了解你用的啥框架去连的什么数据库,初看,感觉触发的是超时锁,不是真死锁

    真死锁得是两个进程,各自在其线程内 /事务内
    pid1: 先获取 lock1——do something——再获取 lock2
    pid2: 先获取 lock2——do something——再获取 lock1

    然后 pid1 因为 pid2 锁住了 lock2 ,导致 pid1 无法进入后续步骤,无法结束,进而 [无法释放 lock1] ,
    进一步导致 pid2 无法获得 lock1 也无法结束无法释放 lock2 。
    ( pid1 try lock2 -- failed because pid2 hold lock2 -- pid1 keep holding lock1 -- pid2 try lock1 and failed -- pid2 keep holding lock2 -- pid1 try lock2......)

    产生死锁的点在于,pid1 和 pid2 在不能获取第二步骤的锁( pid1 拿 lock2 ,pid2 拿 lock1 )的时候, [无法释放第一个锁] 。

    但如果
    WebControllerMethod(){
    update(id:1)
    ThreadPoolTaskExecutor.execute(() -> {
    update(id:2)
    }
    }
    里的 update(id:1) 可以在执行 ThreadPoolTaskExecutor.execute(() -> {update(id:2)} 前,就释放 id:1 的锁,那理论上就不存在死锁了

    所以怀疑是超时锁,因为许许多多因为用户反复重试导致的 pid1 在不停 lock2 and update 2 ,导致你新的 pid2 lock2 fail 进而报了超时。

    除非有种可能,你的 commit/rollback 不在 update 方法里,而在 WebControllerMethod 里,那么你俩线程其实是共用同一个连接同一个事务,这样就肯定有死锁的情况了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3295 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 12:55 · PVG 20:55 · LAX 05:55 · JFK 08:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.