只插入死锁

Goo*_*SQL 7 sql-server deadlock insert sql-server-2012

我们有一个将数据插入表中的应用程序。不幸的是,我们遇到了死锁,而且死锁仅来自插入。我们看到插入在非聚集索引上以不同的顺序获取键锁,这导致了问题。

为什么插入会这样,我们应该怎么做才能缓解死锁?任何帮助或见解表示赞赏。

在下面的示例中,只涉及两个插入,但我们在死锁中涉及多达 4 个不同的插入。

这是死锁图:

    <deadlock>
    <victim-list>
        <victimProcess id="process3ab355868" />
    </victim-list>
    <process-list>
        <process id="process3ab355868" taskpriority="0" logused="1184" waitresource="KEY: 5:72057594043629568 (6234ed5bf036)" waittime="7493" ownerId="92332106" transactionname="implicit_transaction" lasttranstarted="2014-10-13T12:37:43.060" XDES="0x123699668" lockMode="X" schedulerid="3" kpid="3540" status="suspended" spid="89" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-10-13T12:37:44.333" lastbatchcompleted="2014-10-13T12:37:44.333" lastattention="1900-01-01T00:00:00.333" clientapp="Microsoft JDBC Driver for SQL Server" hostname="" hostpid="0" loginname="" isolationlevel="read committed (2)" xactid="92332106" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="278" stmtend="818" sqlhandle="0x0200000053a65d302154b91e9fee55234669030a42479c050000000000000000000000000000000000000000">
                    INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)
                </frame>
                <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 datetime2,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 datetime2,@P4 nvarchar(4000),@P5 nvarchar(4000),@P6 decimal(38,1),@P7 int,@P8 int)INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)                                                                         select SCOPE_IDENTITY() AS GENERATED_KEYS
            </inputbuf>
        </process>
        <process id="process14b38c928" taskpriority="0" logused="2564" waitresource="KEY: 5:72057594043629568 (275232b7b238)" waittime="7491" ownerId="92325909" transactionname="implicit_transaction" lasttranstarted="2014-10-13T12:37:39.567" XDES="0x16b38b988" lockMode="X" schedulerid="3" kpid="3668" status="suspended" spid="65" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-10-13T12:37:44.337" lastbatchcompleted="2014-10-13T12:37:44.337" lastattention="1900-01-01T00:00:00.337" clientapp="Microsoft JDBC Driver for SQL Server" hostname="" hostpid="0" loginname="" isolationlevel="read committed (2)" xactid="92325909" currentdb="5" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128058">
            <executionStack>
                <frame procname="adhoc" line="1" stmtstart="278" stmtend="818" sqlhandle="0x0200000053a65d302154b91e9fee55234669030a42479c050000000000000000000000000000000000000000">
                    INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)
                </frame>
                <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 datetime2,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 datetime2,@P4 nvarchar(4000),@P5 nvarchar(4000),@P6 decimal(38,1),@P7 int,@P8 int)INSERT INTO table (col1, col2, col3, col4, col5, col6, col7, col8, col9) VALUES (@P0, @P1, @P2, @P3, @P4, @P5, @P6, @P7, @P8)                                                                         select SCOPE_IDENTITY() AS GENERATED_KEYS
            </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594043629568" dbid="5" objectname="table1" indexname="unique_index" id="lock17bc3a480" mode="X" associatedObjectId="72057594043629568">
            <owner-list>
                <owner id="process14b38c928" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process3ab355868" mode="X" requestType="wait" />
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594043629568" dbid="5" objectname="table1" indexname="unique_index" id="lock10735ce00" mode="X" associatedObjectId="72057594043629568">
            <owner-list>
                <owner id="process3ab355868" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process14b38c928" mode="X" requestType="wait" />
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>
Run Code Online (Sandbox Code Playgroud)

这是表 DDL:

CREATE TABLE [table1](
    [col0] [int] IDENTITY(1,1) NOT NULL,
    [col1] [int] NOT NULL,
    [col2] [int] NOT NULL,
    [col3] [decimal](15, 4) NULL,
    [col4] [datetime2](7) NOT NULL,
    [col5] [varchar](8) NOT NULL,
    [col6] [varchar](30) NOT NULL,
    [col7] [datetime2](7) NOT NULL,
    [col8] [varchar](8) NOT NULL,
    [col9] [varchar](30) NOT NULL,
 CONSTRAINT [PK] PRIMARY KEY CLUSTERED 
(
    [col0] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
 CONSTRAINT [unique_index] UNIQUE NONCLUSTERED 
(
    [col2] ASC,
    [col1] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE table1 ADD  DEFAULT (sysdatetime()) FOR [col4]
GO

ALTER TABLE table1 ADD  DEFAULT (sysdatetime()) FOR [col7]
GO
Run Code Online (Sandbox Code Playgroud)

Goo*_*SQL 7

我在这里回答我自己的问题,因为我们终于找到了问题所在。

简短版本:我们向非聚集索引添加了第三列。死锁消失了。

长版:

首先,查看James Rowland-Jones关于锁哈希冲突的 dynamite 博客文章(我的解释与他的质量相去甚远)。

来自博客文章:

当 SQL Server 需要锁定一行时,它会根据表的键值创建一个哈希值。锁定管理器使用的正是这个哈希值,这意味着在检查行是否已被锁定时,它只有一个值可供查看。

当生成重复的哈希值时,就会发生锁哈希冲突。

在对许多死锁图进行更深入的分析后,我们注意到很多 WAITRESOURCE Key 哈希值(括号之间的值)是相同的。我开始制作一个简短的清单来跟踪:

waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (bb0d06c12baa)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (5b39284eef16)
waitresource="KEY: 5:72057594043629568 (a27543d90a1a)
waitresource="KEY: 5:72057594043629568 (8328314847df)
waitresource="KEY: 5:72057594043629568 (5b39284eef16)
Run Code Online (Sandbox Code Playgroud)

果然,我们从不同的死锁图中得到了很多重复的哈希值。我决定查看 unique_index 索引(发生死锁的位置)的两列(col2 和 col1)中的数据。问题中的所有表 DDL 都在上面。

对于 col1 列中的单个值,col2 列的值始终为 1-6。所以这开始变得有意义了。可用于 SQL 生成哈希值的数据种类有限——这解释了为什么我们会得到重复的哈希值。

博客中提到的 JRJ 修复之一是向索引添加一个额外的列。这为数据增加了一些多样性,并为散列算法提供了更多选择。幸运的是,我们能够向索引添加 create_timestamp 列并保持与两列相同的唯一性。繁荣!将第三列添加到索引后,死锁消失了。

旁注:博客上的评论之一建议禁用索引上的行锁定。我们首先尝试了这个。它确实摆脱了死锁,但导致更多的锁定并将整体吞吐量降低了大约 40-50%,因此我们不喜欢我们系统的这个选项。但是,在工作负载较轻的数据库上,这可能会正常工作。

希望这一切都有意义。