數據表的隔離等級會定義交易必須與並行作業所做的修改隔離的程度。 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 運行時間版本,請參閱列層級並行預覽行為(舊版)。
MERGE INTO 行級並行的支持需要在 Databricks Runtime 14.2 中使用 Photon。 在 Databricks Runtime 14.3 LTS 和更新版本中,不需要 Photon。
下表描述在啟用行層級併發的情況下,哪些寫入作業在每一個隔離級別中可能會發生衝突。
備註
具有識別數據行的數據表不支援並行交易。 請參閱 在 Delta Lake 中使用識別欄位。
| INSERT (1) | UPDATE、DELETE、MERGE INTO | OPTIMIZE | |
|---|---|---|---|
| INSERT | 不可衝突 | ||
| UPDATE、DELETE、MERGE INTO | 無法在 WriteSerializable 中發生衝突。 修改相同數據列時,可串行化中的衝突。 請參閱 數據列層級並行的限制。 | 修改相同數據列時可能會發生衝突。 請參閱 數據列層級並行的限制。 | |
| OPTIMIZE | 不可衝突 | 在使用ZORDER BY時可能會發生衝突。 否則無法衝突。 |
在使用ZORDER BY時可能會發生衝突。 否則無法衝突。 |
重要
(1)INSERT 上述表格中的所有操作都描述了在提交之前,附加操作不會從相同的表格中讀取任何數據。
INSERT 包含讀取相同數據表之子查詢的作業,支援與 MERGE相同的並行存取。
REORG 作業的隔離語意與重寫數據檔時相同 OPTIMIZE ,以反映刪除向量中記錄的變更。 當您使用 REORG 來套用升級時,數據表通訊協議會變更,這與所有進行中的作業衝突。
寫入沒有數據列層級並行的衝突
下表描述哪些寫入作業在每一個 隔離等級中可能會發生衝突。
如果數據表已定義資料分割或未啟用刪除向量,則數據表不支援數據列層級並行存取。 數據列層級並行需要 Databricks Runtime 14.2 或更新版本。
備註
具有識別數據行的數據表不支援並行交易。 請參閱 在 Delta Lake 中使用識別欄位。
| INSERT (1) | UPDATE、DELETE、MERGE INTO | OPTIMIZE | |
|---|---|---|---|
| INSERT | 不可衝突 | ||
| UPDATE、DELETE、MERGE INTO | 無法在 WriteSerializable 中發生衝突。 可在 Serializable 中發生衝突。 請參閱 避免與磁碟分割區發生衝突。 | 可在 Serializable 和 WriteSerializable 中發生衝突。 請參閱 避免與磁碟分割區發生衝突。 | |
| OPTIMIZE | 不可衝突 | 除非使用ZORDER BY,否則無法在啟用刪除向量的數據表中避免衝突。 否則可能會發生衝突。 |
除非使用ZORDER BY,否則無法在啟用刪除向量的數據表中避免衝突。 否則可能會發生衝突。 |
重要
(1)INSERT 上述表格中的所有操作都描述了在提交之前,附加操作不會從相同的表格中讀取任何數據。
INSERT 包含讀取相同數據表之子查詢的作業,支援與 MERGE相同的並行存取。
REORG 作業的隔離語意與重寫數據檔時相同 OPTIMIZE ,以反映刪除向量中記錄的變更。 當您使用 REORG 來套用升級時,數據表通訊協議會變更,這與所有進行中的作業衝突。
資料列層級並行性的限制
某些限制適用於數據列層級並行。 針對下列作業,衝突解決遵循 Azure Databricks 上寫入衝突的標準併發處理。 請參閱 在沒有行級並行情況下的寫入衝突。
- 具有複雜條件子句的命令,包括下列項目:
- 複雜數據類型的條件,例如結構體、數組或映射。
- 使用不具決定性表達式和子查詢的條件。
- 包含相互關聯子查詢的條件。
- 在 Databricks Runtime 14.2 中,
MERGE命令必須使用目標數據表上的明確述詞來篩選符合源數據表的數據列。 針對合併解析,篩選只會掃描可能根據並行作業中的篩選條件而衝突的數據列。
備註
數據列層級衝突偵測可能會增加總運行時間。 在許多並行交易的情況下,寫入器優先考慮延遲而非衝突解決,因此可能發生衝突。
刪除向量的所有限制也都適用。 請參閱限制。
Delta Lake 何時認可而不檢視資料表?
如果符合下列條件,Delta Lake INSERT 或附加作業在提交之前不會讀取資料表狀態:
- 邏輯會使用
INSERTSQL 邏輯或附加模式來表示。 - 邏輯不包含子查詢或條件,這些都不參考寫入作業目標的數據表。
如同其他提交,Delta Lake 會使用交易日誌中的元數據來驗證並解析提交時的數據表版本,但實際上不會讀取數據表的版本。
備註
許多常見的模式會使用 MERGE 作業,根據數據表條件插入數據。 雖然可以用INSERT語句重寫此邏輯,但如果有任何條件表達式參考目標表格中的數據列,這些語句會有與MERGE相同的並行限制。
寫入可串行化與可串行化隔離等級
數據表的隔離等級會定義交易必須與並行交易所做的修改隔離的程度。 Azure Databricks 上的 Delta Lake 支援兩個隔離等級:Serializable 和 WriteSerializable。
可串行化:最強的隔離等級。 它可確保認可的寫入作業和所有讀取都是 可串行化的。 只要存在一個逐一執行作業的序列,且它產生的結果與表中所見結果相同,則允許執行這些作業。 針對寫入作業,序列順序與數據表記錄中所見完全相同。
WriteSerializable (Default):比 Serializable 弱的隔離等級。 它只會確保寫入作業(也就是不是讀取)可串行化。 不過,這仍然比 快照集 隔離更強。 WriteSerializable 是預設隔離等級,因為它可為最常見的作業提供數據一致性和可用性的絕佳平衡。
在此模式中,Delta 數據表的內容可能與數據表歷程記錄中所見的作業順序不同。 這是因為此模式允許特定配對的並行寫入(例如,作業 X 和 Y)繼續進行,使得結果會如同 Y 是在 X 之前執行一樣,儘管歷程顯示 Y 在 X 之後才提交。若要防止這種重新排序,請將資料表隔離級別設為可序列化,以使這些事務失敗。
讀取作業一律使用快照隔離。 寫入隔離級別決定讀取器是否能看到依照歷史記錄「從不存在」的數據表快照。
針對可串行化層級,讀取器一律只會看到符合歷程記錄的數據表。 針對 WriteSerializable 層級,讀取器可能會看到在 Delta 日誌中不存在的資料表。
例如,考慮長時間執行的刪除事務和插入事務同時啟動並讀取版本 v0的場景。 插入事務首先提交並創建版本 v1。 之後,刪除事務會嘗試提交 v2:
t0: deleteTxn_START
t1: insertTxn_START
t2: insertTxn_COMMIT(v1)
t3: deleteTxn_COMMIT(v2)
在此案例中, deleteTxn 未看到 插入 insertTxn 的資料,因此不會刪除它們:
- 在隔離下
Serializable,deleteTxn不允許犯規,發生衝突。 - 在隔離下
WriteSerializable,允許認可,deleteTxn因為可以排序交易。 產生的表格狀態就像insertTxndeleteTxn發生在之後一樣,因此插入的列是表格的一部分。 不過,差異歷程記錄會顯示實體認可順序,且insertTxn(v1) 發生在 (v2)deleteTxn之前。
如需了解在每一個隔離等級中,哪些類型的操作可能會互相衝突,以及可能產生的錯誤,請參閱 使用分區和不相交命令條件來避免衝突。
設定隔離等級
您可以使用 命令來設定隔離等級 ALTER TABLE 。
ALTER TABLE <table-name> SET TBLPROPERTIES ('delta.isolationLevel' = <level-name>)
其中 <level-name> 是 Serializable 或 WriteSerializable。
例如,若要將隔離等級從預設值 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)
當並行作業將檔案新增至與您的作業讀取相同的分割區 (或未分割資料表中的任何位置) 時,就會發生此例外狀況。 檔案新增可能是由 INSERT、 DELETE、 UPDATE或 MERGE 作業所造成。
使用預設的隔離級別時,透過盲目操作(即不先讀取資料而直接附加資料的操作)新增的檔案不會與任何操作發生衝突,即使它們涉及相同的分割區或未分割數據表中的任何位置。 如果隔離等級設定為 Serializable,則盲目附加可能會衝突。
重要:如果執行 WriteSerializable、DELETE或 UPDATE 作業的多個並行交易可能會參考盲目附加所插入的值,盲目附加在 MERGE 模式中可能會發生衝突。 若要避免此衝突,請執行下列其中一項:
- 請確定並行
DELETE、UPDATE或MERGE作業不會讀取附加的數據。 - 最多只能有一個可讀取附加數據的
DELETE、UPDATE或MERGE作業。
此例外狀況通常會在並行 DELETE、 UPDATE或 MERGE 作業期間擲回。 雖然並行作業可能會實際更新不同的分割區目錄,但其中一個作業可能會讀取另一個同時更新的相同分割區,因而造成衝突。 您可以藉由在作業條件中明確區分,以避免這種情況。 請思考一下下列範例。
// 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
當並行作業刪除作業讀取的檔案時,就會發生此例外狀況。 常見原因是重寫檔案的 DELETE、 UPDATE或 MERGE 作業。
ConcurrentDeleteDelete例外狀況
當並行作業刪除您作業所要刪除的檔案時,就會發生此例外狀況。 這可能是由兩個並行壓縮作業重寫相同的檔案所造成。
MetadataChangedException
當並行交易更新 Delta 數據表的元數據時,就會發生這個例外狀況。 常見的原因是 ALTER TABLE 操作或寫入您的 Delta 資料表,以更新資料表的結構。
並發交易異常
如果使用相同檢查點位置的串流查詢會同時啟動多次,並嘗試同時寫入 Delta 數據表。 您不應該有兩個串流查詢使用相同的檢查點位置,並同時執行。
協定更改例外 (ProtocolChangedException)
下列情況可能會發生此例外狀況:
- 當您的 Delta 數據表升級至新的通訊協定版本時。 若要讓未來的作業成功,您可能需要升級 Databricks Runtime。
- 當多位撰寫者同時建立或取代資料表時。
- 當多個寫入器同時寫入空路徑時。
如需詳細資訊 ,請參閱 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 作業的列層級並行。