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.
Wichtig
Metadatenänderungen führen dazu, dass alle gleichzeitigen Schreibvorgänge fehlschlagen. Zu diesen Vorgängen gehören Änderungen am Tabellenprotokoll, an Tabelleneigenschaften oder Datenschemas.
Streaminglesevorgänge schlagen fehl, wenn ein Commit auftritt, der Tabellenmetadaten ändert. Wenn der Stream fortgesetzt werden soll, müssen Sie ihn neu starten. Empfohlene Methoden finden Sie unter Produktionsüberlegungen für strukturiertes Streaming.
Im Folgenden sind Beispiele für Abfragen aufgeführt, die Metadaten ändern:
-- Set a table property.
ALTER TABLE table-name SET TBLPROPERTIES ('delta.isolationLevel' = 'Serializable')
-- Enable a feature using a table property and update the table protocol.
ALTER TABLE table_name SET TBLPROPERTIES ('delta.enableDeletionVectors' = true);
-- Drop a table feature.
ALTER TABLE table_name DROP FEATURE deletionVectors;
-- Upgrade to UniForm.
REORG TABLE table_name APPLY (UPGRADE UNIFORM(ICEBERG_COMPAT_VERSION=2));
-- Update the table schema.
ALTER TABLE table_name ADD COLUMNS (col_name STRING);
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).
Für die Unterstützung von MERGE INTO
für Parallelität auf Zeilenebene ist Photon in Databricks Runtime 14.2 erforderlich. In Databricks Runtime 14.3 LTS und höher ist Photon nicht erforderlich.
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 Serialisierbar (serializable) einen Konflikt verursachen. Siehe Einschränkungen für Parallelität auf Zeilenebene. | Kann beim Ändern derselben Zeile Konflikte verursachen. Siehe Einschränkungen für Parallelität auf Zeilenebene. | |
OPTIMIZE | Kein Konflikt möglich | Kann einen Konflikt verursachen, wenn ZORDER BY verwendet wird. Andernfalls kann kein Konflikt auftreten. |
Kann einen Konflikt verursachen, wenn ZORDER BY verwendet wird. Andernfalls kann kein 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
.
REORG
-Vorgänge haben die gleiche Isolationsemantik wie OPTIMIZE
, wenn Datendateien umgeschrieben werden, um die in Löschvektoren aufgezeichneten Änderungen widerzuspiegeln. Wenn Sie mit REORG
ein Upgrade anwenden, ändern sich die Tabellenprotokolle, was für Konflikte mit allen laufenden Vorgängen sorgt.
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 in Serialisierbar (serializable) einen Konflikt verursachen. Weitere Informationen unter Vermeiden von Konflikten mit Partitionen. | Kann in „Serializable“ und „WriteSerializable“ in Konflikt stehen. Weitere Informationen unter Vermeiden von Konflikten mit Partitionen. | |
OPTIMIZE | Kein Konflikt möglich | Kann keinen Konflikt in Tabellen verursachen, für die Löschvektoren aktiviert sind, außer es wird ZORDER BY verwendet. Andernfalls kann ein Konflikt auftreten. |
Kann keinen Konflikt in Tabellen verursachen, für die Löschvektoren aktiviert sind, außer es wird ZORDER BY verwendet. 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
.
REORG
-Vorgänge haben die gleiche Isolationsemantik wie OPTIMIZE
, wenn Datendateien umgeschrieben werden, um die in Löschvektoren aufgezeichneten Änderungen widerzuspiegeln. Wenn Sie mit REORG
ein Upgrade anwenden, ändern sich die Tabellenprotokolle, was für Konflikte mit allen laufenden Vorgängen sorgt.
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.
- 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.
- In Databricks Runtime 14.2 müssen Sie für
MERGE
-Befehle ein explizites Prädikat in der 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:
- Logik wird mithilfe der
INSERT
-SQL-Logik oder des Anfügemodus ausgedrückt. - 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 WriteSerializable
verursachen Dateien, die durch blinde INSERT
-Vorgänge (d. h. Vorgänge, die Daten blind anfügen, ohne Daten zu lesen) hinzugefügt werden, keinen Konflikt mit anderen Vorgängen, selbst wenn sie für dieselbe Partition (oder eine beliebige Stelle in einer nicht partitionierten Tabelle) ausgeführt werden. 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.