Isolationsstufen und Schreibkonflikte in Azure Databricks

Die Isolationsstufe einer Tabelle legt fest, inwieweit eine Transaktion von Änderungen durch gleichzeitige Vorgänge isoliert sein muss. Schreibkonflikte in Azure Databricks hängen von der Isolationsstufe ab.

Delta Lake bietet ACID-Transaktionsgarantien zwischen Lese- und Schreibvorgängen. Dies bedeutet Folgendes:

  • Mehrere Writer in mehreren Clustern können gleichzeitig eine Tabellenpartition ändern. Writer sehen eine konsistente Momentaufnahmeansicht der Tabelle, und Schreibvorgänge erfolgen in serieller Reihenfolge.
    • Leser sehen weiterhin eine konsistente Momentaufnahmeansicht der Tabelle, mit der der Azure Databricks-Auftrag gestartet wurde, auch wenn eine Tabelle während eines Auftrags geändert wird.

Weitere Informationen finden Sie unter Was sind ACID-Garantien in Azure Databricks?.

Hinweis

Azure Databricks verwendet Delta Lake standardmäßig für alle Tabellen. In diesem Artikel wird das Verhalten für Delta Lake in Azure Databricks beschrieben.

Schreibkonflikte mit Parallelität auf Zeilenebene

Parallelität auf Zeilenebene reduziert Konflikte zwischen gleichzeitigen Schreibvorgängen, indem Änderungen auf Zeilenebene erkannt und Konflikte bei gleichzeitigen Schreibvorgängen automatisch aufgelöst werden, die unterschiedliche Zeilen in derselben Datendatei aktualisieren oder löschen.

Parallelität auf Zeilenebene ist für Databricks Runtime 14.2 und höher allgemein verfügbar. Parallelität auf Zeilenebene wird standardmäßig für die folgenden Bedingungen unterstützt:

  • Tabellen mit aktivierten Löschvektoren und ohne Partitionierung.
  • Tabellen mit fließendem Clustering, es sei denn, Sie haben Löschvektoren deaktiviert.

Tabellen mit Partitionen unterstützen keine Parallelität auf Zeilenebene, können jedoch weiterhin Konflikte zwischen OPTIMIZE und allen anderen Schreibvorgängen vermeiden, wenn Löschvektoren aktiviert sind. Siehe Einschränkungen für Parallelität auf Zeilenebene.

Weitere Databricks Runtime-Versionen finden Sie unter Verhalten der Vorschauversion von Parallelität auf Zeilenebene (Legacy).

In der folgenden Tabelle wird beschrieben, welche Schreibvorgangspaare auf den jeweiligen Isolationsstufen mit aktivierter Parallelität auf Zeilenebene in Konflikt stehen können.

Hinweis

Tabellen mit Identitätsspalten unterstützen keine gleichzeitigen Transaktionen. Weitere Informationen finden Sie unter Verwenden von Identitätsspalten in Delta Lake.

INSERT (1) UPDATE, DELETE, MERGE INTO OPTIMIZE
INSERT Kein Konflikt möglich
UPDATE, DELETE, MERGE INTO Kein Konflikt in WriteSerializable. Kann beim Ändern derselben Zeile in „Serializable“ einen Konflikt verursachen; siehe Einschränkungen für Parallelität auf Zeilenebene. MERGE INTO erfordert Photon für die Konfliktauflösung auf Zeilenebene. Kann beim Ändern derselben Zeile einen Konflikt verursachen; siehe Einschränkungen für Parallelität auf Zeilenebene.
OPTIMIZE Kein Konflikt möglich Kein Konflikt möglich Kein Konflikt möglich

Wichtig

(1) Alle INSERT-Vorgänge in den obigen Tabellen beschreiben Anfügevorgänge, die vor dem Commit keine Daten aus derselben Tabelle lesen. INSERT-Vorgänge, die Unterabfragen enthalten, die dieselbe Tabelle lesen, unterstützen die gleiche Parallelität wie MERGE.

Schreibkonflikte ohne Parallelität auf Zeilenebene

In der folgenden Tabelle wird beschrieben, welche Schreibvorgangspaare auf den jeweiligen Isolationsstufen in Konflikt stehen können.

Tabellen unterstützen keine Parallelität auf Zeilenebene, wenn für sie Partitionen definiert oder keine Löschvektoren aktiviert sind. Databricks Runtime 14.2 oder höher ist für die Parallelität auf Zeilenebene erforderlich.

Hinweis

Tabellen mit Identitätsspalten unterstützen keine gleichzeitigen Transaktionen. Weitere Informationen finden Sie unter Verwenden von Identitätsspalten in Delta Lake.

