「SelfRef」というテーブルのあるデータベースがあります。 SelfRefには2つのフィールドがあります。
Id (guid, PK, not null)
SelfRefId (guid, nullable)
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());
}
編集: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
エンティティを生成するには:
Scaffold-DbContext "Server=localhost;Database=SelfReferential;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -Context SelfRefDb -OutputDir Entities -Force
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);
}
}
エラーメッセージ:
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.
プロファイラーからのSQLイベントの一部を以下に示します。多数の「監査ログイン」および「監査ログアウト」イベントをスキップしていますが、フィールドが1つずつコピーされます。何かを抽出するためのより良い方法があるはずですが、それが何であるかはわかりません。
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
この特定の実行は2つのスレッドで行われました。上記のような2つのイベントの後、私は次の2つを見ました。
exec sp_reset_connection
次に、このような2つ:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 uniqueidentifier',@p0='F5B53458-08C5-485E-8364-2A2842E95158'
さらに2つの接続がリセットされ、それが行われました。
デッドロック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\[email protected]" 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\[email protected]" 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>
デッドロックXMLは、2つのセッションが2つの異なる行をめぐって争っており、各セッションが一方にe(X)clusiveロックを持ち、もう一方に(S)haredロックを要求していることを示しています。
与えられた:
hostpid
の値による)trancount="2"
を示しているため、EFがバックグラウンドで実行している可能性がありますが、トランザクションが使用されていることを示すものはありません。次のいずれかが可能です。
現在、接続プーリング(番号2)は一般にで問題が発生することはありませんが、それが可能なシナリオがあるため(分散トランザクションの場合など)使用されている)、私はそれを除外したくありませんでした。そして、EFおよび/または非同期オプションがどのように処理するのか私にはわからないので、それは非同期プールと接続プールの組み合わせである可能性が非常に高いです。
では、最初にステップ1の非同期保存を維持してみてください。ただし、接続文字列にPooling=false;
を追加して、接続プールを無効にしてください。
もちろん、保存時に非同期を使用するnotとすると、接続プールを無効にすることが役立つかどうかにかかわらず、問題は解決されます(または、少なくともこれまでのところ表示されます)。アイテムを作成するときは、非同期を使用しないことを検討する必要があります。おそらくそれを削除と選択にのみ使用しますか?手順1でasyncを使用する場合と使用しない場合の動作の正確な変化を特定したとしても、回避できるものではない可能性があります(または、少なくとも実行すべきでないことを行わないと回避できません)。