2010-09-14 5 views
7

Beim Ausführen einer SELECT-Anweisung mit einem JOIN von zwei Tabellen scheint SQL Server beide Tabellen der Anweisung einzeln zu sperren. Zum Beispiel durch eine Abfrage wie dieses:Deadlock durch SELECT JOIN-Anweisung mit SQL Server

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 

fand ich heraus, dass die Reihenfolge der Schlösser an der WHERE-Bedingung abhängt. Der Abfrageoptimierer versucht, einen Ausführungsplan zu erstellen, der nur so viele Zeilen wie erforderlich liest. Wenn also die WHERE-Bedingung eine Spalte von table1 enthält, erhält sie zuerst die Ergebniszeilen aus table1 und dann die entsprechenden Zeilen aus table2. Wenn die Spalte aus Tabelle 2 ist, wird es die andere Weise tun Runde. Komplexere Bedingungen oder die Verwendung von Indizes können sich auch auf die Entscheidung des Abfrageoptimierers auf auswirken.

Wenn die durch eine Erklärung gelesenen Daten anzumerken, dass die Reihenfolge die Reihenfolge entspricht der UPDATE Aussagen garantieren nicht später in der Transaktion mit UPDATE-Anweisungen aktualisiert werden, um die Daten zu lesen von den zwei Tabellen verwendet wurde. Wenn eine andere Transaktion versucht, Daten zu lesen, während eine Transaktion die Tabellen aktualisiert, kann es einen Deadlock verursachen, wenn die SELECT-Anweisung in zwischen den UPDATE-Anweisungen ausgeführt wird, da weder SELECT die Sperre auf der ersten Tabelle noch das UPDATE erhalten kann Holen Sie das Schloss auf den zweiten Tisch. Für Beispiel:

T1: SELECT ... FROM ... JOIN ... 
T1: UPDATE table1 SET ... WHERE id = ? 
T2: SELECT ... FROM ... JOIN ... (locks table2, then blocked by lock on table1) 
T1: UPDATE table2 SET ... WHERE id = ? 

Beide Tabellen stellen eine Art Hierarchie und werden immer zusammen geladen. So macht es Sinn, ein Objekt mit einem SELECT mit einem JOIN zu laden. Das Laden beider Tabellen einzeln würde dem Abfrageoptimierer keine Chance geben, den besten Ausführungsplan zu finden. Da UPDATE-Anweisungen jedoch nur eine Tabelle unter aktualisieren können, kann dies zu Deadlocks führen, wenn ein Objekt geladen wird, während das Objekt von einer anderen Transaktion aktualisiert wird. Aktualisierungen von Objekten führen häufig zu UPDATEs in den beiden Tabellen , wenn Eigenschaften des Objekts aktualisiert werden, die zu verschiedenen Typen der Typhierarchie gehören.

Ich habe versucht, der SELECT-Anweisung Sperrhinweise hinzuzufügen, aber das ändert das Problem nicht . Es verursacht nur den Deadlock in den SELECT-Anweisungen, wenn beide Anweisungen versuchen, die Tabellen zu sperren, und eine SELECT-Anweisung die Sperre in der umgekehrten Reihenfolge der anderen Anweisung erhält. Vielleicht wäre es möglich, Daten für Updates immer mit der gleichen Anweisung zu laden, um die Sperren zu zwingen, in der gleichen Reihenfolge zu sein. Das würde einen Deadlock zwischen zwei Transaktionen verhindern, die die Daten aktualisieren möchten, aber würde nicht verhindern, dass eine Transaktion, die Daten nur liest Deadlock, die andere WHERE-Bedingungen haben muss.

Das einzige Work-a-Round so scheint dies bisher zu sein, dass liest möglicherweise Schlösser überhaupt nicht bekommen. Mit SQL Server 2005 kann dies mithilfe von SNAPSHOT ISOLATION erfolgen. Die einzige Möglichkeit für SQL Server 2000 wäre, die READ UNCOMMITED-Isolationsebene zu verwenden.

Ich würde gerne wissen, ob es eine andere Möglichkeit gibt zu verhindern, dass der SQL Server diese Deadlocks verursacht?

+0

Snapshot-Isolationsstufe? –

+0

Wenn das eine Frage ist, ist die Antwort nein. Es passiert mit allen Isolationsstufen, außer vielleicht READ UNCOMMITTED, das ich nicht getestet habe, weil ich nicht möchte, dass Transaktionen halbaktuelle Daten lesen können. – Reboot

Antwort

5

Dies geschieht niemals unter Snapshot-Isolation, wenn Leser keine Writer blockieren. Ansonsten gibt es keine Möglichkeit, solche Dinge zu verhindern. Ich schrieb eine Menge Repro-Skripte hier: Reproducing deadlocks involving only one table

Edit:

Ich habe keinen Zugriff auf SQL 2000, aber ich würde versuchen, Zugriff auf das Objekt serialisiert werden durch sp_getapplock verwenden, so dass das Lesen und Modifikationen nie gleichzeitig laufen. Wenn Sie sp_getapplock nicht verwenden können, führen Sie Ihren eigenen Mutex aus.

