SQL 索引范围锁引起的服务器死锁
SQL Server deadlock caused by range locks in index
我遇到这样一种情况,即从 C# ASP.NET Web API 将新记录插入到 SQL 服务器数据库 table 时,在遇到多线程时会导致死锁。这是由初始 SELECT
获取共享范围锁 (RangeS-S) 引起的,然后当 INSERT
发生时它被转换为 RangeI-N。
简化和匿名的死锁 XML 如下所示。
<deadlock-list>
<deadlock victim="process19d91c28">
<process-list>
<process id="process19d91c28" taskpriority="0" logused="372" waitresource="KEY: 6:72057594044416000 (ffffffffffff)" waittime="3914" ownerId="1072531" transactionname="user_transaction" lasttranstarted="2015-03-27T15:41:26.670" XDES="0x5faad0d0" lockMode="RangeI-N" schedulerid="3" kpid="4300" status="suspended" spid="58" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-03-27T15:41:26.693" lastbatchcompleted="2015-03-27T15:41:26.693" lastattention="1900-01-01T00:00:00.693" clientapp=".Net SqlClient Data Provider" hostname="MYHOST" hostpid="17300" loginname="MYDOMAIN\XYZ_DB_DEV" isolationlevel="serializable (4)" xactid="1072531" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="XYZ_Local.dbo.Article_Insert" line="20" stmtstart="1100" stmtend="2372" sqlhandle="0x0300060049c6306a7d37c200b0a3000001000000000000000000000000000000000000000000000000000000">
INSERT INTO Article (
-- ABREVIATED
) VALUES (
-- ABREVIATED
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 6 Object Id = 1781581385]
</inputbuf>
</process>
<process id="process3bd3f468" taskpriority="0" logused="372" waitresource="KEY: 6:72057594044416000 (ffffffffffff)" waittime="3916" ownerId="1072527" transactionname="user_transaction" lasttranstarted="2015-03-27T15:41:26.663" XDES="0x5faa73f0" lockMode="RangeI-N" schedulerid="2" kpid="3240" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-03-27T15:41:26.690" lastbatchcompleted="2015-03-27T15:41:26.690" lastattention="1900-01-01T00:00:00.690" clientapp=".Net SqlClient Data Provider" hostname="MYHOST" hostpid="17300" loginname="MYDOMAIN\XYZ_DB_DEV" isolationlevel="serializable (4)" xactid="1072527" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="XYZ_Local.dbo.Article_Insert" line="20" stmtstart="1100" stmtend="2372" sqlhandle="0x0300060049c6306a7d37c200b0a3000001000000000000000000000000000000000000000000000000000000">
INSERT INTO Article (
-- ABREVIATED
) VALUES (
-- ABREVIATED
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 6 Object Id = 1781581385]
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594044416000" dbid="6" objectname="XYZ_Local.dbo.Article" indexname="IX_Article_THEINDEX" id="lock43227700" mode="RangeS-S" associatedObjectId="72057594044416000">
<owner-list>
<owner id="process3bd3f468" mode="RangeS-S"/>
<owner id="process3bd3f468" mode="RangeI-N" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process19d91c28" mode="RangeI-N" requestType="convert"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594044416000" dbid="6" objectname="XYZ_Local.dbo.Article" indexname="IX_Article_THEINDEX" id="lock43227700" mode="RangeS-S" associatedObjectId="72057594044416000">
<owner-list>
<owner id="process19d91c28" mode="RangeS-S"/>
<owner id="process19d91c28" mode="RangeI-N" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process3bd3f468" mode="RangeI-N" requestType="convert"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
将 WITH (UPDLOCK)
添加到初始 SELECT
'solves' 死锁,但它不是很漂亮和有效,与代码中的 lock() 相同,因为它阻塞了所有其他线程在交易期间。
将 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
添加到 Article_Insert SPROC 似乎也解决了死锁。
我从这个 XML 看到隔离级别是 Serializable
,我很困惑这是怎么回事。当我 运行 这个代码时
SELECT *
FROM [Ecs_Local].[dbo].[Article]
WHERE title ='jcp001'
SELECT CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL
FROM sys.dm_exec_sessions
where session_id = @@SPID
响应为 ReadCommitted
,因此 SQL 服务器实例的默认值为 ReadCommitted
。
当我在 C# 代码中构建事务时,我使用了一个实用程序来执行此操作
_transaction = _connection.BeginTransaction(IsolationLevel.Serializable);
我假设这会将整个事务的默认隔离级别设置为可序列化。
我已经研究过重构代码以在事务之外执行初始 select,但这不是直截了当的,也不一定能解决可能也遇到此问题的代码的其他区域。
我想了解的是解决此问题的最佳方法是什么:
- 更改在代码中构建交易的方式,以便它们使用
READ COMMITTED
代替(这将适用于我的应用程序中的每个交易)以及潜在的问题是什么(我不明白幻读问题)
- 将 WITH (UPDLOCK) 添加到单个 SPROCS 以提早阻止并确保以后不需要锁定转换
- 将执行 INSERTS/UPDATES 的单个 SPROCS 中的隔离级别更改为
REPEATABLE READ
编辑 1
根据 Bogdan 的要求,这里是匿名执行计划
<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="12.0.2000.8" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple StatementCompId="1" StatementId="1" StatementText="Article_Insert" StatementType="EXECUTE PROC" RetrievedFromCache="false" />
</Statements>
<Statements>
<StmtSimple>
<StoredProc ProcName="Article_Insert">
<Statements>
<StmtSimple StatementCompId="2" StatementId="2" StatementText="CREATE PROCEDURE [dbo].[Article_Insert]
 @CurrentUser NVARCHAR(1000),
 @xyzJDocId BIGINT,
 @xyzZDocId BIGINT,
 @ZDocNumber NVARCHAR(255) = NULL,
 @InstutionType NVARCHAR(255),
 @InstutionName NVARCHAR(255) = NULL,
 @QWStatus NVARCHAR(255) = NULL,
 @Doi NVARCHAR(32) = NULL,
 @Title NVARCHAR(4000),
 @ArticleType NVARCHAR(255),
 @Active BIT,
 @Exempt BIT,
 @NewDocRequired BIT
