为啥相同事务的两个并行执行之间会出现死锁?

Posted

技术标签:

【中文标题】为啥相同事务的两个并行执行之间会出现死锁?【英文标题】:Why is there a deadlock between two parallel executions of identical transactions?为什么相同事务的两个并行执行之间会出现死锁? 【发布时间】:2022-01-24 02:16:50 【问题描述】:

我有一个使用 SQL 事务来更新 SQL Server 上的 msdb 数据库的应用程序。该事务以不规则的时间间隔定期执行。使用 SQL Server 2012 (SP4)。一段时间后,SQL Server 报告 msdb.dbo.backupset 表出现死锁,唯一的参与者是执行上述 SQL 事务的进程

以下是事务的功能示例,将参数替换为示例值,以便可以在 msdb 上轻松执行。应用程序在每次调用事务时传递新参数。

BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 3498233
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 378880

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion > 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    

UPDATE bms
    SET media_family_count = @MediaFamilyCount, mirror_count = @MirrorCount
    FROM msdb..backupmediaset AS bms
    WHERE media_set_id = @MediaSetId
    
 -- All bmf's

SET @FamilySequenceNumber = 1
SET @BackupFileName = N'\\testcase\Test_TLog_20211121180001.trn'
SET @Mirror = 0

UPDATE bmf
    SET bmf.physical_device_name = @BackupFileName
    FROM msdb..backupmediafamily AS bmf
    WHERE     bmf.media_set_id           = @MediaSetId
          AND bmf.family_sequence_number = @FamilySequenceNumber
          AND bmf.mirror                 = @Mirror
          
UPDATE bmf
    SET bmf.logical_device_name = @LogDevName
    FROM msdb..backupmediafamily AS bmf
    WHERE     bmf.media_set_id           = @MediaSetId
          AND bmf.family_sequence_number = @FamilySequenceNumber
          AND bmf.mirror                 = @Mirror

SET @MaxFamilySequence = 1

DELETE FROM msdb..backupmediafamily WITH (ROWLOCK)
WHERE media_set_id = @MediaSetId AND family_sequence_number > @MaxFamilySequence

COMMIT TRAN UpdateMediaTables

这是死锁图的 xml:

<deadlock>
    <victim-list>
        <victimProcess id="process2d890c8"/>
    </victim-list>
    <process-list>
        <process id="process2d890c8" taskpriority="-5" logused="0" waitresource="PAGE: 4:1:8623 " waittime="1550" ownerId="1566457106" transactionname="UpdateMediaTables" lasttranstarted="2021-12-21T15:00:05.697" XDES="0xb16d09b80" lockMode="U" schedulerid="8" kpid="19856" status="suspended" spid="488" sbid="0" ecid="5" priority="5" trancount="0" lastbatchstarted="2021-12-21T15:00:05.660" lastbatchcompleted="2021-12-21T15:00:05.633" lastattention="1900-01-01T00:00:00.633" clientapp="Demo" hostname="AA-SQL1" hostpid="14200" isolationlevel="read uncommitted (1)" xactid="1566457106" currentdb="1" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="190" sqlhandle="0x020000000aac851a57d841f58dc63ba4bbe4b4059b68c6ab0000000000000000000000000000000000000000">
UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount, compressed_backup_size = @CompressedSize FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId    </frame>
                <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" stmtstart="-1" sqlhandle="0x0400ff7f427f99d9010000000000000000000000000000000000000000000000000000000000000000000000">
sp_executesql    </frame>
                <frame procname="adhoc" line="36" stmtstart="1996" stmtend="2528" sqlhandle="0x02000000a5af092360e1ef5edb55dab1b8cfba609a9b02e50000000000000000000000000000000000000000">
EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    </frame>
            </executionStack>
            <inputbuf>
BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 566952
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 156160

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion &gt; 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql,    </inputbuf>
        </process>
        <process id="process2daa928" taskpriority="-5" logused="0" waitresource="PAGE: 4:1:9263 " waittime="1550" ownerId="1566457106" transactionname="UpdateMediaTables" lasttranstarted="2021-12-21T15:00:05.697" XDES="0x14b82acbd0" lockMode="U" schedulerid="1" kpid="8248" status="suspended" spid="488" sbid="0" ecid="6" priority="5" trancount="0" lastbatchstarted="2021-12-21T15:00:05.660" lastbatchcompleted="2021-12-21T15:00:05.633" lastattention="1900-01-01T00:00:00.633" clientapp="Demo" hostname="AA-SQL1" hostpid="14200" isolationlevel="read uncommitted (1)" xactid="1566457106" currentdb="1" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="190" sqlhandle="0x020000000aac851a57d841f58dc63ba4bbe4b4059b68c6ab0000000000000000000000000000000000000000">
UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount, compressed_backup_size = @CompressedSize FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId    </frame>
                <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" stmtstart="-1" sqlhandle="0x0400ff7f427f99d9010000000000000000000000000000000000000000000000000000000000000000000000">
sp_executesql    </frame>
                <frame procname="adhoc" line="36" stmtstart="1996" stmtend="2528" sqlhandle="0x02000000a5af092360e1ef5edb55dab1b8cfba609a9b02e50000000000000000000000000000000000000000">
EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    </frame>
            </executionStack>
            <inputbuf>
BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 566952
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 156160

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion &gt; 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql,    </inputbuf>
        </process>
        <process id="process2bb4558" taskpriority="-5" logused="328" waitresource="PAGE: 4:1:9263 " waittime="948" ownerId="1566456428" transactionname="UpdateMediaTables" lasttranstarted="2021-12-21T15:00:04.410" XDES="0x14981ffb80" lockMode="U" schedulerid="7" kpid="19408" status="suspended" spid="470" sbid="0" ecid="7" priority="5" trancount="0" lastbatchstarted="2021-12-21T15:00:04.393" lastbatchcompleted="2021-12-21T15:00:04.393" lastattention="1900-01-01T00:00:00.393" clientapp="Demo" hostname="AA-SQL1" hostpid="14732" isolationlevel="read uncommitted (1)" xactid="1566456428" currentdb="1" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="190" sqlhandle="0x020000000aac851a57d841f58dc63ba4bbe4b4059b68c6ab0000000000000000000000000000000000000000">
UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount, compressed_backup_size = @CompressedSize FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId    </frame>
                <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" stmtstart="-1" sqlhandle="0x0400ff7f427f99d9010000000000000000000000000000000000000000000000000000000000000000000000">
sp_executesql    </frame>
                <frame procname="adhoc" line="36" stmtstart="1996" stmtend="2528" sqlhandle="0x02000000a9093d2c0a0d22ed2f73dc20e182fd1d912553b10000000000000000000000000000000000000000">
EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    </frame>
            </executionStack>
            <inputbuf>
BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 566948
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 259072

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion &gt; 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql,    </inputbuf>
        </process>
        <process id="process2daacf8" taskpriority="-5" logused="328" waitresource="PAGE: 4:1:8623 " waittime="972" ownerId="1566456428" transactionname="UpdateMediaTables" lasttranstarted="2021-12-21T15:00:04.410" XDES="0x14b82ace80" lockMode="U" schedulerid="1" kpid="7048" status="suspended" spid="470" sbid="0" ecid="4" priority="5" trancount="0" lastbatchstarted="2021-12-21T15:00:04.393" lastbatchcompleted="2021-12-21T15:00:04.393" lastattention="1900-01-01T00:00:00.393" clientapp="Demo" hostname="AA-SQL1" hostpid="14732" isolationlevel="read uncommitted (1)" xactid="1566456428" currentdb="1" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="190" sqlhandle="0x020000000aac851a57d841f58dc63ba4bbe4b4059b68c6ab0000000000000000000000000000000000000000">
UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount, compressed_backup_size = @CompressedSize FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId    </frame>
                <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" stmtstart="-1" sqlhandle="0x0400ff7f427f99d9010000000000000000000000000000000000000000000000000000000000000000000000">
sp_executesql    </frame>
                <frame procname="adhoc" line="36" stmtstart="1996" stmtend="2528" sqlhandle="0x02000000a9093d2c0a0d22ed2f73dc20e182fd1d912553b10000000000000000000000000000000000000000">
EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    </frame>
            </executionStack>
            <inputbuf>
BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 566948
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 259072

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion &gt; 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql,    </inputbuf>
        </process>
        <process id="process12ee786188" taskpriority="-5" logused="10000" waittime="730" schedulerid="12" kpid="1808" status="suspended" spid="470" sbid="0" ecid="0" priority="5" trancount="2" lastbatchstarted="2021-12-21T15:00:04.393" lastbatchcompleted="2021-12-21T15:00:04.393" lastattention="1900-01-01T00:00:00.393" clientapp="Demo" hostname="AA-SQL1" hostpid="14732" loginname="DEMO\test" isolationlevel="read uncommitted (1)" xactid="1566456428" currentdb="1" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="190" sqlhandle="0x020000000aac851a57d841f58dc63ba4bbe4b4059b68c6ab0000000000000000000000000000000000000000">
UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount, compressed_backup_size = @CompressedSize FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId    </frame>
                <frame procname="mssqlsystemresource.sys.sp_executesql" line="1" stmtstart="-1" sqlhandle="0x0400ff7f427f99d9010000000000000000000000000000000000000000000000000000000000000000000000">
sp_executesql    </frame>
                <frame procname="adhoc" line="36" stmtstart="1996" stmtend="2528" sqlhandle="0x02000000a9093d2c0a0d22ed2f73dc20e182fd1d912553b10000000000000000000000000000000000000000">
EXEC sp_executesql @sql, N'@MediaFamilyCount int, @BackupFileNumber int, @CompressedSize numeric(20, 0), @MediaSetId int', @BackupFileNumber = @BackupFileNumber,@MediaFamilyCount = @MediaFamilyCount, @CompressedSize = @CompressedSize, @MediaSetId = @MediaSetId    </frame>
            </executionStack>
            <inputbuf>
BEGIN TRAN UpdateMediaTables

