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\[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>
quelle
SelfRefDbFactory.Create
vollständig threadsicher ist und immer neue Instanzen Ihres Datenbankkontexts zurückgibt?DeadlockTasks
. Es werden viele AufrufeCreateSelfRef_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 SieSaveChanges
einmal anAntworten:
Das Deadlock-XML zeigt an, dass die beiden Sitzungen über zwei verschiedene Zeilen kämpfen, wobei jede Sitzung eine e (X) -Clusive-Sperre für eine hat und eine (S) -Hared-Sperre für die andere anfordert.
Gegeben:
hostpid
Wert).trancount="2"
für beide Sitzungen angezeigt wirdes ist möglich, dass entweder:
Nun, das Verbindungspooling (Nummer 2) sollte im Allgemeinen keine Probleme verursachen, aber da es Szenarien gibt, in denen dies möglich ist (z. B. wenn verteilte Transaktionen verwendet werden), wollte ich dies nicht ausschließen. Und da ich nicht weiß, wie EF und / oder die asynchrone Option die Dinge handhaben, könnte es sich durchaus um eine Kombination aus asynchronem und Verbindungspooling handeln.
Warum versuchen Sie nicht zuerst, die asynchrone Speicherung für Schritt 1 beizubehalten, sondern deaktivieren das Verbindungspooling, indem Sie es
Pooling=false;
zu Ihrer Verbindungszeichenfolge hinzufügen .Unabhängig davon, ob das Deaktivieren des Verbindungspoolings hilfreich ist oder nicht , sollten Sie beim Erstellen von Elementen in Betracht ziehen, Async nicht zu verwenden, da das Problem nicht durch asynchrone Speicherung behoben wird (oder zumindest bisher). Vielleicht nur zum Löschen und Auswählen verwenden? Selbst wenn wir die genaue Änderung des Verhaltens zwischen der Verwendung und der Nichtverwendung von Async in Schritt 1 bestimmen, kann möglicherweise nichts umgangen werden (oder zumindest nicht umgangen werden, ohne Dinge zu tun, die wahrscheinlich nicht getan werden sollten).
quelle