INSERT (1) UPDATE, DELETE, MERGE INTO OPTIMIZE
INSERT Kein Konflikt möglich
UPDATE, DELETE, MERGE INTO Kein Konflikt in WriteSerializable. Kann einen Konflikt in „Serializable“ verursachen; siehe Vermeiden von Konflikten mit Partitionen. Kann einen Konflikt in „Serializable“ und „WriteSerializable“ verursachen; siehe Vermeiden von Konflikten mit Partitionen.
OPTIMIZE Kein Konflikt möglich Kann keinen Konflikt in Tabellen verursachen, für die Löschvektoren aktiviert sind. Andernfalls kann ein Konflikt auftreten. Kann keinen Konflikt in Tabellen verursachen, für die Löschvektoren aktiviert sind. Andernfalls kann ein Konflikt auftreten.

Wichtig

(1) Alle INSERT-Vorgänge in den obigen Tabellen beschreiben Anfügevorgänge, die vor dem Commit keine Daten aus derselben Tabelle lesen. INSERT-Vorgänge, die Unterabfragen enthalten, die dieselbe Tabelle lesen, unterstützen die gleiche Parallelität wie MERGE.

Einschränkungen für Parallelität auf Zeilenebene

Für Parallelität auf Zeilenebene gelten einige Einschränkungen. Für die folgenden Vorgänge folgt die Konfliktauflösung der normalen Parallelität für Schreibkonflikte in Azure Databricks. Siehe Schreibkonflikte ohne Parallelität auf Zeilenebene.

  • OPTIMIZE Befehle mit ZORDER BY.
  • Befehle mit komplexen bedingten Klauseln, einschließlich der folgenden:
    • Bedingungen für komplexe Datentypen wie Strukturen, Arrays oder Zuordnungen.
    • Bedingungen, die nicht deterministische Ausdrücke und Unterabfragen verwenden.
    • Bedingungen, die korrelierte Unterabfragen enthalten.
  • Für MERGE-Befehle müssen Sie ein explizites Prädikat für die Zieltabelle verwenden, um Zeilen zu filtern, die der Quelltabelle entsprechen. Bei der Merge-Auflösung wird der Filter nur zum Überprüfen von Zeilen verwendet, die basierend auf Filterbedingungen in gleichzeitigen Vorgängen in Konflikt stehen können.

Hinweis

Die Konflikterkennung auf Zeilenebene kann die Gesamtausführungszeit erhöhen. Bei vielen gleichzeitigen Transaktionen priorisiert der Writer die Latenz über Konfliktbehebung und Konflikte können auftreten.

Alle Einschränkungen für Löschvektoren gelten ebenfalls. Informationen finden Sie unter Einschränkungen.

Wann führt Delta Lake einen Commit durch, ohne die Tabelle zu lesen?

Bei INSERT- oder Anfügevorgängen in Delta Lake wird der Tabellenstatus vor dem Commit nicht gelesen, wenn die folgenden Bedingungen erfüllt sind:

  1. Logik wird mithilfe der INSERT-SQL-Logik oder des Anfügemodus ausgedrückt.
  2. Die Logik enthält keine Unterabfragen oder Bedingungen, die auf die Tabelle verweisen, die Ziel des Schreibvorgangs ist.

Wie bei anderen Commits überprüft Delta Lake die Tabellenversionen beim Commit anhand von Metadaten im Transaktionsprotokoll und löst sie auf, aber es wird keine Version der Tabelle tatsächlich gelesen.

Hinweis

Viele gängige Muster verwenden MERGE-Vorgänge zum Einfügen von Daten basierend auf Tabellenbedingungen. Obwohl es möglich sein kann, diese Logik mit INSERT-Anweisungen neu zu schreiben, wenn ein bedingter Ausdruck auf eine Spalte in der Zieltabelle verweist, weisen diese Anweisungen die gleichen Parallelitätseinschränkungen wie MERGE auf.

Vergleich der Isolationsstufen WriteSerializable und Serializable

Die Isolationsstufe einer Tabelle legt fest, inwieweit eine Transaktion von Änderungen durch gleichzeitige Transaktionen isoliert sein muss. Delta Lake in Azure Databricks unterstützt zwei Isolationsstufen: Serializable und WriteSerializable.

  • Serializable: die stärkste Isolationsstufe. Sie stellt sicher, dass alle Schreib- und Lesevorgänge serialisierbar sind. Vorgänge sind zulässig, solange es eine serielle Sequenz gibt, in der sie nacheinander ausgeführt werden und das gleiche Ergebnis wie in der Tabelle angegeben erzielt wird. Für die Schreibvorgänge ist die serielle Sequenz genau die gleiche wie im Verlauf der Tabelle.

  • WriteSerializable (Standard): eine schwächere Isolationsstufe als Serializable. Sie stellt lediglich sicher, dass die Schreibvorgänge (d. h. nicht die Lesevorgänge) serialisierbar sind. Sie ist jedoch immer noch stärker als die Momentaufnahmenisolation. WriteSerializable ist die Standardeinstellung für die Isolationsstufe, da sie für die meisten gängigen Vorgänge eine gute Balance zwischen Datenkonsistenz und Verfügbarkeit bietet.

    In diesem Modus kann sich der Inhalt der Delta-Tabelle von demjenigen unterscheiden, der aufgrund der Sequenz der Vorgänge im Tabellenverlauf erwartet wird. Das liegt daran, dass dieser Modus bestimmte Paare gleichzeitiger Schreibvorgänge (z. B. die Vorgänge X und Y) zulässt, sodass das Ergebnis so aussieht, als wäre Y vor X ausgeführt worden (was heißt, dass sie serialisierbar sind), obwohl der Verlauf zeigen würde, dass Y nach X ausgeführt wurde. Um diese Neuordnung zu verhindern, legen Sie die Isolationsstufe der Tabelle auf „Serializable“ fest, um diese Transaktionen fehlschlagen zu lassen.