+0

+1 für die Deadlock-Forschung –

+0

Die Antwort hat mir geholfen, eine Lösung mit SQL Server 2005 oder neuer zu finden. Aber die Software wird immer noch mit SQL Server 2000 verwendet, ich kann wahrscheinlich verschiedene Lösungen für beide Versionen implementieren, so dass eine Lösung, die für alle Versionen oder eine andere Lösung für SQL Server 2000 funktioniert, geschätzt wird. – Reboot

-1

Ich war mit dem gleichen Problem konfrontiert. Durch Verwendung des Abfragehinweises FORCE ORDER wird dieses Problem behoben. Der Nachteil ist, dass Sie nicht in der Lage sein werden, den besten Plan zu nutzen, den der Abfrageoptimierer für Ihre Abfrage hat, aber dadurch wird der Deadlock verhindert.

Also (von "Rechnung die Eidechse" Benutzer) wenn Sie eine Abfrage FROM Tabelle1 LINKER JOIN Tabelle2 und Ihre WHERE-Klausel enthält nur Spalten aus Tabelle2 der Ausführungsplan wird in der Regel zuerst die Zeilen aus Tabelle2 auswählen und dann nachschlagen die Zeilen aus Tabelle1. Bei einer kleinen Ergebnismenge aus Tabelle2 müssen nur ein paar Zeilen aus Tabelle1 geholt werden. Bei FORCE ORDER müssen zuerst alle Zeilen aus table1 geholt werden, da es keine WHERE-Klausel gibt, dann werden die Zeilen aus table2 verknüpft und das Ergebnis wird mit der WHERE-Klausel gefiltert. Dies beeinträchtigt die Leistung.

Aber wenn Sie wissen, dass dies nicht der Fall sein wird, verwenden Sie dies. Möglicherweise möchten Sie die Abfrage manuell optimieren.

Die Syntax ist

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 
OPTION (FORCE ORDER) 
+0

Eigentlich habe ich den Kommentar über die Performance geschrieben, scheint Stack Overflow deine Antwort und meinen Kommentar vermasselt zu haben, ich habe mich schon gewundert warum er verschwunden ist. Das Problem ist nicht nur die Leistung mit FORCE ORDER, es ist auch das Sperren. Wenn Sie FORCE ORDER verwenden und der Ausführungsplan alle Zeilen von table1 liest, wird auch eine gemeinsame Sperre für sie festgelegt. Im Grunde wird jede Abfrage, die nur Spalten aus table2 verwendet, die gesamte Tabelle sperren, wodurch Aktualisierungen unmöglich werden. Es ist nicht eine einzige Abfrage, die funktionieren muss, die SELECT muss mit jeder WHERE-Klausel arbeiten. – Reboot

+0

Sie möchten also Tabelle1 nicht sperren, während Sie mit Tabelle2 verbinden können? In meinem Fall hatte ich Einsätze in Tabelle1 und dann in Tabelle2.Und der Abfrageoptimierer wählte die umgekehrte Reihenfolge im Fall von Join. Also Sackgasse. Das Hinzufügen von FORCE ORDER löste daher kein Deadlock aus. Beachten Sie hier, ich möchte immer noch Sperre auf Tabelle1 lesen. Einfügungen können warten, bis die Auswahl abgeschlossen ist. Aber es wird keinen Stillstand geben. – Ankush

+0

Keine Sperren sind in Tabelle 1 in Ordnung, aber mit FORCE ORDER werden alle Zeilen in der Tabelle und nicht nur die Zeilen gesperrt, die mit den Zeilen aus Tabelle2 verknüpft werden müssen. Das Zuweisen von Sperren für alle Zeilen kann ebenfalls zu einem Problem werden, da es sich um eine Ressource handelt, die Prozesse zuordnen müssen. Wenn der SQL Server den Speicher für Sperren ausführt, können Prozesse bei der Speicherzuweisung blockieren. – Reboot

0

Eine weitere Möglichkeit, dieses Problem zu beheben, um die Auswahl aufzuspalten ... von ... in mehrere select-Anweisungen kommen. Legen Sie fest, dass die Isolationsstufe "Committed" lautet. Verwenden Sie die Tabellenvariable, um Daten von Select, die mit anderen verknüpft werden sollen, zu leiten. Verwenden Sie distinct, um Einfügungen in diese Tabellenvariablen zu filtern.

Also, wenn ich zwei Tabellen A, B habe. Ich füge/aktualisiere in A und dann B. Wo der Abfrageoptimierer von sql es vorzieht, zuerst B und A zu lesen. Ich werde die einzelne Auswahl in 2 aufteilen wählt aus. Zuerst werde ich B lesen. Dann gebe ich diese Daten zur nächsten Select-Anweisung weiter, die A. liest.

Hier wird kein Deadlock passieren, weil die gelesenen Sperren in Tabelle B freigegeben werden, sobald die erste Anweisung ausgeführt wird.

PS Ich habe dieses Problem konfrontiert und das hat sehr gut funktioniert. Viel besser als meine Antwort.