Diese Seite wurde maschinell übersetzt. Lesen Sie das englische Original. English

Alexey Kovyazin, 08-May-2019, (c) IBSurgeon

Update-Konflikte (oft auch „Deadlocks“ genannt) treten in Firebird-Anwendungen auf, die intensive gleichzeitige Datenaktualisierungen durchführen.

Wie Sie aus dem Artikel « Transaktionen in Firebird» ersehen können, gibt es 3 Arten von Fehlern, die das Schlüsselwort „deadlock“ enthalten:

Code
deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN

lock time-out on wait transaction
-deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN

lock conflict on no wait transaction
-deadlock
-update conflicts with concurrent update
-concurrent transaction number is NNN

Allen 3 Arten von Update-Konflikten ist gemeinsam, dass der Update-Konflikt in der Regel das Ergebnis von 2 Änderungsoperationen ist, die versuchen, denselben Datensatz zu ändern.

Die möglichen Optionen sind: UPDATE, DELETE SELECT WITH LOCK, MERGE, UPDATE OR INSERT.

Es ist relativ einfach, die „Opfer“-Operation zu verfolgen, die die Fehlermeldung erhält - setzen Sie sie einfach in try… catch und protokollieren Sie die Parameter, die in der Abfrage verwendet werden, die bei dem Fehler abbricht.

Mit diesem Ansatz ist es jedoch schwierig, den „Gewinner“ zu verfolgen - d. h. die gleichzeitige Transaktion, in der die Aktualisierung ohne Fehler erfolgreich durchgeführt wurde.

Um Entwicklern bei der Untersuchung von Update-Konflikten zu helfen, fügt Firebird in Fehlermeldungen einen Verweis auf die gleichzeitige Transaktion ein - d. h. die Transaktion, in der die gleichzeitige Aktualisierung noch nicht festgeschrieben (committed) ist. Zusammen mit der Trace-API erhalten wir so die Möglichkeit, beide konkurrierenden Operationen zu verfolgen.

Betrachten wir die praktischen Schritte, wie das geht.

Zuerst müssen wir das Tracing konfigurieren. Erstellen Sie dazu eine Textdatei (in unserem Fall C:\Temp\mytrace.conf) mit folgendem Inhalt:

Code
database
{
	enabled = true
	include_filter="(UPDATE%)|(DELETE%)|(MERGE%)|(SELECT%FROM%WITH LOCK)|(UPDATE OR INSERT%)"
	log_statement_finish = true
	log_errors = true
	include_gds_codes="deadlock"
	log_initfini = false
	time_threshold = 0
	max_sql_length = 65000

}

Die obige Konfigurationsdatei aktiviert das Tracing für alle Datenbanken. Wenn Sie also mehr als eine aktive Datenbank haben, geben Sie besser den Namen der Datenbank in der Konfigurationsdatei an, z. B.:

Code
Database=mydatabase.fdb
{
  enabled = true
…
}

Lassen Sie uns die wichtigsten Parameter in der Konfigurationsdatei durchgehen:

Include-Filter-Parameter

Code
include_filter="(UPDATE%)|(DELETE%)|(MERGE%)|(SELECT%FROM%WITH LOCK)|(UPDATE OR INSERT%)"

Das bedeutet, dass wir nur UPDATE- und DELETE-Anweisungen verfolgen und SELECTs ignorieren möchten. Es ist notwendig, so viele Anweisungen wie möglich herauszufiltern, da die Anzahl der Datensätze im Textprotokoll bei hoher Last im Produktionssystem zu groß sein kann, um sie zu analysieren.

Normalerweise wissen wir, welche Tabelle an einem Update-Konflikt beteiligt ist. Es ist eine gute Idee, sie in den Filter aufzunehmen, um die Anzahl der zu analysierenden Updates zu reduzieren:

Code
include_filter="(UPDATE T1%)|(DELETE FROM T1%)|(MERGE INTO T1%)|(SELECT% FROM T1%WITH lock%)|(UPDATE OR INSERT INTO T1%)"

Was ist, wenn Updates und Deletes von gespeicherten Prozeduren durchgeführt werden? Nun, das macht den gesamten Prozess schwieriger: Fügen Sie dem Filter alle gespeicherten Prozeduren hinzu, die an gleichzeitigen Updates beteiligt sein können, und aktivieren Sie auch die Protokollierung von Prozeduren:

Code
include_filter="(UPDATE T1%)|(DELETE FROM T1%)|(MERGE INTO T1%)|(SELECT% FROM T1%with lock%)|(UPDATE OR INSERT INTO T1%)|(%SP_UPDATE_T1%)|(%SP_DEL_T1%)"
log_procedures=true

Parameter für die Fehlerprotokollierung

Dann müssen wir die Fehlerprotokollierung festlegen und auf Deadlocks beschränken.

Code
log_errors = true
include_gds_codes="deadlock"