Für Lesevorgänge gilt stets die Momentaufnahmenisolation. Die Isolationsstufe für Schreibvorgänge legt fest, ob es einem Leser möglich ist, eine Momentaufnahme einer Tabelle zu sehen, die laut Verlauf „nie vorhanden war“.

Auf der Stufe „Serializable“ sieht ein Leser stets nur Tabellen, die dem Verlauf entsprechen. Auf der Stufe WriteSerializable könnte ein Leser eine Tabelle sehen, die im Delta-Protokoll nicht vorhanden ist.

Angenommen, txn1 ist ein zeitintensiver Löschvorgang, und txn2 fügt Daten ein, die von txn1 gelöscht wurden. txn2 und txn1 werden abgeschlossen und in dieser Reihenfolge im Verlauf aufgezeichnet. Dem Verlauf nach sollten die in txn2 eingefügten Daten nicht in der Tabelle vorhanden sein. Auf der Stufe „Serializable“ sähe ein Leser niemals die von txn2 eingefügten Daten. Auf der Stufe WriteSerializable könnte ein Leser jedoch zu einem bestimmten Zeitpunkt die von txn2 eingefügten Daten sehen.

Weitere Informationen dazu, welche Typen von Vorgängen bei den einzelnen Isolationsstufen miteinander in Konflikt stehen und welche Fehler auftreten können, finden Sie unter Vermeiden von Konflikten durch Partitionierung und Bedingungen des Disjoint-Befehls.

Festlegen der Isolationsstufe

Sie legen die Isolationsstufe mithilfe des Befehls ALTER TABLE fest.

ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = <level-name>)

Dabei ist <level-name> entweder Serializable oder WriteSerializable.

Um beispielsweise die Isolationsstufe von der Standardeinstellung WriteSerializable in Serializable zu ändern, führen Sie Folgendes aus:

ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')

Vermeiden von Konflikten durch Partitionierung und Bedingungen des Disjoint-Befehls

In allen Fällen, die als „Kann in Konflikt stehen“ gekennzeichnet sind, hängt das Auftreten des Konflikts davon ab, ob sie für denselben Dateisatz ausgeführt werden. Sie können die beiden Dateisätze voneinander abgrenzen, indem Sie die Tabelle anhand derselben Spalten partitionieren, die in den Bedingungen der Vorgänge verwendet werden. Die beiden Befehle UPDATE table WHERE date > '2010-01-01' ... und DELETE table WHERE date < '2010-01-01' stehen beispielsweise in Konflikt, wenn die Tabelle nicht nach Datum partitioniert ist, da beide versuchen können, denselben Satz von Dateien zu ändern. Durch partitionieren der Tabelle nach date wird der Konflikt vermieden. Daher kann das Partitionieren einer Tabelle anhand der häufig für den Befehl verwendeten Bedingungen Konflikte erheblich reduzieren. Das Partitionieren einer Tabelle anhand einer Spalte mit hoher Kardinalität kann jedoch aufgrund der großen Anzahl von Unterverzeichnissen zu anderen Leistungsproblemen führen.

Konfliktsausnahmen

Wenn es zu einem Transaktionskonflikt kommt, tritt eine der folgenden Ausnahmen auf:

ConcurrentAppendException

Diese Ausnahme tritt auf, wenn ein gleichzeitiger Vorgang Dateien in derselben Partition (oder an einer beliebigen Stelle in einer nicht partitionierten Tabelle) hinzufügt, die ihr Vorgang liest. Dieses Hinzufügen von Dateien kann durch INSERT-, DELETE-, UPDATE- oder MERGE-Vorgänge verursacht werden.

