我有一个带有“SelfRef”表的数据库。SelfRef 有两个字段:
Id (guid, PK, not null)
SelfRefId (guid, nullable)
Run Code Online (Sandbox Code Playgroud)
有一个外键约束将 SelfRefId 字段映射回 Id 字段。
我有一个引用数据库的 EntityFrameworkCore 项目。我正在运行以下测试:
我发现第 2 步经常导致死锁。我不明白为什么应该这样做。
我在下面展示了我的代码,但我怀疑这个问题是特定于这段代码的:
public class TestSelfRefDeadlock
{
private async Task CreateSelfRef_ThenDelete_Deletes() {
var sr = new SelfRef
{
Id = Guid.NewGuid(),
Name = "SR"
};
var factory = new SelfRefDbFactory();
using (var db = factory.Create()) {
db.Add(sr);
await db.SaveChangesAsync(); // EDIT: Changing this to db.SaveChanges() appears to fix the problem, at least in this test scenario.
}
using (var db = factory.Create()) {
db.SelfRef.Remove(sr);
await db.SaveChangesAsync();
}
}
private IEnumerable<Task> DeadlockTasks() {
for (int i=0; i<2; i++) {
yield return CreateSelfRef_ThenDelete_Deletes();
}
}
[Fact]
public async Task LotsaDeletes_DoNotDeadlock()
=> await Task.WhenAll(DeadlockTasks());
}
Run Code Online (Sandbox Code Playgroud)
编辑:我已经确认在 EF6 中发生了同样的死锁。
在我的数据库中创建表:
USE [SelfReferential]
GO
/****** Object: Table [dbo].[SelfRef] Script Date: 3/20/2018 3:43:50 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SelfRef](
[Id] [uniqueidentifier] NOT NULL,
[SelfReferentialId] [uniqueidentifier] NULL,
[Name] [nchar](10) NULL,
CONSTRAINT [PK_SelfRef] PRIMARY KEY CLUSTERED
(
[Id] 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
ALTER TABLE [dbo].[SelfRef] WITH CHECK ADD CONSTRAINT [FK_SelfRef_SelfRef] FOREIGN KEY([SelfReferentialId])
REFERENCES [dbo].[SelfRef] ([Id])
GO
ALTER TABLE [dbo].[SelfRef] CHECK CONSTRAINT [FK_SelfRef_SelfRef]
GO
Run Code Online (Sandbox Code Playgroud)
生成实体:
Scaffold-DbContext "Server=localhost;Database=SelfReferential;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -Context SelfRefDb -OutputDir Entities -Force
Run Code Online (Sandbox Code Playgroud)
DbFactory 类:
public class SelfRefDbFactory : IFactory<SelfRefDb>
{
private const string str1 = @"Data Source=MyPcName;Initial Catalog=SelfReferential;Integrated Security=True;ApplicationIntent=ReadWrite;";
private const string str2 = @"Data Source=MyPcName;Initial Catalog=SelfReferential;Integrated Security=True;ApplicationIntent=ReadWrite;MultipleActiveResultSets=True";
public SelfRefDb Create() {
var options = new DbContextOptionsBuilder<SelfRefDb>()
.UseSqlServer(str1).Options;
return new SelfRefDb(options);
}
}
Run Code Online (Sandbox Code Playgroud)
错误信息:
Message: System.InvalidOperationException : An exception has been raised that is likely due to a transient failure. If you are connecting to a SQL Azure database consider using SqlAzureExecutionStrategy.
---- Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
-------- System.Data.SqlClient.SqlException : Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Run Code Online (Sandbox Code Playgroud)
下面是分析器中的一些 Sql 事件。我跳过了许多“审核登录”和“审核注销”事件,并且这些字段被一一复制。一定有更好的方法来提取东西,但我不知道它是什么。
exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [SelfRef] ([Id], [Name], [SelfReferentialId])
VALUES (@p0, @p1, @p2);
',N'@p0 uniqueidentifier,@p1 nvarchar(10),@p2
uniqueidentifier',@p0='93671E2E-28E5-414D-A3DB-239FA433640C',@p1=N'SR',@p2=NULL
Run Code Online (Sandbox Code Playgroud)
这个特殊的运行有两个线程。在上述两个事件之后,我看到了两个:
exec sp_reset_connection
Run Code Online (Sandbox Code Playgroud)
然后两个像这样:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 uniqueidentifier',@p0='F5B53458-08C5-485E-8364-2A2842E95158'
Run Code Online (Sandbox Code Playgroud)
再重新连接两次,就大功告成了。
死锁xml:
<deadlock-list>
<deadlock victim="process1fe3db6b468">
<process-list>
<process id="process1fe3db6b468" taskpriority="0" logused="300" waitresource="KEY: 14:72057594041401344 (427c492d0b23)" waittime="147" ownerId="218910" transactionname="user_transaction" lasttranstarted="2018-03-22T14:33:57.880" XDES="0x2021f8bc408" lockMode="S" schedulerid="2" kpid="8540" status="suspended" spid="53" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-03-22T14:33:57.883" lastbatchcompleted="2018-03-22T14:33:57.880" lastattention="1900-01-01T00:00:00.880" clientapp=".Net SqlClient Data Provider" hostname="WILLIAMASUS" hostpid="14656" loginname="MicrosoftAccount\jockusch@gmail.com" isolationlevel="read committed (2)" xactid="218910" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="2" stmtstart="78" stmtend="154" sqlhandle="0x0200000087849c297464e5637211740e8fde989bf9ffc37a0000000000000000000000000000000000000000">
unknown </frame>
<frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@p0 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;
</inputbuf>
</process>
<process id="process1fe3db684e8" taskpriority="0" logused="300" waitresource="KEY: 14:72057594041401344 (8e30f77e2707)" waittime="146" ownerId="218908" transactionname="user_transaction" lasttranstarted="2018-03-22T14:33:57.880" XDES="0x20227f6b458" lockMode="S" schedulerid="1" kpid="8300" status="suspended" spid="54" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-03-22T14:33:57.883" lastbatchcompleted="2018-03-22T14:33:57.880" lastattention="1900-01-01T00:00:00.880" clientapp=".Net SqlClient Data Provider" hostname="WILLIAMASUS" hostpid="14656" loginname="MicrosoftAccount\jockusch@gmail.com" isolationlevel="read committed (2)" xactid="218908" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
<executionStack>
<frame procname="adhoc" line="2" stmtstart="78" stmtend="154" sqlhandle="0x0200000087849c297464e5637211740e8fde989bf9ffc37a0000000000000000000000000000000000000000">
unknown </frame>
<frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@p0 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;
</inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594041401344" dbid="14" objectname="SelfReferential.dbo.SelfRef" indexname="PK_SelfRef" id="lock20229074e00" mode="X" associatedObjectId="72057594041401344">
<owner-list>
<owner id="process1fe3db684e8" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process1fe3db6b468" mode="S" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594041401344" dbid="14" objectname="SelfReferential.dbo.SelfRef" indexname="PK_SelfRef" id="lock20229073c80" mode="X" associatedObjectId="72057594041401344">
<owner-list>
<owner id="process1fe3db6b468" mode="X"/>
</owner-list>
<waiter-list>
<waiter id="process1fe3db684e8" mode="S" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
Run Code Online (Sandbox Code Playgroud)
死锁 XML 表明两个会话正在争夺两个不同的行,每个会话在其中一个行上具有 e(X) 独占锁,并在另一行上请求 (S) 共享锁。
鉴于:
hostpid
)trancount="2"
两个会话都显示死锁 XML有可能:
现在,连接池的东西(数字 2)通常不会引起任何问题,但由于在某些情况下它会引起任何问题(例如,如果正在使用分布式事务),我不想排除它。而且,由于我不知道 EF 和/或异步选项如何处理事情,所以它很可能是异步和连接池的组合。
那么,为什么不首先尝试保留步骤 1 的异步保存,而是通过添加Pooling=false;
到连接字符串来禁用连接池。
当然,无论禁用连接池是否有帮助,考虑到在保存时不使用异步可以解决问题(或者至少到目前为止似乎如此),您应该考虑在创建项目时不使用异步。也许只将其用于删除和选择?即使我们确定在第 1 步中使用和不使用异步之间行为的确切变化,也可能无法解决任何问题(或者至少在不做可能不应该做的事情的情况下无法解决)。