In der Stack Overflow SQL Server 2005-Datenbank werden einige schädliche, aber seltene Deadlock-Bedingungen angezeigt.
Ich habe den Profiler angehängt, mithilfe dieses hervorragenden Artikels zur Fehlerbehebung bei Deadlocks ein Ablaufverfolgungsprofil erstellt und eine Reihe von Beispielen erfasst. Das Seltsame ist, dass das Deadlocking-Schreiben immer dasselbe ist :
UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0
Die andere Deadlocking-Anweisung variiert, aber es ist normalerweise eine Art triviales, einfaches Lesen der Posts-Tabelle. Dieser wird immer in der Sackgasse getötet. Hier ist ein Beispiel
SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount],
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId],
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0
Um ganz klar zu sein, sehen wir keine Deadlocks beim Schreiben / Schreiben, sondern beim Lesen / Schreiben.
Wir haben momentan eine Mischung aus LINQ- und parametrisierten SQL-Abfragen. Wir haben with (nolock)
alle SQL-Abfragen hinzugefügt . Dies mag einigen geholfen haben. Wir hatten auch eine einzige (sehr) schlecht geschriebene Abzeichenabfrage, die ich gestern behoben habe. Die Ausführung dauerte jedes Mal mehr als 20 Sekunden und lief jede Minute darüber hinaus. Ich hatte gehofft, dass dies die Ursache für einige der Schließprobleme war!
Leider habe ich vor ca. 2 Stunden einen weiteren Deadlock-Fehler erhalten. Gleiche exakte Symptome, gleiche exakte Schuldige schreiben.
Das wirklich Seltsame ist, dass die oben gezeigte SQL-Anweisung zum Sperren von Schreibvorgängen Teil eines sehr spezifischen Codepfads ist. Es wird nur ausgeführt, wenn einer Frage eine neue Antwort hinzugefügt wird. Die übergeordnete Frage wird mit der neuen Antwortanzahl und dem letzten Datum / Benutzer aktualisiert. Dies ist offensichtlich nicht so häufig im Vergleich zu der enormen Anzahl von Lesevorgängen, die wir durchführen! Soweit ich das beurteilen kann, schreiben wir nirgendwo in der App eine große Anzahl von Schreibvorgängen.
Mir ist klar, dass NOLOCK eine Art Riesenhammer ist, aber die meisten Abfragen, die wir hier ausführen, müssen nicht so genau sein. Interessiert es Sie, wenn Ihr Benutzerprofil einige Sekunden veraltet ist?
Die Verwendung von NOLOCK mit Linq ist etwas schwieriger, wie Scott Hanselman hier erläutert .
Wir flirten mit der Idee zu verwenden
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
im Basisdatenbankkontext, sodass alle unsere LINQ-Abfragen diesen Satz haben. Ohne das müssten wir jeden LINQ-Aufruf, den wir tätigen (also die einfach lesenden, die die überwiegende Mehrheit von ihnen sind), in einen hässlichen Transaktionscodeblock mit 3-4 Zeilen packen, was hässlich ist.
Ich glaube, ich bin ein wenig frustriert darüber, dass triviale Lesevorgänge in SQL 2005 beim Schreiben zum Stillstand kommen können. Ich konnte sehen, dass Deadlocks beim Schreiben / Schreiben ein großes Problem sind, aber liest? Wir betreiben hier keine Bank-Site, wir brauchen nicht jedes Mal perfekte Genauigkeit.
Ideen? Gedanken?
Instanziieren Sie für jede Operation ein neues LINQ to SQL DataContext-Objekt oder teilen Sie möglicherweise für alle Ihre Aufrufe denselben statischen Kontext?
Jeremy, wir teilen größtenteils einen statischen Datenkontext im Basis-Controller:
private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
get
{
if (_db == null)
{
_db = new DBContext() { SessionName = GetType().Name };
//_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
}
return _db;
}
}
Empfehlen Sie, dass wir für jeden Controller oder pro Seite oder öfter einen neuen Kontext erstellen?
quelle
Antworten:
Laut MSDN:
http://msdn.microsoft.com/en-us/library/ms191242.aspx
Es scheint eine leichte Leistungsminderung für den zusätzlichen Overhead zu geben, die jedoch vernachlässigbar sein kann. Wir sollten testen, um sicherzugehen.
Versuchen Sie, diese Option festzulegen, und entfernen Sie alle NOLOCKs aus Code-Abfragen, es sei denn, dies ist wirklich erforderlich. NOLOCKs oder die Verwendung globaler Methoden im Datenbankkontext-Handler zur Bekämpfung der Isolationsebenen von Datenbanktransaktionen sind Band-Aids für das Problem. NOLOCKS maskiert grundlegende Probleme mit unserer Datenschicht und führt möglicherweise zur Auswahl unzuverlässiger Daten, wobei die automatische Versionsauswahl zum Auswählen / Aktualisieren von Zeilen die Lösung zu sein scheint.
quelle
NOLOCK und READ UNCOMMITTED sind ein rutschiger Hang. Sie sollten sie niemals verwenden, es sei denn, Sie verstehen, warum der Deadlock zuerst auftritt. Es würde mich beunruhigen, wenn Sie sagen: "Wir haben alle SQL-Abfragen mit (nolock) ergänzt." Das Hinzufügen von WITH NOLOCK überall ist ein sicheres Zeichen dafür, dass Sie Probleme in Ihrer Datenschicht haben.
Die Update-Anweisung selbst sieht etwas problematisch aus. Bestimmen Sie die Anzahl früher in der Transaktion oder ziehen Sie sie einfach aus einem Objekt?
AnswerCount = AnswerCount+1
Wenn eine Frage hinzugefügt wird, ist dies wahrscheinlich eine bessere Möglichkeit, damit umzugehen. Dann benötigen Sie keine Transaktion, um die richtige Anzahl zu erhalten, und Sie müssen sich keine Gedanken über das Problem der Parallelität machen, dem Sie sich möglicherweise aussetzen.Eine einfache Möglichkeit, diese Art von Deadlock-Problem ohne viel Arbeit und ohne Aktivierung von Dirty Reads zu umgehen, ist die Verwendung
"Snapshot Isolation Mode"
(neu in SQL 2005), mit der Sie immer die letzten unveränderten Daten sauber lesen können. Sie können festgefahrene Anweisungen auch ziemlich einfach abfangen und erneut versuchen, wenn Sie sie ordnungsgemäß verarbeiten möchten.quelle
Die OP-Frage war zu fragen, warum dieses Problem aufgetreten ist. Dieser Beitrag hofft, dies zu beantworten, während mögliche Lösungen von anderen ausgearbeitet werden können.
Dies ist wahrscheinlich ein indexbezogenes Problem. Angenommen, die Tabelle Posts hat einen nicht gruppierten Index X, der die ParentID und eines (oder mehrere) der zu aktualisierenden Felder enthält (AnswerCount, LastActivityDate, LastActivityUserId).
Ein Deadlock würde auftreten, wenn das SELECT-Cmd eine Shared-Read-Sperre für Index X ausführt, um nach der ParentId zu suchen, und dann eine Shared-Read-Sperre für den Clustered-Index durchführen muss, um die verbleibenden Spalten abzurufen, während das UPDATE-Cmd eine exklusive Schreibsperre ausführt Sperren Sie den Clustered-Index und benötigen Sie eine schreibgeschützte Sperre für Index X, um ihn zu aktualisieren.
Sie haben jetzt eine Situation, in der A X gesperrt hat und versucht, Y zu erhalten, während B Y gesperrt hat und versucht, X zu erhalten.
Natürlich muss das OP seine Veröffentlichung mit weiteren Informationen darüber aktualisieren, welche Indizes im Spiel sind, um zu bestätigen, ob dies tatsächlich die Ursache ist.
quelle
Diese Frage und die damit verbundenen Antworten sind mir ziemlich unangenehm. Es gibt eine Menge "Probieren Sie diesen magischen Staub! Nein, diesen magischen Staub!"
Ich kann nirgendwo sehen, dass Sie die genommenen Schlösser analysiert und festgestellt haben, welche Art von Schlössern genau blockiert sind.
Sie haben lediglich angegeben, dass einige Sperren auftreten - nicht das, was blockiert.
In SQL 2005 können Sie weitere Informationen darüber erhalten, welche Sperren aufgehoben werden, indem Sie Folgendes verwenden:
Wenn der Deadlock auftritt, haben Sie eine bessere Diagnose.
quelle
Instanziieren Sie für jede Operation ein neues LINQ to SQL DataContext-Objekt oder teilen Sie möglicherweise für alle Ihre Aufrufe denselben statischen Kontext? Ich habe ursprünglich den letzteren Ansatz ausprobiert, und soweit ich mich erinnere, verursachte er unerwünschte Sperren in der Datenbank. Ich erstelle jetzt einen neuen Kontext für jede atomare Operation.
quelle
Bevor Sie das Haus niederbrennen, um mit NOLOCK überall eine Fliege zu fangen, sollten Sie sich das Deadlock-Diagramm ansehen, das Sie mit Profiler hätten erfassen sollen.
Denken Sie daran, dass für einen Deadlock (mindestens) 2 Sperren erforderlich sind. Verbindung 1 hat Sperre A, möchte Sperre B - und umgekehrt für Verbindung 2. Dies ist eine unlösbare Situation, die jemand geben muss.
Was Sie bisher gezeigt haben, wird durch einfaches Sperren gelöst, was SQL Server gerne den ganzen Tag lang tut.
Ich vermute, Sie (oder LINQ) starten eine Transaktion mit dieser UPDATE-Anweisung und wählen vorher eine andere Information aus. Sie müssen jedoch wirklich durch das Deadlock-Diagramm zurückverfolgen, um die von jedem Thread gehaltenen Sperren zu finden , und dann durch Profiler zurückverfolgen, um die Anweisungen zu finden, die dazu geführt haben, dass diese Sperren gewährt wurden.
Ich gehe davon aus, dass es mindestens 4 Anweisungen gibt, um dieses Rätsel zu lösen (oder eine Anweisung, die mehrere Sperren erfordert - vielleicht gibt es einen Auslöser für die Posts-Tabelle?).
quelle
Nein - das ist vollkommen akzeptabel. Das Festlegen der Isolationsstufe für Basistransaktionen ist wahrscheinlich der beste / sauberste Weg.
quelle
Ein typischer Lese- / Schreib-Deadlock ergibt sich aus dem Zugriff auf die Indexreihenfolge. Read (T1) sucht die Zeile in Index A und sucht dann die projizierte Spalte in Index B (normalerweise gruppiert). Write (T2) ändert den Index B (den Cluster) und muss dann den Index A aktualisieren. T1 hat S-Lck auf A, will S-Lck auf B, T2 hat X-Lck auf B, will U-Lck auf A. Deadlock , puff. T1 wird getötet. Dies ist in Umgebungen mit starkem OLTP-Verkehr und nur ein bisschen zu vielen Indizes der Fall :). Die Lösung besteht darin, entweder dafür zu sorgen, dass der Lesevorgang nicht von A nach B springen muss (dh die Spalte in A enthalten ist oder die Spalte aus der projizierten Liste entfernt wird), oder dass T2 nicht von B nach A springen muss (die indizierte Spalte nicht aktualisieren). Leider ist linq hier nicht dein Freund ...
quelle
@ Jeff - Ich bin definitiv kein Experte in diesem Bereich, aber ich habe gute Ergebnisse erzielt, indem ich bei fast jedem Anruf einen neuen Kontext instanziiert habe. Ich denke, es ähnelt dem Erstellen eines neuen Verbindungsobjekts bei jedem Aufruf mit ADO. Der Overhead ist nicht so hoch, wie Sie denken würden, da das Verbindungspooling ohnehin weiterhin verwendet wird.
Ich benutze einfach einen globalen statischen Helfer wie diesen:
und dann mache ich so etwas:
Und ich würde das Gleiche für Updates tun. Wie auch immer, ich habe nicht annähernd so viel Verkehr wie Sie, aber ich bekam definitiv einige Sperren, als ich einen gemeinsam genutzten DataContext mit nur einer Handvoll Benutzern verwendete. Keine Garantie, aber es könnte sich lohnen, es zu versuchen.
Update : Wenn Sie sich Ihren Code ansehen, teilen Sie den Datenkontext nur für die Lebensdauer dieser bestimmten Controller-Instanz. Dies scheint im Grunde genommen in Ordnung zu sein, es sei denn, er wird gleichzeitig von mehreren Aufrufen innerhalb des Controllers verwendet. In einem Thread zum Thema sagte ScottGu:
Das ist es vielleicht nicht, aber es ist wahrscheinlich einen Versuch wert, vielleicht in Verbindung mit einigen Lasttests.
quelle
Frage: Warum speichern Sie das überhaupt
AnswerCount
in derPosts
Tabelle?Ein alternativer Ansatz besteht darin, das "Zurückschreiben" in die
Posts
Tabelle zu eliminieren, indem das nichtAnswerCount
in der Tabelle gespeichert wird, sondern die Anzahl der Antworten auf den Beitrag nach Bedarf dynamisch berechnet wird.Ja, dies bedeutet, dass Sie eine zusätzliche Abfrage ausführen:
oder typischer (wenn Sie dies für die Startseite anzeigen):
Dies führt jedoch in der Regel zu einem
INDEX SCAN
und kann bei der Verwendung von Ressourcen effizienter sein als bei der VerwendungREAD ISOLATION
.Es gibt mehr als einen Weg, eine Katze zu häuten. Eine vorzeitige De-Normalisierung eines Datenbankschemas kann zu Skalierbarkeitsproblemen führen.
quelle
Sie möchten auf jeden Fall, dass READ_COMMITTED_SNAPSHOT aktiviert ist, was nicht standardmäßig der Fall ist. Das gibt Ihnen MVCC-Semantik. Es ist dasselbe, was Oracle standardmäßig verwendet. Eine MVCC-Datenbank zu haben ist so unglaublich nützlich, dass es verrückt ist, KEINE zu verwenden. Auf diese Weise können Sie innerhalb einer Transaktion Folgendes ausführen:
USERS aktualisieren Set FirstName = 'foobar'; // entscheide dich für ein Jahr zu schlafen.
In der Zwischenzeit kann jeder ohne diese Verpflichtung weiter aus dieser Tabelle auswählen. Wenn Sie mit MVCC nicht vertraut sind, werden Sie schockiert sein, dass Sie jemals ohne MVCC leben konnten. Ernsthaft.
quelle
Es ist keine gute Idee, die Standardeinstellung so festzulegen, dass sie nicht festgeschrieben ist. Ihr Wille führt zweifellos zu Inkonsistenzen und führt zu einem Problem, das schlimmer ist als das, was Sie jetzt haben. Die Snapshot-Isolation mag gut funktionieren, ist jedoch eine drastische Änderung der Funktionsweise von SQL Server und stellt eine enorme Belastung für Tempdb dar.
Folgendes sollten Sie tun: Verwenden Sie try-catch (in T-SQL), um die Deadlock-Bedingung zu erkennen. Führen Sie in diesem Fall die Abfrage erneut aus. Dies ist die Standardpraxis für die Datenbankprogrammierung.
Es gibt gute Beispiele für diese Technik in Paul Nielsons Sql Server 2005 Bible .
Hier ist eine kurze Vorlage, die ich verwende:
quelle
Eine Sache, die in der Vergangenheit für mich funktioniert hat, ist sicherzustellen, dass alle meine Abfragen und Aktualisierungen auf Ressourcen (Tabellen) in derselben Reihenfolge zugreifen.
Das heißt, wenn eine Abfrage in der Reihenfolge Tabelle1, Tabelle2 und eine andere Abfrage in der Reihenfolge Tabelle2, Tabelle1 aktualisiert wird, werden möglicherweise Deadlocks angezeigt.
Sie sind sich nicht sicher, ob Sie die Reihenfolge der Aktualisierungen ändern können, da Sie LINQ verwenden. Aber es ist etwas zu sehen.
quelle
Ein paar Sekunden wären definitiv akzeptabel. Es scheint sowieso nicht so lange zu dauern, es sei denn, eine große Anzahl von Menschen gibt gleichzeitig Antworten.
quelle
Da stimme ich Jeremy zu. Sie fragen, ob Sie für jeden Controller oder pro Seite einen neuen Datenkontext erstellen sollen. Ich neige dazu, für jede unabhängige Abfrage einen neuen zu erstellen.
Ich baue derzeit eine Lösung, die den statischen Kontext wie Sie implementiert hat, und als ich während Stresstests Tonnen von Anfragen an das Biest eines Servers (über Millionen) warf, bekam ich auch zufällig Lese- / Schreibsperren.
Sobald ich meine Strategie geändert hatte, um auf Abfrageebene einen anderen Datenkontext auf LINQ-Ebene zu verwenden, und darauf vertraute, dass SQL Server seine Magie des Verbindungspoolings anwenden konnte, schienen die Sperren zu verschwinden.
Natürlich stand ich unter Zeitdruck, also habe ich eine Reihe von Dingen gleichzeitig ausprobiert, daher kann ich nicht 100% sicher sein, dass dies das Problem behoben hat, aber ich habe ein hohes Maß an Selbstvertrauen - sagen wir es so .
quelle
Sie sollten Dirty Reads implementieren.
Wenn Sie bei Ihren Abfragen nicht unbedingt eine perfekte Transaktionsintegrität benötigen, sollten Sie beim Zugriff auf Tabellen mit hoher Parallelität Dirty Reads verwenden. Ich gehe davon aus, dass Ihre Posts-Tabelle eine davon wäre.
Dies kann zu sogenannten "Phantom Reads" führen, wenn Ihre Abfrage auf Daten aus einer Transaktion reagiert, die nicht festgeschrieben wurde.
Verwenden Sie Dirty Reads. Sie haben Recht damit, dass sie Ihnen keine perfekte Genauigkeit bieten, aber sie sollten Ihre Probleme mit toten Verriegelungen klären.
Wenn Sie Dirty Reads im "Basisdatenbankkontext" implementieren, können Sie Ihre einzelnen Aufrufe immer mit einer höheren Isolationsstufe umschließen, wenn Sie die Transaktionsintegrität benötigen.
quelle
Was ist das Problem bei der Implementierung eines Wiederholungsmechanismus? Es wird immer die Möglichkeit eines Deadlocks geben. Warum also nicht eine Logik haben, um ihn zu identifizieren und es einfach erneut zu versuchen?
Werden nicht zumindest einige der anderen Optionen Leistungseinbußen einführen, die immer dann auftreten, wenn ein Wiederholungssystem selten eingesetzt wird?
Vergessen Sie auch nicht eine Art Protokollierung, wenn ein erneuter Versuch stattfindet, damit Sie nicht in die Situation geraten, dass Sie selten werden.
quelle
Nachdem ich Jeremys Antwort gesehen habe, erinnere ich mich, dass ich gehört habe, dass die beste Vorgehensweise darin besteht, für jede Datenoperation einen neuen DataContext zu verwenden. Rob Conery hat mehrere Posts über DataContext geschrieben, und er informiert sie immer, anstatt einen Singleton zu verwenden.
Hier ist das Muster, das wir für Video.Show verwendet haben ( Link zur Quellansicht in CodePlex ):
Dann auf Service-Ebene (oder noch detaillierter für Updates):
quelle
Ich müsste Greg zustimmen, solange das Einstellen der Isolationsstufe zum Lesen ohne Festschreiben keine negativen Auswirkungen auf andere Abfragen hat.
Es würde mich interessieren, Jeff, wie sich das Festlegen auf Datenbankebene auf eine Abfrage wie die folgende auswirkt:
quelle
Es ist in Ordnung für mich, wenn mein Profil sogar einige Minuten veraltet ist.
Versuchen Sie den Lesevorgang erneut, nachdem er fehlgeschlagen ist? Es ist sicherlich möglich, dass einige wenige zufällige Lesevorgänge ausführen, wenn sie nicht lesen können. Die meisten Anwendungen, mit denen ich arbeite, sind im Vergleich zur Anzahl der Lesevorgänge nur sehr wenige Schreibvorgänge, und ich bin sicher, dass die Lesevorgänge nicht annähernd der Anzahl entsprechen, die Sie erhalten.
Wenn die Implementierung von "READ UNCOMMITTED" Ihr Problem nicht löst, ist es schwierig zu helfen, ohne viel mehr über die Verarbeitung zu wissen. Möglicherweise gibt es eine andere Optimierungsoption, die dieses Verhalten unterstützt. Sofern kein MSSQL-Guru zur Rettung kommt, empfehle ich, das Problem beim Anbieter einzureichen.
quelle
Ich würde weiterhin alles stimmen; Wie funktioniert das Festplattensubsystem? Was ist die durchschnittliche Länge der Festplattenwarteschlange? Wenn E / A-Vorgänge gesichert werden, besteht das eigentliche Problem möglicherweise nicht in diesen beiden Deadlocks, sondern in einer anderen Abfrage, die das System zum Engpass macht. Sie haben eine Abfrage erwähnt, die 20 Sekunden dauert und optimiert wurde. Gibt es noch andere?
Wenn Sie sich darauf konzentrieren, die lang laufenden Abfragen zu verkürzen, werden die Deadlock-Probleme wahrscheinlich verschwinden.
quelle
Hatte das gleiche Problem und kann "TransationLevel = IsolationLevel.ReadUncommitted" in TransactionScope nicht verwenden, da auf dem Server DTS nicht aktiviert ist (!).
Das habe ich mit einer Erweiterungsmethode gemacht:
Für ausgewählte Benutzer, die kritische Parallelitätstabellen verwenden, aktivieren wir den "Nolock" wie folgt:
Vorschläge sind willkommen!
quelle