在运行三足事务时避免死锁

Posted

技术标签:

【中文标题】在运行三足事务时避免死锁【英文标题】:Avoiding Deadlock while running a three legged transaction 【发布时间】:2019-08-02 21:15:15 【问题描述】:

在会计系统中,资金可以在账户之间转移。

为了避免重复预订交易时出现僵局(账户之间的转账)从账户到账户的转账是基于id order:

@Transactional
    override fun doubleBookPrepaid(eventId: Long, srcPurposefulAccountId: PurposefulAccountId, trgPurposefulAccountId: PurposefulAccountId, amount: Money): Pair<Money, Money>? =
        if (srcPurposefulAccountId.accountId < trgPurposefulAccountId.accountId)  // Locking minimal account ID first, to prevent deadlocks.
            val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -amount)
            val trgBooking = bookPrepaid(eventId, trgPurposefulAccountId, amount)

            T(srcBooking, trgBooking)
        
        else 
            val trgBooking = bookPrepaid(eventId, trgPurposefulAccountId, amount)
            val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -amount)

            T(srcBooking, trgBooking)
        

我怎样才能为 三边 交易实现相同的结果? 在这种交易中,一个账户会在同一笔交易中向两个账户转账:

data class PurposefulAccountTransfer(val trgPurposefulAccountId: PurposefulAccountId, val amount: Money)
    @Transactional
    fun threeLegBookPrepaid(eventId: Long, srcPurposefulAccountId: PurposefulAccountId, purposefulAccountTransfer: PurposefulAccountTransfer, secondPurposefulAccountTransfer: PurposefulAccountTransfer) 
        val srcBooking = bookPrepaid(eventId, srcPurposefulAccountId, -(purposefulAccountTransfer.amount + secondPurposefulAccountTransfer.amount))
        val trgFirstBooking = bookPrepaid(eventId, purposefulAccountTransfer.trgPurposefulAccountId, purposefulAccountTransfer.amount)
        val trgSecondBooking = bookPrepaid(eventId, secondPurposefulAccountTransfer.trgPurposefulAccountId, secondPurposefulAccountTransfer.amount)
    

【问题讨论】:

【参考方案1】:

您只需对所有命令进行排序以确保不存在循环依赖(假设T() 接受vararg):

fun threeLegBookPrepaid(eventId: Long, ...) 
   val txs = sortedMapOf(
      srcAccountId  to bookPrepaid(eventId, srcAccountId , ...),
      trg1AccountId to bookPrepaid(eventId, trg1AccountId, ...),
      trg2AccountId to bookPrepaid(eventId, trg2AccountId, ...)
   )
   .map  it.component2() 
   .toTypedArray()

   T(*txs)

这可以很容易地推广到任意数量的帐户。

【讨论】:

以上是关于在运行三足事务时避免死锁的主要内容,如果未能解决你的问题,请参考以下文章

事务 - 如何避免死锁?

避免 SQL 事务中的死锁

为啥这个事务会产生死锁?

事务 ( 进程 ID 60) 与另一个进程被死锁在锁资源上,并且已被选作死锁牺牲品。请重新运行 该事务。

mysql 开发进阶篇系列 14 锁问题(避免死锁,死锁查看分析)

死锁产生的原因及避免死锁的方法