Azure Databricks 上的隔離等級和寫入衝突

數據表的隔離等級會定義交易必須與並行作業所做的修改隔離的程度。 Azure Databricks 上的寫入衝突取決於隔離等級。

Delta Lake 提供讀取和寫入之間的 ACID 交易保證。 這表示:

  • 多個叢集上的多個寫入器可以同時修改數據表分割區。 寫入器會看到數據表的一致快照集檢視,並以序列順序進行寫入。
    • 讀取器會繼續看到 Azure Databricks 作業啟動之數據表的一致快照集檢視,即使在作業期間修改數據表也一致。

請參閱 什麼是 Azure Databricks 上的 ACID 保證?

注意

根據預設,Azure Databricks 會針對所有數據表使用 Delta Lake。 本文說明 Azure Databricks 上 Delta Lake 的行為。

重要

元數據變更會導致所有並行寫入作業失敗。 這些作業包括數據表通訊協議、數據表屬性或數據架構的變更。

串流讀取會在遇到變更數據表元數據的認可時失敗。 如果您想要讓資料流程繼續,您必須加以重新啟動。 如需建議的方法,請參閱 結構化串流的生產考慮。

以下是變更元數據的查詢範例:

-- 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);

寫入與數據列層級並行衝突

數據列層級並行存取可藉由偵測數據列層級的變更,並自動解決並行寫入更新或刪除相同數據檔中不同數據列時發生的衝突,以減少並行寫入作業之間的衝突。

Databricks Runtime 14.2 和更新版本通常會提供數據列層級並行存取。 根據預設,下列條件支援數據列層級並行:

  • 已啟用刪除向量且不含數據分割的數據表。
  • 具有液體群集的數據表,除非您已停用刪除向量。

具有數據分割的數據表不支持數據列層級並行,但在啟用刪除向量時,仍然可以避免與所有其他寫入作業之間的 OPTIMIZE 衝突。 請參閱 數據列層級並行的限制。

如需其他 Databricks 運行時間版本,請參閱數據列層級並行預覽行為(舊版)。

下表描述哪些寫入作業在每 一個隔離等級 中可能會發生衝突,且已啟用數據列層級並行。

注意

具有識別數據行的數據表不支援並行交易。 請參閱 在 Delta Lake 中使用識別數據行。

INSERT (1) UPDATE、DELETE、MERGE INTO 最佳化
INSERT 無法衝突
UPDATE、DELETE、MERGE INTO 無法在 WriteSerializable 中發生衝突。 修改相同的數據列時,可串行化時可能會發生衝突;請參閱 數據列層級並行的限制。 MERGE INTO 需要 Photon 來進行數據列層級衝突解決。 修改相同數據列時可能會發生衝突;請參閱 數據列層級並行的限制。
優化 無法衝突 無法衝突 無法衝突

重要

(1)INSERT 上述數據表中的所有作業都描述在認可之前,未從相同數據表讀取任何數據的附加作業。 INSERT 包含讀取相同數據表之子查詢的作業,支援與 MERGE相同的並行存取。

寫入沒有數據列層級並行的衝突

下表描述哪些寫入作業在每一個 隔離等級中可能會發生衝突。

如果數據表已定義資料分割或未啟用刪除向量,則數據表不支援數據列層級並行存取。 數據列層級並行需要 Databricks Runtime 14.2 或更新版本。

注意

具有識別數據行的數據表不支援並行交易。 請參閱 在 Delta Lake 中使用識別數據行。

INSERT (1) UPDATE、DELETE、MERGE INTO 最佳化
INSERT 無法衝突
UPDATE、DELETE、MERGE INTO 無法在 WriteSerializable 中發生衝突。 可在 Serializable 中發生衝突;請參閱 避免與分割區發生衝突。 可在 Serializable 和 WriteSerializable 中發生衝突;請參閱 避免與分割區發生衝突。
優化 無法衝突 無法在已啟用刪除向量的數據表中發生衝突。 否則可能會發生衝突。 無法在已啟用刪除向量的數據表中發生衝突。 否則可能會發生衝突。

重要

(1)INSERT 上述數據表中的所有作業都描述在認可之前,未從相同數據表讀取任何數據的附加作業。 INSERT 包含讀取相同數據表之子查詢的作業,支援與 MERGE相同的並行存取。