AS
BEGIN
 -- ThirdPartyContent Abc -> xyz only
 -- ConflictOfInterest Abc -> xyz only

 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

 " StatementType="SET TRANSACTION ISOLATION LEVEL" RetrievedFromCache="true" />
<StmtSimple StatementCompId="3" StatementEstRows="1" StatementId="3" StatementOptmLevel="TRIVIAL" CardinalityEstimationModelVersion="120" StatementSubTreeCost="0.0300044" StatementText="INSERT INTO Article (
 [CreatedDate], 
 [LastModifiedBy],
 [xyzJDocId], 
 [xyzZDocId],
 [ZDocNumber],
 [InstutionType],
 [InstutionName],
 [QWStatus],
 [Doi],
 [Title],
 [ArticleType],
 [Active],
 [Exempt],
 [NewDocRequired]
 ) VALUES (
 GETUTCDATE(),
 @CurrentUser,
 @xyzJDocId,
 @xyzZDocId,
 @ZDocNumber,
 @InstutionType,
 @InstutionName,
 @QWStatus,
 @Doi,
 @Title,
 @ArticleType,
 @Active,
 @Exempt,
 @NewDocRequired
 )" StatementType="INSERT" QueryHash="0x108916B56B42574E" QueryPlanHash="0x73B019D9C6F1A1C5" RetrievedFromCache="true">
<StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
<QueryPlan CachedPlanSize="32" CompileTime="1" CompileCPU="1" CompileMemory="176">
<MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" />
<OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="38400" EstimatedPagesCached="9600" EstimatedAvailableDegreeOfParallelism="2" />
<RelOp AvgRowSize="9" EstimateCPU="3E-06" EstimateIO="0.03" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Insert" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Insert" EstimatedTotalSubtreeCost="0.0300044">
<OutputList />
<Update DMLRequestSort="false">
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[PK__tmp_ms_x__3214EC071788A4DB]" IndexKind="Clustered" Storage="RowStore" />
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[IX_Article_THEINDEX]" IndexKind="NonClustered" Storage="RowStore" />
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[IX_Article_OTHERINDEX]" IndexKind="NonClustered" Storage="RowStore" />
<SetPredicate>
<ScalarOperator ScalarString="[XYZ_Local].[dbo].[Article].[CreatedDate] = RaiseIfNullInsert([Expr1003]),[XYZ_Local].[dbo].[Article].[LastModifiedBy] = RaiseIfNullInsert([@CurrentUser]),[XYZ_Local].[dbo].[Article].[xyzJDocId] = RaiseIfNullInsert([@xyzJDocId]),[XYZ_Local].[dbo].[Article].[xyzZDocId] = RaiseIfNullInsert([@xyzZDocId]),[XYZ_Local].[dbo].[Article].[ZDocNumber] = [@ZDocNumber],[XYZ_Local].[dbo].[Article].[InstutionType] = [@InstutionType],[XYZ_Local].[dbo].[Article].[InstutionName] = [@InstutionName],[XYZ_Local].[dbo].[Article].[QWStatus] = [@QWStatus],[XYZ_Local].[dbo].[Article].[Doi] = [@Doi],[XYZ_Local].[dbo].[Article].[Title] = RaiseIfNullInsert([@Title]),[XYZ_Local].[dbo].[Article].[ArticleType] = RaiseIfNullInsert([@ArticleType]),[XYZ_Local].[dbo].[Article].[Active] = RaiseIfNullInsert([@Active]),[XYZ_Local].[dbo].[Article].[Exempt] = RaiseIfNullInsert([@Exempt]),[XYZ_Local].[dbo].[Article].[NewDocRequired] = RaiseIfNullInsert([@NewDocRequired]),[XYZ_Local].[dbo].[Article].[Id] = [Expr1002],[XYZ_Local].[dbo].[Article].[ThirdPartyContent] = NULL,[XYZ_Local].[dbo].[Article].[ConflictOfInterest] = NULL">
<ScalarExpressionList>
<ScalarOperator>
<MultipleAssign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="CreatedDate" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="Expr1003" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="LastModifiedBy" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@CurrentUser" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="xyzJDocId" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@xyzJDocId" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="xyzZDocId" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@xyzZDocId" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ZDocNumber" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@ZDocNumber" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="InstutionType" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@InstutionType" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="InstutionName" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@InstutionName" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="QWStatus" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@QWStatus" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Doi" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Doi" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Title" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Title" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ArticleType" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@ArticleType" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Active" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Active" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Exempt" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Exempt" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="NewDocRequired" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@NewDocRequired" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Id" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="Expr1002" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ThirdPartyContent" />
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ConflictOfInterest" />
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Assign>
</MultipleAssign>
</ScalarOperator>
</ScalarExpressionList>
</ScalarOperator>
</SetPredicate>
<RelOp AvgRowSize="19" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="1" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="1.357E-06">
<OutputList>
<ColumnReference Column="Expr1002" />
<ColumnReference Column="Expr1003" />
</OutputList>
<ComputeScalar>
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1003" />
<ScalarOperator ScalarString="getutcdate()">
<Identifier>
<ColumnReference Column="ConstExpr1004">
<ScalarOperator>
<Intrinsic FunctionName="getutcdate" />
</ScalarOperator>
</ColumnReference>
</Identifier>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="11" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="2" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="1.257E-06">
<OutputList>
<ColumnReference Column="Expr1002" />
</OutputList>
<ComputeScalar ComputeSequence="true">
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="getidentity((880722190),(6),NULL)">
<Intrinsic FunctionName="getidentity">
<ScalarOperator>
<Const ConstValue="(880722190)" />
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="(6)" />
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="9" EstimateCPU="1.157E-06" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Constant Scan" NodeId="3" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="1.157E-06">
<OutputList />
<ConstantScan />
</RelOp>
</ComputeScalar>
</RelOp>
</ComputeScalar>
</RelOp>
</Update>
</RelOp>
<ParameterList>
<ColumnReference Column="@NewDocRequired" ParameterCompiledValue="(0)" />
<ColumnReference Column="@Exempt" ParameterCompiledValue="(0)" />
<ColumnReference Column="@Active" ParameterCompiledValue="(1)" />
<ColumnReference Column="@ArticleType" ParameterCompiledValue="N'Article'" />
<ColumnReference Column="@Title" ParameterCompiledValue="N'jcp001'" />
<ColumnReference Column="@Doi" ParameterCompiledValue="NULL" />
<ColumnReference Column="@QWStatus" ParameterCompiledValue="NULL" />
<ColumnReference Column="@InstutionName" ParameterCompiledValue="NULL" />
<ColumnReference Column="@InstutionType" ParameterCompiledValue="N'Cogent'" />
<ColumnReference Column="@ZDocNumber" ParameterCompiledValue="NULL" />
<ColumnReference Column="@xyzZDocId" ParameterCompiledValue="(2015032810025104)" />
<ColumnReference Column="@xyzJDocId" ParameterCompiledValue="(852)" />
<ColumnReference Column="@CurrentUser" ParameterCompiledValue="N'Abc\Api'" />
</ParameterList>
</QueryPlan>
</StmtSimple>
<StmtSimple StatementCompId="4" StatementId="4" StatementText="

 SELECT SCOPE_IDENTITY() AS [ArticleId]" StatementType="SELECT WITHOUT QUERY" RetrievedFromCache="true" />
