我们有一个将数据插入表的应用程序。不幸的是,我们遇到了死锁,而死锁仅来自插入。我们看到插入在导致问题的非聚集索引上以不同的顺序获取键锁。
为什么插入行为会这样?我们应该怎么做才能缓解死锁?任何帮助或见解表示赞赏。
在下面的示例中,只涉及两个插入,但我们有多达 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>
这是 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
我在这里回答我自己的问题,因为我们终于找到了问题所在。
简短版本:我们在非聚集索引中添加了第三列。死锁消失了。
长版:
首先,查看James Rowland-Jones 的有关锁散列冲突的 dynamite 博客文章(我的解释与他的质量相去甚远)。
来自博客文章:
当生成重复的哈希值时,就会发生锁哈希冲突。
在对许多死锁图进行更深入的分析后,我们注意到很多 WAITRESOURCE Key 哈希值(括号之间的值)是相同的。我开始制作一个简短的清单来跟踪:
果然,我们从不同的死锁图中得到了很多重复的哈希值。我决定查看 unique_index 索引(发生死锁的位置)的两列(col2 和 col1)中的数据。所有表格 DDL 都在问题的上方。
对于 col1 列中的单个值,col2 列的值始终为 1-6。所以这开始有意义了。SQL 可用于生成哈希值的数据种类有限 - 这解释了为什么我们会得到重复的哈希值。
JRJ在博客中提到的修复之一是在索引中添加一个额外的列。这为数据增加了一些多样性,并为散列算法提供了更多选择。幸运的是,我们能够向索引添加一个 create_timestamp 列,并保持与两列相同的唯一性。繁荣!将第三列添加到索引后,死锁就消失了。
旁注:博客上的评论之一建议禁用索引上的行锁定。我们首先尝试了这个。它确实摆脱了死锁,但导致更多的锁定并将整体吞吐量降低了大约 40-50%,所以我们不喜欢我们的系统使用这个选项。但是,在工作负载较轻的数据库上,这可能会正常工作。
希望这一切都有意义。
我不确定这是否真的可以回答,但我不能将其发布为评论,所以把这个
如果你看到你的应用程序代码就像
如果您看到您已声明
@p4,@p5...@P6
为,nvarchar()
但如果您看到表定义他们在其中插入值的列被声明为
varchar
并且您传递的值是nvarchar
。您的表列都没有nvarchar
数据类型。如果这是真的,就会发生隐式转换,并且会发生索引扫描,而不是寻求持有锁(特别是当你有 update 时)更长的时间。我想这可能是导致僵局的原因。还有 trancount=2 这意味着有未提交的事务,如果需要查询调整,当然还有位。
参与死锁的两个事务都在争用相同的资源
waitresource="KEY: 5:72057594043629568 (6234ed5bf036)"