數據列層級並行的限制

某些限制適用於數據列層級並行。 針對下列作業,衝突解決會遵循 Azure Databricks 上寫入衝突的一般並行。 請參閱 寫入沒有數據列層級並行的衝突。

  • OPTIMIZE 具有 ZORDER BY的命令。
  • 具有複雜條件子句的命令,包括下列專案:
    • 複雜數據類型的條件,例如結構、數位或對應。
    • 使用不具決定性表達式和子查詢的條件。
    • 包含相互關聯子查詢的條件。
  • 針對 MERGE 命令,您必須在目標數據表上使用明確的述詞來篩選符合源數據表的數據列。 針對合併解析,篩選只會掃描根據並行作業中篩選條件可能會衝突的數據列。

注意

數據列層級衝突偵測可能會增加總運行時間。 如果是許多並行交易,寫入器會優先處理衝突解決的延遲,而且可能發生衝突。

刪除向量的所有限制也都適用。 請參閱 限制

Delta Lake 何時認可而不讀取數據表?

如果符合下列條件,Delta Lake INSERT 或 append 作業在認可之前不會讀取數據表狀態:

  1. 邏輯會使用 INSERT SQL 邏輯或附加模式來表示。
  2. 邏輯不包含子查詢或條件,可參考寫入作業目標數據表。

如同其他認可,Delta Lake 會使用事務歷史記錄中的元數據來驗證並解析認可上的數據表版本,但實際上不會讀取數據表版本。

注意

許多常見的模式會使用 MERGE 作業,根據數據表條件插入數據。 雖然可以使用 語句重寫此邏輯 INSERT ,但如果有任何條件表達式參考目標數據表中的數據行,這些語句的並行限制 MERGE與 相同。

寫入可串行化與可串行化隔離等級

數據表的隔離等級會定義交易必須與並行交易所做的修改隔離的程度。 Azure Databricks 上的 Delta Lake 支援兩個隔離等級:Serializable 和 WriteSerializable。

  • 串行化:最強的隔離等級。 它可確保認可的寫入作業和所有讀取都是 串行化的。 只要有一連串的作業,就會允許執行作業,以產生與數據表中所見相同的結果。 針對寫入作業,序列順序與數據表記錄中所見完全相同。

  • WriteSerializable (Default):比 Serializable 弱的隔離等級。 它只會確保寫入作業(也就是不是讀取)可串行化。 不過,這仍然比 快照集 隔離更強。 WriteSerializable 是預設隔離等級,因為它可為最常見的作業提供數據一致性和可用性的絕佳平衡。

    在此模式中,Delta 數據表的內容可能與數據表歷程記錄中所見的作業順序不同。 這是因為此模式允許特定配對的並行寫入(例如,作業 X 和 Y)繼續,如此一來,即使歷程記錄會顯示在 X 之後認可 Y,結果會如同在 X 之前執行 Y 一樣。若要不允許重新排序, 請將數據表隔離等級 設定為可串行化,讓這些交易失敗。

讀取作業一律使用快照集隔離。 寫入隔離等級會判斷讀取器是否可以看到數據表的快照集,根據歷程記錄「永不存在」。

針對可串行化層級,讀取器一律只會看到符合歷程記錄的數據表。 針對 WriteSerializable 層級,讀取器可以看到差異記錄檔中不存在的數據表。

例如,假設 txn1 是長時間執行的 delete 和 txn2,它會插入 txn1 所刪除的數據。 txn2 和 txn1 完成,並記錄在歷程記錄中的順序。 根據歷程記錄,在 txn2 中插入的數據不應該存在於數據表中。 針對可串行化層級,讀取器永遠不會看到 txn2 插入的數據。 不過,針對 WriteSerializable 層級,讀取器可能會在某個時間點看到 txn2 所插入的數據。

如需在每一個隔離等級和可能的錯誤中,哪些作業類型可能會彼此衝突的詳細資訊,請參閱 避免使用數據分割和脫離命令條件的衝突。

設定隔離等級

您可以使用 命令來設定隔離等級 ALTER TABLE

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

其中 <level-name>SerializableWriteSerializable

例如,若要將隔離等級從預設值 WriteSerializable 變更為 Serializable,請執行:

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