Bitte beachten Sie: Der Parameter include_gds_codes ist erst in Firebird 3.0.2 erschienen. Wenn Sie also 2.5 oder 3.0.0 oder 3.0.1 verwenden, ist es nicht möglich, nur Deadlock-Fehler herauszufiltern, sodass alle Fehler im Protokoll ausgegeben werden.

Demonstration

Um den gesamten Prozess zu demonstrieren, bereiten wir die Testdatenbank vor - der Einfachheit halber gibt es 1 Tabelle mit 1 Primärspalte.

Code
C:\HQbird\Firebird30>isql
Use CONNECT or CREATE DATABASE to specify a database
SQL> create database "c:\temp\testdeadlock.fdb" user "SYSDBA" password "masterkey";
SQL> create table t1(i1 integer not null primary key);
SQL> insert into t1(i1) values(1);
SQL> commit; exit;

Danach müssen wir die Trace-Sitzung starten:

Code
fbtracemgr.exe -se localhost:service_mgr -start -conf "C:\temp\mytrace.conf" -user SYSDBA -pass masterkey

In diesem Fall gibt fbtracemgr das Protokoll auf dem Bildschirm aus, aber in der Produktion müssen wir es natürlich in eine Datei umleiten.

Nach dem erfolgreichen Start gibt fbtracemgr etwa Folgendes aus:

Code
Trace session ID 4 started

Dann starten wir 2 isql-Sitzungen und versuchen, den Update-Konflikt zu simulieren. In der folgenden Tabelle haben wir 2 Spalten für 2 isql-Sitzungen. Jede Zeile stellt einen Zeitpunkt dar, zu dem die Befehle ausgeführt wurden.

Zeit isql-Sitzung 1 isql-Sitzung 2
T1 <br>isql -user SYSDBA -pass masterkey localhost:c:\temp\testdeadlock.fdb<br>Database: localhost:c:\temp\testdeadlock.fdb, User: SYSDBA<br>SQL> set transaction wait;<br>Commit current transaction (y/n)?y<br>Committing.<br>SQL> select current_transaction from rdb$database;<br> CURRENT_TRANSACTION<br>=====================<br> 15<br>SQL> select * from t1;<br> I1<br>============<br> 2<br>SQL><br>
T2 <br>isql -user SYSDBA -pass masterkey localhost:c:\temp\testdeadlock.fdb<br>Database: localhost:c:\temp\testdeadlock.fdb, User: SYSDBA<br>SQL> set transaction wait;<br>Commit current transaction (y/n)?y<br>Committing.<br>SQL> select current_transaction from rdb$database;<br> CURRENT_TRANSACTION<br>=====================<br> 20<br>SQL> select * from t1;<br> I1<br>============<br> 2<br>SQL><br>
Am Ausgangspunkt sehen wir also, dass 2 Transaktionen gestartet wurden, #15 und #20. Dann versuchen wir, die gleichzeitige Aktualisierung desselben Datensatzes durchzuführen:
T3 <br>SQL> UPDATE T1 SET i1=5 where i1=2;<br>
T4 <br>SQL> UPDATE T1 SET i1=100 where i1=2;<br>
T5 <br>SQL> commit;<br>SQL><br> <br>Statement failed, SQLSTATE = 40001<br>deadlock<br>-update conflicts with concurrent update<br>-concurrent transaction number is 15<br>SQL><br>

Wir haben UPDATE in Sitzung 1 gestartet, aber nicht festgeschrieben (committed).

In Sitzung 2 „hing“ UPDATE - es wartete auf das Ende der gleichzeitigen Transaktion, und als wir UPDATE in Sitzung 1 festgeschrieben haben, erhielt Sitzung 2 den Fehler:

Code
Statement failed, SQLSTATE = 40001
deadlock
-update conflicts with concurrent update
-concurrent transaction number is 15
SQL>

Wir haben also eine typische Situation bei einem Update-Konflikt mit einer Wartetransaktion.

Lassen Sie uns das Trace-Protokoll überprüfen, um die Beweise für den Update-Konflikt zu finden.

In diesem Fall ist das Trace-Protokoll sehr kurz, aber im Falle eines Produktionssystems kann es viel länger sein, aber der Ansatz zum Auffinden von Deadlocks ist derselbe.

Code
fbtracemgr.exe -se localhost:service_mgr -start -conf "C:\temp\fbtrace.conf" -user SYSDBA -pass masterkey
Trace session ID 4 started

2019-05-07T11:28:43.9850 (2692:00000000018C1940) EXECUTE_STATEMENT_FINISH
        C:\TEMP\TESTDEADLOCK.FDB (ATT_12, SYSDBA:NONE, NONE, TCPv6:::1/52938)
        C:\HQbird\Firebird30\isql.exe:8828
                (TRA_15, CONCURRENCY | WAIT | READ_WRITE)

Statement 52:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=5 where i1=2
0 records fetched
      0 ms, 2 read(s), 21 fetch(es), 3 mark(s)

