Ich habe eine Datenbank mit einer Tabelle "SelfRef". SelfRef hat zwei Felder:
Id (guid, PK, not null)
SelfRefId (guid, nullable)
Es gibt eine Fremdschlüsseleinschränkung, die das SelfRefId-Feld wieder dem ID-Feld zuordnet.
Ich habe ein EntityFrameworkCore-Projekt, das auf die Datenbank verweist. Ich führe den folgenden Test durch:
- Erstellen Sie zwei Einträge in der SelfRef-Tabelle. In jedem Fall ist die SelfRefId null. Änderungen speichern.
- Löschen Sie beide Einträge in separaten, mehr oder weniger gleichzeitigen Aufgaben.
Ich stelle fest, dass Schritt 2 häufig einen Deadlock verursacht. Ich verstehe nicht, warum es sollte.
Ich zeige meinen Code unten, obwohl ich bezweifle, dass das Problem für diesen Code spezifisch ist:
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());
}
EDIT: Ich habe bestätigt, dass der gleiche Deadlock in EF6 auftritt.
So erstellen Sie die Tabelle in meiner Datenbank:
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
So generieren Sie die Entitäten:
Scaffold-DbContext "Server=localhost;Database=SelfReferential;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -Context SelfRefDb -OutputDir Entities -Force
Die DbFactory-Klasse:
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);
}
}
Die Fehlermeldung:
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.
Einige SQL-Ereignisse aus dem Profiler sind unten aufgeführt. Ich überspringe zahlreiche "Audit Login" - und "Audit Logout" -Ereignisse und die Felder werden einzeln kopiert. Es muss einen besseren Weg geben, Dinge zu extrahieren, aber ich weiß nicht, was es ist.
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
Dieser spezielle Lauf war mit zwei Threads. Nach zwei Ereignissen wie dem oben genannten sah ich zwei von:
exec sp_reset_connection
dann zwei wie folgt:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;
',N'@p0 uniqueidentifier',@p0='F5B53458-08C5-485E-8364-2A2842E95158'
Zwei weitere Verbindungsrücksetzungen, dann war es geschafft.
Deadlock 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>
DeadlockTasks
. Es werden viele Aufrufe CreateSelfRef_ThenDelete_Deletes
in separaten Aufgaben ausgeführt. Diese Methode erstellt zwei verschiedene Kontexte , dh zwei Verbindungen, um zwei verschiedene Entitäten zu ändern. Das sind zu viele Vorgänge, die dieselben Objekte betreffen, die gleichzeitig ausgeführt werden. Warum irgendetwas davon? Erstellen Sie einfach einen einzelnen Kontext, ändern Sie alle Entitäten nach Bedarf und rufen Sie SaveChanges
einmal an
SelfRefDbFactory.Create
vollständig threadsicher ist und immer neue Instanzen Ihres Datenbankkontexts zurückgibt?