DECLARE @SqlVersion int
DECLARE @LogDevName nvarchar(512)
DECLARE @MediaSetId int
DECLARE @FamilySequenceNumber tinyint
DECLARE @MaxFamilySequence tinyint
DECLARE @Mirror tinyint
DECLARE @BackupFileName nvarchar(1024)
DECLARE @BackupFileNumber int
DECLARE @MediaFamilyCount int
DECLARE @MirrorCount int
DECLARE @CompressedSize numeric(20, 0)
DECLARE @sql as nvarchar(4000)

SET @SqlVersion = @@microsoftversion / 0x1000000
SET @MediaSetId = 566948
SET @BackupFileNumber = 1
SET @MediaFamilyCount = 1
SET @MirrorCount = 1
SET @CompressedSize = 259072

SELECT @LogDevName = name
FROM master..sysdevices AS sdev
WHERE phyname = @BackupFileName

SET @sql = N'UPDATE bs SET position = @BackupFileNumber, last_family_number = @MediaFamilyCount'

IF @SqlVersion &gt; 9  -- 2008+
    BEGIN
        SET @sql = @sql + N', compressed_backup_size = @CompressedSize'
    END
SET @sql = @sql + N' FROM msdb..backupset AS bs
            WHERE media_set_id = @MediaSetId'

EXEC sp_executesql @sql,    </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <pagelock fileid="1" pageid="8623" dbid="4" subresource="FULL" objectname="msdb.dbo.backupset" id="lock5e1d530780" mode="U" associatedObjectId="72057594045595648">
            <owner-list>
                <owner id="process12ee786188" mode="U"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2d890c8" mode="U" requestType="wait"/>
            </waiter-list>
        </pagelock>
        <pagelock fileid="1" pageid="9263" dbid="4" subresource="FULL" objectname="msdb.dbo.backupset" id="lockf1b4c0980" mode="U" associatedObjectId="72057594045595648">
            <owner-list>
                <owner id="process12ee786188" mode="U"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2daa928" mode="U" requestType="wait"/>
            </waiter-list>
        </pagelock>
        <pagelock fileid="1" pageid="9263" dbid="4" subresource="FULL" objectname="msdb.dbo.backupset" id="lockf1b4c0980" mode="U" associatedObjectId="72057594045595648">
            <owner-list>
                <owner id="process2daa928" mode="U" requestType="wait"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2bb4558" mode="U" requestType="wait"/>
            </waiter-list>
        </pagelock>
        <pagelock fileid="1" pageid="8623" dbid="4" subresource="FULL" objectname="msdb.dbo.backupset" id="lock5e1d530780" mode="U" associatedObjectId="72057594045595648">
            <owner-list>
                <owner id="process2d890c8" mode="U" requestType="wait"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2daacf8" mode="U" requestType="wait"/>
            </waiter-list>
        </pagelock>
        <exchangeEvent id="Pipe25c250400" WaitType="e_waitPipeGetRow" nodeId="2">
            <owner-list>
                <owner id="process2daacf8"/>
                <owner id="process2bb4558"/>
            </owner-list>
            <waiter-list>
                <waiter id="process12ee786188"/>
            </waiter-list>
        </exchangeEvent>
    </resource-list>
</deadlock>

我不明白这种情况如何导致僵局。即使这两个进程并行重合,这两个事务也是相同的,因此以相同的顺序执行它们的查询。此外,更新备份集表的查询没有死锁对象,因为它是整个事务中唯一查询该表的查询。锁定是,但不是死锁。有人可以帮我理解这种死锁情况是如何发生的吗?

【问题讨论】:

猜测您可能需要在msdb..backupset (media_set_id) 上添加索引。您可能希望在该查询(动态 SQL 中的那个)上有一个 ROWLOCK 提示。旁白:动态 SQL 变量应始终声明为 nvarchar(max) msdb..backupset (media_set_id) 已经有一个索引。我同意@Charlieface 的观点,如果你要设置行锁提示会有所帮助。发生死锁的原因是您在页面锁上存在竞争条件,其中两个进程都在另一个进程需要的页面上获取锁。您可以在包含的 XML 的底部看到这一点(请参阅&lt;resource-list&gt; 节点)。假设您从不并行更新相同的MediaSetId ,ROWLOCKs 应该可以解决您的问题。 【参考方案1】:

我不会诊断这个,而是点击“简单按钮”,然后使用应用程序锁序列化事务:

BEGIN TRAN UpdateMediaTables
exec sp_getapplock 'UpdateMediaTables', 'Exclusive'
. . .

那么第二个会话将在获取任何锁之前被阻塞,直到第一个会话完成。

【讨论】:

这对于这种情况非常有用。很有帮助,非常感谢

以上是关于为啥相同事务的两个并行执行之间会出现死锁?的主要内容,如果未能解决你的问题,请参考以下文章

JDK并发策略

为啥执行矩阵乘法的两个进程并行运行比连续运行慢?

为啥执行计划中的并行性不好[关闭]

当 Hangfire 并行处理多个作业时,为啥 MySQL InnoDB 会产生如此多的死锁?

为啥并行和串行版本的执行时间几乎一样

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