2019-05-07T11:29:45.0190 (2692:00000000018C0040) FAILED EXECUTE_STATEMENT_FINISH
        C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
        C:\HQbird\Firebird30\isql.exe:7032
                (TRA_20, CONCURRENCY | WAIT | READ_WRITE)

Statement 55:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=100 where i1=2
0 records fetched
  40242 ms, 19 fetch(es), 2 mark(s)

2019-05-07T11:29:45.0190 (2692:00000000018C0040) ERROR AT JStatement::execute
        C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
        C:\HQbird\Firebird30\isql.exe:7032
335544336 : deadlock
335544451 : update conflicts with concurrent update
335544878 : concurrent transaction number is 15

Wie Sie sehen, haben wir im Protokoll Einträge über beide Updates - das erfolgreiche und das fehlgeschlagene - sowie eine Fehlermeldung über den Deadlock.

Betrachten wir, wie das Protokoll zu analysieren ist:

In der Fehlermeldung lokalisieren wir die Kennung der Transaktion:

Code
 2019-05-07T11:29:45.0190 (2692:00000000018C0040) ERROR AT JStatement::execute
        C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
        C:\HQbird\Firebird30\isql.exe:7032
335544336 : deadlock
335544451 : update conflicts with concurrent update
335544878 : concurrent transaction number is 15

In der Nähe des Eintrags über den Fehler (normalerweise direkt darüber) sehen Sie die fehlgeschlagene Anweisung:

Code
2019-05-07T11:29:45.0190 (2692:00000000018C0040) FAILED EXECUTE_STATEMENT_FINISH
        C:\TEMP\TESTDEADLOCK.FDB (ATT_13, SYSDBA:NONE, NONE, TCPv6:::1/52968)
        C:\HQbird\Firebird30\isql.exe:7032
                (TRA_20, CONCURRENCY | WAIT | READ_WRITE)

Statement 55:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=100 where i1=2
0 records fetched
  40242 ms, 19 fetch(es), 2 mark(s)

Beachten Sie, dass die Ausführungszeit enorm ist (~40 Sekunden) - dies zeigt die Zeit an, in der die Operation auf das Ergebnis der gleichzeitigen Transaktion gewartet hat.

Und schließlich suchen Sie nach der Transaktionsnummer mit der erfolgreichen UPDATE-Operation: Suchen Sie dazu nach TRA_NNN, wobei NNN die Transaktionsnummer ist. In unserem Fall wäre das TRA_15:

Code
2019-05-07T11:28:43.9850 (2692:00000000018C1940) EXECUTE_STATEMENT_FINISH
        C:\TEMP\TESTDEADLOCK.FDB (ATT_12, SYSDBA:NONE, NONE, TCPv6:::1/52938)
        C:\HQbird\Firebird30\isql.exe:8828
                (TRA_15, CONCURRENCY | WAIT | READ_WRITE)

Statement 52:
-------------------------------------------------------------------------------
UPDATE T1 SET i1=5 where i1=2
0 records fetched
      0 ms, 2 read(s), 21 fetch(es), 3 mark(s)

Das war’s also - wir haben beide Parteien des Update-Konflikts gefunden - den erfolgreichen und den fehlgeschlagenen.

Werkzeuge zur Identifizierung von Deadlocks

Wie Sie sehen, kann es ziemlich schwierig sein, die Ursache eines Sperrkonflikts/Deadlocks zu identifizieren: Da der Konflikt immer zwei Seiten hat und eine Seite „gewinnt“ (ohne Fehlermeldungen abgeschlossen wird), kann es knifflig sein, den Gewinner zu identifizieren. Zum Beispiel kann ein Konflikt innerhalb von EXECUTE STATEMENT, in einer verschachtelten gespeicherten Prozedur, innerhalb eines Triggers oder in einer Kombination all dieser Dinge auftreten.

Um Deadlocks zu identifizieren, hat IBSurgeon das Tool „Deadlock Analyzer“ entwickelt, das Trace-Protokolle analysiert und leicht verständliche Diagramme mit Details der Aufrufe erstellt, die zu Deadlocks geführt haben. Dieses Tool ist als Teil des Enterprise-Abonnements verfügbar (es hat eine Testversion) - registrieren Sie sich auf cc.ib-aid.com.

Außerdem bietet HQbird eine erweiterte Version des fbtrace-Plugins an, das so konfiguriert werden kann, dass nur Änderungen und Änderungsversuche (d. h. fehlgeschlagene Operationen sowie Sperrkonflikte und Deadlocks) gespeichert werden.

Kontaktieren Sie den IBSurgeon-Support für weitere Informationen: [email protected].

Wie kann man Deadlocks vermeiden?

Jetzt müssen Sie die beste Lösung für Ihre Anwendung identifizieren, um Deadlocks zu vermeiden - es kann eine Änderung der Transaktionsparameter sein, oder es ist möglich, eine zusätzliche Fehlerbehandlung hinzuzufügen und die Operation zu wiederholen oder zusätzliche Prüfungen auf der Geschäftsebene der Anwendung durchzuführen.