SQL Server 2005:读取提交事务隔离级别中的键范围锁?
Posted
技术标签:
【中文标题】SQL Server 2005:读取提交事务隔离级别中的键范围锁?【英文标题】:SQL Server 2005: Key-Range Locks in Read Committed Transaction Isolation Level? 【发布时间】:2009-01-15 13:33:21 【问题描述】:我正在帮助解决使用 SQL Server 2005 的 .NET 应用程序中的一些死锁问题。我有来自下面跟踪的 XML 数据。
真正让我感到困惑的是,当事务隔离级别被读取提交时,PK_Exp_Experience_PriorFirm
上的 RangeX-X 锁。
我读过的所有内容都表明您只获得了一个键范围锁,您正在使用“可序列化”事务隔离级别。到目前为止,我在我们的应用程序中找不到任何将隔离级别设置为读取提交以外的任何位置,并且下面的 XML 也表明我们正在使用读取提交。
但如果我们使用已提交读,我不明白跟踪如何显示存在键范围锁定。有没有人知道这是怎么发生的?
<deadlock-list>
<deadlock victim="processc2f438">
<process-list>
<process id="processc2f438" taskpriority="0" logused="13488" waitresource="KEY: 120:72057594583646208 (8201498b6efe)" waittime="484" ownerId="693258089" transactionname="user_transaction" lasttranstarted="2009-01-06T16:33:27.817" XDES="0xa71ce370" lockMode="U" schedulerid="1" kpid="9112" status="suspended" spid="53" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-01-06T16:33:27.863" lastbatchcompleted="2009-01-06T16:33:27.863" clientapp=".Net SqlClient Data Provider" hostname="CHQAPT3" hostpid="6464" loginname="AppUser" isolationlevel="read committed (2)" xactid="693258089" currentdb="120" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="1" stmtstart="108" sqlhandle="0x0200000015d9962978fc6206b09e4c872150511b455e8923">
UPDATE Exp_Experience_PriorFirm SET RelatedGuid = @newGuid WHERE RelatedGuid = @oldGuid
</frame>
<frame procname="mssqlsystemresource.sys.sp_executesql" line="1" sqlhandle="0x0400ff7fbe80662601000000000000000000000000000000">
sp_executesql
</frame>
<frame procname="MyDb.dbo.Contact_MergeRelationships" line="74" stmtstart="4754" stmtend="4976" sqlhandle="0x0300780036a608461ed8af00669b00000100000000000000">
EXEC sp_executesql @sql,
N'@oldGuid uniqueidentifier, @newGuid uniqueidentifier',
@oldGuid, @newGuid
</frame>
<frame procname="MyDb.dbo.Contact_Company_MergeRelationships" line="8" stmtstart="312" sqlhandle="0x03007800b271a129c8ccaf00669b00000100000000000000">
EXEC Contact_MergeRelationships @oldGuid, @newGuid, 'Contact_Company', @excludedTableNames
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 120 Object Id = 698446258]
</inputbuf>
</process>
<process id="processeb5d68" taskpriority="0" logused="14212" waitresource="KEY: 120:72057594594066432 (7c02a3a5890e)" waittime="2312" ownerId="693243114" transactionname="user_transaction" lasttranstarted="2009-01-06T16:33:20.957" XDES="0x8cdb9450" lockMode="S" schedulerid="2" kpid="9000" status="suspended" spid="73" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2009-01-06T16:33:29.770" lastbatchcompleted="2009-01-06T16:33:29.770" clientapp=".Net SqlClient Data Provider" hostname="CHQAPT3" hostpid="6464" loginname="AppUser" isolationlevel="read committed (2)" xactid="693243114" currentdb="120" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="MyDb.dbo.Contact_Company_Delete" line="27" stmtstart="1128" sqlhandle="0x03007800b0e5761877cbaf00669b00000100000000000000">
DELETE FROM Contact WHERE GUID = @Guid;
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 120 Object Id = 410445232]
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594583646208" dbid="120" objectname="MyDb.dbo.Exp_Experience_PriorFirm" indexname="PK_Exp_Experience_PriorFirm" id="lockd1d43f80" mode="RangeX-X" associatedObjectId="72057594583646208">
<owner-list>
<owner id="processeb5d68" mode="RangeX-X"/>
</owner-list>
<waiter-list>
<waiter id="processc2f438" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594594066432" dbid="120" objectname="MyDb.dbo.Contact_PersonCompanyLocation" indexname="PK_Contact_PersonCompanyLocation" id="lockd20c4380" mode="X" associatedObjectId="72057594594066432">
<owner-list>
<owner id="processc2f438" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="processeb5d68" mode="S" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
【问题讨论】:
【参考方案1】:PK_Exp_Experience_PriorFirm 上的 RangeX-X 锁被视为级联删除的一部分。
SQL Server 自动将某些操作的隔离级别升级为可序列化,例如级联删除。
这里有更详细的描述:Conor vs. Isolation Level Upgrade on UPDATE/DELETE Cascading RI。
【讨论】:
+1。隔离级别自动升级或通过代码意外引入到比可重复读取更高的级别可能是原因。【参考方案2】:正如您所料,您正在使用 READ COMMITTED。
如果 UPDATE 获取聚集索引上的排他键锁并修改行,并且该锁阻止了 SELECT 在聚集索引上的书签查找,则可能会发生此类死锁。
这种性质的锁通常可以通过创建一个覆盖的非聚集索引来消除。
您可以使用的另一个选项是为数据库设置READ_COMMITTED_SNAPSHOT ON
。这改变了 SELECT 语句读取已提交数据的方式;它们不采用共享锁,而是读取在 SELECT 语句开始时开始的事务更改的任何数据的先前版本(快照)。不过,这并不是完全免费的。成本是tempDB
中的活动增加。 [READ COMMITTED SNAPSHOT 模式下的触发器也可能出现问题。]
【讨论】:
我同意这些都是可能的解释和对死锁的好建议。但是我仍然不明白如果隔离级别是读取提交的,为什么会发生 RangeX-X 锁。我想了解为什么我们对一系列键而不是单个键进行排他锁。【参考方案3】:对此的另一个常见解释可能与 IGNORE_DUP_KEY 选项设置为 ON 的 UNIQUE 索引有关。
From BOL - 此选项指定当插入操作尝试将重复键值插入唯一索引时的错误响应。 IGNORE_DUP_KEY 选项仅适用于创建或重建索引后的插入操作。该选项在执行 CREATE INDEX、ALTER INDEX 或 UPDATE 时无效。默认为关闭。
开启 将重复的键值插入唯一索引时会出现警告消息。只有违反唯一性约束的行才会失败。
关闭 将重复的键值插入唯一索引时会出现错误消息。整个 INSERT 操作将被回滚。
他们在这里没有提到的是,当启用此选项时,在 INSERTS 期间会强制执行 SERIALIZABLE 隔离。我个人还没有掌握这样做的内在要求,因为一些插入的行可能会被丢弃,仅此而已,但事实就是如此。让 SQL 开发团队在这里插话……
这种行为很容易证明;
首先创建一个带有典型 PK 的新表:
CREATE TABLE [dbo].[Test_RC_TIL_RangeLocks](
[RID] [int] IDENTITY(1,1) NOT NULL,
[Col1] [int] NOT NULL,
[Col2] [int] NOT NULL,
[Col3] [int] NOT NULL
) ON [PRIMARY]
接下来我们要在 IGNORE_DUP_KEY ON 的情况下在 Col1 和 Col2 上添加唯一索引:
CREATE UNIQUE NONCLUSTERED INDEX [UIX_Test_RC_TIL_RangeLocks] ON [dbo].[Test_RC_TIL_RangeLocks](
[Col1] ASC,
[Col2] ASC
)WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = ON, --<<**THE OFFENDER**>>
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
接下来我们将添加 5 行,看看会发生什么...
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
DECLARE @C int
SELECT @C=5
BEGIN TRANSACTION
WHILE @C>0
BEGIN
INSERT Test_RC_TIL_RangeLocks(Col1,Col2,Col3)
VALUES (@C,@C+1,2*@C + 100)
SET @C=@C-1
END
SELECT * FROM Test_RC_TIL_RangeLocks
EXEC sp_lock @@SPID
COMMIT
正如所料,我们添加了五行:
RID Col1 Col2 Col3
1 5 6 110
2 4 5 108
3 3 4 106
4 2 3 104
5 1 2 102
我们感兴趣的锁是:
sid ObjId IndId Type Resource Mode
53 0 0 DB S
53 0 0 DB S
53 402100473 1 KEY (8194443284a0) X
53 402100473 2 KEY (550e0a2a4b96) RangeX-X
53 402100473 2 KEY (ffffffffffff) RangeS-U
53 1131151075 0 TAB IS
53 402100473 1 PAG 1:744 IX
53 402100473 2 PAG 1:748 IX
53 402100473 1 KEY (98ec012aa510) X
53 402100473 1 KEY (a0c936a3c965) X
53 402100473 2 KEY (ec04ac4bee1f) RangeX-X
53 402100473 0 TAB IX
53 402100473 2 KEY (0207a0a08e23) RangeX-X
53 402100473 2 KEY (7112ec63c430) RangeX-X
53 402100473 1 KEY (59855d342c69) X
53 402100473 1 KEY (61a06abd401c) X
53 338100245 0 TAB IX
啊,在简单插入的 READ COMMITTED 隔离级别下调用的可怕的 SERIALIZABLE 键范围锁!
所以当 BOL 说; 在发生键范围锁定之前,必须满足以下条件:
• 事务隔离级别必须设置为 SERIALIZABLE。
请记住,这并不总是正确的......
附言作为设计说明,盲目地消除可能具有唯一行值的重复键通常是不好的做法。更好的做法是确保您没有尝试在 INSERT 语句中插入重复的键...
干杯...
【讨论】:
【参考方案4】:根据 SQL Docs,不知何故,您的事务(或其他事务)运行在可序列化级别。
键范围锁定文档here。第一句话是:
键范围锁在使用 serializable 事务隔离级别时保护隐式包含在由 Transact-SQL 语句读取的记录集中的一系列行。 p>
【讨论】:
请提供相关文档的链接。不确定这是否会添加任何相关值作为答案。 按要求添加了指向 TechNet 文档的链接。我不确定这是操作问题。但是 key-range 锁往往是在 Serializable Isolation 级别引起的,因此值得一提和研究。以上是关于SQL Server 2005:读取提交事务隔离级别中的键范围锁?的主要内容,如果未能解决你的问题,请参考以下文章
mysql,oracle,sql server中的默认事务隔离级别查看,更改