避免使用數據分割和脫離命令條件的衝突

在所有標示為「可能衝突」的案例中,這兩個作業是否會衝突取決於兩個作業是否在同一組檔案上運作。 您可以將資料表分割成與作業條件中使用的相同資料行,讓這兩組檔案不相交。 例如,如果資料表不是依日期分割,則 UPDATE table WHERE date > '2010-01-01' ...DELETE table WHERE date < '2010-01-01' 這兩個命令會衝突,因為兩者都可以嘗試修改相同的檔案集。 分割數據表 date 的方式將避免衝突。 因此,根據命令上常用的條件分割數據表可能會大幅減少衝突。 不過,依具有高基數的數據行分割數據表可能會導致其他效能問題,因為大量的子目錄。

衝突例外狀況

發生交易衝突時,您會看到下列其中一個例外狀況:

ConcurrentAppendException

當並行作業將檔案新增至與您的作業讀取相同的分割區 (或未分割資料表中的任何位置) 時,就會發生此例外狀況。 檔案新增可能是由 INSERTDELETEUPDATEMERGE 作業所造成。

使用的預設隔離等級WriteSerializable時,盲INSERT目作業所新增的檔案(也就是盲目附加數據而不讀取任何數據的作業)不會與任何作業衝突,即使它們觸及相同的分割區(或未分割數據表中的任何位置)。 如果隔離等級設定為 Serializable,則盲目附加可能會衝突。

此例外狀況通常會在並行 DELETEUPDATEMERGE 作業期間擲回。 雖然並行作業可能會實際更新不同的分割區目錄,但其中一個作業可能會讀取另一個同時更新的相同分割區,因而造成衝突。 您可以藉由在作業條件中明確區分,以避免這種情況。 請思考一下下列範例。

// 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()

假設您同時針對不同的日期或國家/地區執行上述程式碼。 由於每個作業都處理目標 Delta 資料表上的獨立分割區,因此您不會預期發生任何衝突。 不過,條件不夠明確,而且可以掃描整個資料表,而且可能會與更新任何其他資料分割的並行作業發生衝突。 相反地,您可以重寫陳述式,將特定日期和國家/地區新增至合併條件,如下列範例所示。

// 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()

此作業現在安全,可在不同的日期和國家/地區同時執行。

ConcurrentDeleteReadException

當並行作業刪除作業讀取的檔案時,就會發生此例外狀況。 常見原因是重寫檔案的 DELETEUPDATEMERGE 作業。

ConcurrentDeleteDeleteException

當並行作業刪除作業也會刪除作業也會刪除的檔案時,就會發生此例外狀況。 這可能是由兩個並行壓縮作業重寫相同的檔案所造成。

MetadataChangedException

當並行交易更新 Delta 數據表的元數據時,就會發生這個例外狀況。 常見的原因是 ALTER TABLE 作業或寫入您的 Delta 數據表,以更新數據表的架構。

ConcurrentTransactionException

如果使用相同檢查點位置的串流查詢會同時啟動多次,並嘗試同時寫入 Delta 數據表。 您不應該有兩個串流查詢使用相同的檢查點位置,並同時執行。

ProtocolChangedException

下列情況可能會發生此例外狀況:

  • 當您的 Delta 數據表升級至新的通訊協定版本時。 若要讓未來的作業成功,您可能需要升級 Databricks Runtime。
  • 當多個寫入器同時建立或取代資料表時。
  • 當多個寫入器同時寫入空路徑時。

如需詳細資訊,請參閱 Azure Databricks 如何管理 Delta Lake 功能相容性?

資料列層級並行預覽行為 (舊版)

本節說明 Databricks Runtime 14.1 和以下數據列層級並行的預覽行為。 數據列層級並行一律需要刪除向量。

在 Databricks Runtime 13.3 LTS 和更新版本中,已啟用液體叢集的數據表會自動啟用數據列層級並行。

在 Databricks Runtime 14.0 和 14.1 中,您可以設定叢集或 SparkSession 的下列設定,為具有刪除向量的數據表啟用數據列層級並行:

spark.databricks.delta.rowLevelConcurrencyPreview = true

在 Databricks Runtime 14.1 和更新版本中,非 Photon 計算僅支援作業的數據 DELETE 列層級並行。