</Statements>
</StoredProc>
</StmtSimple>
</Statements>
</Batch>
</BatchSequence>
由于可序列化的隔离级别,S 锁被 select 获取。 Serializable 的意思是"as if serial execution",在SQL Server 中,要求所有读取的数据都是稳定的。
您似乎不需要它,因此您可能应该降低隔离级别。
快照隔离级别通常非常适合许多应用。通过对读取数据进行零锁定并且不会阻塞现有锁,您可以获得完美的读取一致性(这里说的是行锁 - 请将此声明作为近似信息)。
数字 3(使用可重复读取)没有帮助,因为您无法减少 DML 占用的锁。隔离级别(几乎)没有影响。
请告诉我您的想法。回答诸如此类的问题总是很困难,因为 "best" 解决方案在很大程度上取决于具体情况。如果适用,我总是推荐 SI,因为它是广泛问题的完整解决方案。
我遇到这样一种情况,即从 C# ASP.NET Web API 将新记录插入到 SQL 服务器数据库 table 时,在遇到多线程时会导致死锁。这是由初始 SELECT
获取共享范围锁 (RangeS-S) 引起的,然后当 INSERT
发生时它被转换为 RangeI-N。
简化和匿名的死锁 XML 如下所示。
<deadlock-list>
<deadlock victim="process19d91c28">
<process-list>
<process id="process19d91c28" taskpriority="0" logused="372" waitresource="KEY: 6:72057594044416000 (ffffffffffff)" waittime="3914" ownerId="1072531" transactionname="user_transaction" lasttranstarted="2015-03-27T15:41:26.670" XDES="0x5faad0d0" lockMode="RangeI-N" schedulerid="3" kpid="4300" status="suspended" spid="58" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-03-27T15:41:26.693" lastbatchcompleted="2015-03-27T15:41:26.693" lastattention="1900-01-01T00:00:00.693" clientapp=".Net SqlClient Data Provider" hostname="MYHOST" hostpid="17300" loginname="MYDOMAIN\XYZ_DB_DEV" isolationlevel="serializable (4)" xactid="1072531" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="XYZ_Local.dbo.Article_Insert" line="20" stmtstart="1100" stmtend="2372" sqlhandle="0x0300060049c6306a7d37c200b0a3000001000000000000000000000000000000000000000000000000000000">
INSERT INTO Article (
-- ABREVIATED
) VALUES (
-- ABREVIATED
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 6 Object Id = 1781581385]
</inputbuf>
</process>
<process id="process3bd3f468" taskpriority="0" logused="372" waitresource="KEY: 6:72057594044416000 (ffffffffffff)" waittime="3916" ownerId="1072527" transactionname="user_transaction" lasttranstarted="2015-03-27T15:41:26.663" XDES="0x5faa73f0" lockMode="RangeI-N" schedulerid="2" kpid="3240" status="suspended" spid="59" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2015-03-27T15:41:26.690" lastbatchcompleted="2015-03-27T15:41:26.690" lastattention="1900-01-01T00:00:00.690" clientapp=".Net SqlClient Data Provider" hostname="MYHOST" hostpid="17300" loginname="MYDOMAIN\XYZ_DB_DEV" isolationlevel="serializable (4)" xactid="1072527" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="XYZ_Local.dbo.Article_Insert" line="20" stmtstart="1100" stmtend="2372" sqlhandle="0x0300060049c6306a7d37c200b0a3000001000000000000000000000000000000000000000000000000000000">
INSERT INTO Article (
-- ABREVIATED
) VALUES (
-- ABREVIATED
</frame>
</executionStack>
<inputbuf>
Proc [Database Id = 6 Object Id = 1781581385]
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594044416000" dbid="6" objectname="XYZ_Local.dbo.Article" indexname="IX_Article_THEINDEX" id="lock43227700" mode="RangeS-S" associatedObjectId="72057594044416000">
<owner-list>
<owner id="process3bd3f468" mode="RangeS-S"/>
<owner id="process3bd3f468" mode="RangeI-N" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process19d91c28" mode="RangeI-N" requestType="convert"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594044416000" dbid="6" objectname="XYZ_Local.dbo.Article" indexname="IX_Article_THEINDEX" id="lock43227700" mode="RangeS-S" associatedObjectId="72057594044416000">
<owner-list>
<owner id="process19d91c28" mode="RangeS-S"/>
<owner id="process19d91c28" mode="RangeI-N" requestType="convert"/>
</owner-list>
<waiter-list>
<waiter id="process3bd3f468" mode="RangeI-N" requestType="convert"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
将 WITH (UPDLOCK)
添加到初始 SELECT
'solves' 死锁,但它不是很漂亮和有效,与代码中的 lock() 相同,因为它阻塞了所有其他线程在交易期间。
将 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
添加到 Article_Insert SPROC 似乎也解决了死锁。
我从这个 XML 看到隔离级别是 Serializable
,我很困惑这是怎么回事。当我 运行 这个代码时
SELECT *
FROM [Ecs_Local].[dbo].[Article]
WHERE title ='jcp001'
SELECT CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot' END AS TRANSACTION_ISOLATION_LEVEL
FROM sys.dm_exec_sessions
where session_id = @@SPID
响应为 ReadCommitted
,因此 SQL 服务器实例的默认值为 ReadCommitted
。
当我在 C# 代码中构建事务时,我使用了一个实用程序来执行此操作
_transaction = _connection.BeginTransaction(IsolationLevel.Serializable);
我假设这会将整个事务的默认隔离级别设置为可序列化。
我已经研究过重构代码以在事务之外执行初始 select,但这不是直截了当的,也不一定能解决可能也遇到此问题的代码的其他区域。
我想了解的是解决此问题的最佳方法是什么:
- 更改在代码中构建交易的方式,以便它们使用
READ COMMITTED
代替(这将适用于我的应用程序中的每个交易)以及潜在的问题是什么(我不明白幻读问题) - 将 WITH (UPDLOCK) 添加到单个 SPROCS 以提早阻止并确保以后不需要锁定转换
- 将执行 INSERTS/UPDATES 的单个 SPROCS 中的隔离级别更改为
REPEATABLE READ
编辑 1 根据 Bogdan 的要求,这里是匿名执行计划
<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.2" Build="12.0.2000.8" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple StatementCompId="1" StatementId="1" StatementText="Article_Insert" StatementType="EXECUTE PROC" RetrievedFromCache="false" />
</Statements>
<Statements>
<StmtSimple>
<StoredProc ProcName="Article_Insert">
<Statements>
<StmtSimple StatementCompId="2" StatementId="2" StatementText="CREATE PROCEDURE [dbo].[Article_Insert]
 @CurrentUser NVARCHAR(1000),
 @xyzJDocId BIGINT,
 @xyzZDocId BIGINT,
 @ZDocNumber NVARCHAR(255) = NULL,
 @InstutionType NVARCHAR(255),
 @InstutionName NVARCHAR(255) = NULL,
 @QWStatus NVARCHAR(255) = NULL,
 @Doi NVARCHAR(32) = NULL,
 @Title NVARCHAR(4000),
 @ArticleType NVARCHAR(255),
 @Active BIT,
 @Exempt BIT,
 @NewDocRequired BIT
AS
BEGIN
 -- ThirdPartyContent Abc -> xyz only
 -- ConflictOfInterest Abc -> xyz only

 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

 " StatementType="SET TRANSACTION ISOLATION LEVEL" RetrievedFromCache="true" />
<StmtSimple StatementCompId="3" StatementEstRows="1" StatementId="3" StatementOptmLevel="TRIVIAL" CardinalityEstimationModelVersion="120" StatementSubTreeCost="0.0300044" StatementText="INSERT INTO Article (
 [CreatedDate], 
 [LastModifiedBy],
 [xyzJDocId], 
 [xyzZDocId],
 [ZDocNumber],
 [InstutionType],
 [InstutionName],
 [QWStatus],
 [Doi],
 [Title],
 [ArticleType],
 [Active],
 [Exempt],
 [NewDocRequired]
 ) VALUES (
 GETUTCDATE(),
 @CurrentUser,
 @xyzJDocId,
 @xyzZDocId,
 @ZDocNumber,
 @InstutionType,
 @InstutionName,
 @QWStatus,
 @Doi,
 @Title,
 @ArticleType,
 @Active,
 @Exempt,
 @NewDocRequired
 )" StatementType="INSERT" QueryHash="0x108916B56B42574E" QueryPlanHash="0x73B019D9C6F1A1C5" RetrievedFromCache="true">
<StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
<QueryPlan CachedPlanSize="32" CompileTime="1" CompileCPU="1" CompileMemory="176">
<MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" />
<OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="38400" EstimatedPagesCached="9600" EstimatedAvailableDegreeOfParallelism="2" />
<RelOp AvgRowSize="9" EstimateCPU="3E-06" EstimateIO="0.03" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Insert" NodeId="0" Parallel="false" PhysicalOp="Clustered Index Insert" EstimatedTotalSubtreeCost="0.0300044">
<OutputList />
<Update DMLRequestSort="false">
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[PK__tmp_ms_x__3214EC071788A4DB]" IndexKind="Clustered" Storage="RowStore" />
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[IX_Article_THEINDEX]" IndexKind="NonClustered" Storage="RowStore" />
<Object Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Index="[IX_Article_OTHERINDEX]" IndexKind="NonClustered" Storage="RowStore" />
<SetPredicate>
<ScalarOperator ScalarString="[XYZ_Local].[dbo].[Article].[CreatedDate] = RaiseIfNullInsert([Expr1003]),[XYZ_Local].[dbo].[Article].[LastModifiedBy] = RaiseIfNullInsert([@CurrentUser]),[XYZ_Local].[dbo].[Article].[xyzJDocId] = RaiseIfNullInsert([@xyzJDocId]),[XYZ_Local].[dbo].[Article].[xyzZDocId] = RaiseIfNullInsert([@xyzZDocId]),[XYZ_Local].[dbo].[Article].[ZDocNumber] = [@ZDocNumber],[XYZ_Local].[dbo].[Article].[InstutionType] = [@InstutionType],[XYZ_Local].[dbo].[Article].[InstutionName] = [@InstutionName],[XYZ_Local].[dbo].[Article].[QWStatus] = [@QWStatus],[XYZ_Local].[dbo].[Article].[Doi] = [@Doi],[XYZ_Local].[dbo].[Article].[Title] = RaiseIfNullInsert([@Title]),[XYZ_Local].[dbo].[Article].[ArticleType] = RaiseIfNullInsert([@ArticleType]),[XYZ_Local].[dbo].[Article].[Active] = RaiseIfNullInsert([@Active]),[XYZ_Local].[dbo].[Article].[Exempt] = RaiseIfNullInsert([@Exempt]),[XYZ_Local].[dbo].[Article].[NewDocRequired] = RaiseIfNullInsert([@NewDocRequired]),[XYZ_Local].[dbo].[Article].[Id] = [Expr1002],[XYZ_Local].[dbo].[Article].[ThirdPartyContent] = NULL,[XYZ_Local].[dbo].[Article].[ConflictOfInterest] = NULL">
<ScalarExpressionList>
<ScalarOperator>
<MultipleAssign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="CreatedDate" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="Expr1003" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="LastModifiedBy" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@CurrentUser" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="xyzJDocId" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@xyzJDocId" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="xyzZDocId" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@xyzZDocId" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ZDocNumber" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@ZDocNumber" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="InstutionType" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@InstutionType" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="InstutionName" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@InstutionName" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="QWStatus" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@QWStatus" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Doi" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Doi" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Title" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Title" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ArticleType" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@ArticleType" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Active" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Active" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Exempt" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@Exempt" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="NewDocRequired" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullInsert">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@NewDocRequired" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="Id" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="Expr1002" />
</Identifier>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ThirdPartyContent" />
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[XYZ_Local]" Schema="[dbo]" Table="[Article]" Column="ConflictOfInterest" />
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Assign>
</MultipleAssign>
</ScalarOperator>
</ScalarExpressionList>
</ScalarOperator>
</SetPredicate>
<RelOp AvgRowSize="19" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="1" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="1.357E-06">
<OutputList>
<ColumnReference Column="Expr1002" />
<ColumnReference Column="Expr1003" />
</OutputList>
<ComputeScalar>
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1003" />
<ScalarOperator ScalarString="getutcdate()">
<Identifier>
<ColumnReference Column="ConstExpr1004">
<ScalarOperator>
<Intrinsic FunctionName="getutcdate" />
</ScalarOperator>
</ColumnReference>
</Identifier>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="11" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="2" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="1.257E-06">
<OutputList>
<ColumnReference Column="Expr1002" />
</OutputList>
<ComputeScalar ComputeSequence="true">
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="getidentity((880722190),(6),NULL)">
<Intrinsic FunctionName="getidentity">
<ScalarOperator>
<Const ConstValue="(880722190)" />
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="(6)" />
</ScalarOperator>
<ScalarOperator>
<Const ConstValue="NULL" />
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="9" EstimateCPU="1.157E-06" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Constant Scan" NodeId="3" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="1.157E-06">
<OutputList />
<ConstantScan />
</RelOp>
</ComputeScalar>
</RelOp>
</ComputeScalar>
</RelOp>
</Update>
</RelOp>
<ParameterList>
<ColumnReference Column="@NewDocRequired" ParameterCompiledValue="(0)" />
<ColumnReference Column="@Exempt" ParameterCompiledValue="(0)" />
<ColumnReference Column="@Active" ParameterCompiledValue="(1)" />
<ColumnReference Column="@ArticleType" ParameterCompiledValue="N'Article'" />
<ColumnReference Column="@Title" ParameterCompiledValue="N'jcp001'" />
<ColumnReference Column="@Doi" ParameterCompiledValue="NULL" />
<ColumnReference Column="@QWStatus" ParameterCompiledValue="NULL" />
<ColumnReference Column="@InstutionName" ParameterCompiledValue="NULL" />
<ColumnReference Column="@InstutionType" ParameterCompiledValue="N'Cogent'" />
<ColumnReference Column="@ZDocNumber" ParameterCompiledValue="NULL" />
<ColumnReference Column="@xyzZDocId" ParameterCompiledValue="(2015032810025104)" />
<ColumnReference Column="@xyzJDocId" ParameterCompiledValue="(852)" />
<ColumnReference Column="@CurrentUser" ParameterCompiledValue="N'Abc\Api'" />
</ParameterList>
</QueryPlan>
</StmtSimple>
<StmtSimple StatementCompId="4" StatementId="4" StatementText="

 SELECT SCOPE_IDENTITY() AS [ArticleId]" StatementType="SELECT WITHOUT QUERY" RetrievedFromCache="true" />
</Statements>
</StoredProc>
</StmtSimple>
</Statements>
</Batch>
</BatchSequence>
由于可序列化的隔离级别,S 锁被 select 获取。 Serializable 的意思是"as if serial execution",在SQL Server 中,要求所有读取的数据都是稳定的。
您似乎不需要它,因此您可能应该降低隔离级别。
快照隔离级别通常非常适合许多应用。通过对读取数据进行零锁定并且不会阻塞现有锁,您可以获得完美的读取一致性(这里说的是行锁 - 请将此声明作为近似信息)。
数字 3(使用可重复读取)没有帮助,因为您无法减少 DML 占用的锁。隔离级别(几乎)没有影响。
请告诉我您的想法。回答诸如此类的问题总是很困难,因为 "best" 解决方案在很大程度上取决于具体情况。如果适用,我总是推荐 SI,因为它是广泛问题的完整解决方案。