Mit der Standardisolationsstufe von WriteSerializable verursachen Dateien, die durch blindeINSERT-Vorgänge (d. h. Vorgänge, die Daten blind anfügen, ohne Daten zu lesen) hinzugefügt werden, keinen Konflikt mit einem Vorgang, selbst wenn sie dieselbe Partition (oder eine beliebige Stelle in einer nicht partitionierten Tabelle) betreffen. Wenn die Isolationsstufe auf Serializable festgelegt ist, können blinde Anfügungen einen Konflikt verursachen.

Diese Ausnahme wird häufig bei gleichzeitigen DELETE-, UPDATE- oder MERGE-Vorgängen ausgelöst. Während die gleichzeitigen Vorgänge physisch verschiedene Partitionsverzeichnisse aktualisieren, liest einer von ihnen möglicherweise die gleiche Partition, die von einem anderen zeitgleich aktualisiert wird, was zu einem Konflikt führt. Sie können dies vermeiden, indem Sie die Trennung in den Vorgangsbedingungen explizit machen. Betrachten Sie das folgende Beispiel.

// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

Angenommen, Sie führen den obigen Code gleichzeitig für verschiedene Datumsangaben oder Länder aus. Da jeder Auftrag an einer unabhängigen Partition in der Delta-Zieltabelle arbeitet, erwarten Sie keine Konflikte. Die Bedingung ist jedoch nicht explizit genug. Sie kann die gesamte Tabelle überprüfen und mit gleichzeitigen Vorgängen in Konflikt geraten, die andere Partitionen aktualisieren. Stattdessen können Sie Ihre Anweisung umschreiben, um der Verknüpfungsbedingung ein bestimmtes Datum und Land hinzuzufügen, wie im folgenden Beispiel gezeigt.

// Target 'deltaTable' is partitioned by date and country
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country AND t.date = '" + <date> + "' AND t.country = '" + <country> + "'")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

Dieser Vorgang kann jetzt sicher gleichzeitig für verschiedene Datumsangaben und Länder ausgeführt werden.

ConcurrentDeleteReadException

Diese Ausnahme tritt auf, wenn ein gleichzeitiger Vorgang eine Datei gelöscht hat, die ihr Vorgang gelesen hat. Häufige Ursache ist ein DELETE-, UPDATE- oder MERGE-Vorgang, der Dateien neu schreibt.

ConcurrentDeleteDeleteException

Diese Ausnahme tritt auf, wenn ein gleichzeitiger Vorgang eine Datei gelöscht hat, die ihr Vorgang auch löscht. Dies kann durch zwei gleichzeitige Komprimierungsvorgänge verursacht werden, die dieselben Dateien umschreiben.

MetadataChangedException

Diese Ausnahme tritt auf, wenn eine gleichzeitige Transaktion die Metadaten einer Delta-Tabelle aktualisiert. Häufige Ursachen sind ALTER TABLE-Vorgänge oder Schreibvorgänge in Ihrer Delta-Tabelle, die das Schema der Tabelle aktualisieren.

ConcurrentTransactionException

Wenn eine Streamingabfrage, die den gleichen Prüfpunktspeicherort verwendet, mehrmals gleichzeitig gestartet wird und versucht, gleichzeitig in die Delta-Tabelle zu schreiben. Sie sollten nie zwei Streamingabfragen, die denselben Prüfpunktspeicherort verwenden, gleichzeitig ausführen.

ProtocolChangedException

Diese Ausnahme kann in den folgenden Fällen auftreten:

  • Wenn Ihre Delta-Tabelle auf eine neue Protokollversion aktualisiert wird. Damit zukünftige Vorgänge erfolgreich ausgeführt werden können, müssen Sie möglicherweise ein Upgrade Ihrer Databricks Runtime-Version durchführen.
  • Wenn mehrere Writer gleichzeitig eine Tabelle erstellen oder ersetzen.
  • Wenn mehrere Writer gleichzeitig in einen leeren Pfad schreiben.

Weitere Informationen finden Sie unter Wie verwaltet Azure Databricks die Kompatibilität von Delta Lake-Features?

Verhalten der Vorschauversion von Parallelität auf Zeilenebene (Legacy)

In diesem Abschnitt wird das Verhalten der Vorschau von Parallelität auf Zeilenebene in Databricks Runtime 14.1 und niedriger beschrieben. Parallelität auf Zeilenebene erfordert immer Löschvektoren.

In Databricks Runtime 13.3 LTS und höher aktivieren Tabellen mit aktiviertem Liquid Clustering automatisch die Parallelität auf Zeilenebene.

In Databricks Runtime 14.0 und 14.1 können Sie die Parallelität auf Zeilenebene für Tabellen mit Löschvektoren aktivieren, indem Sie die folgende Konfiguration für den Cluster oder die Spark-Sitzung festlegen:

spark.databricks.delta.rowLevelConcurrencyPreview = true

In Databricks Runtime 14.1 und niedriger unterstützt die Nicht-Photon-Computeressource Parallelität auf Zeilenebene nur für DELETE-Vorgänge.