數據庫上的隔離級別和寫入衝突
表的隔離級別定義了必須將事務與並發操作所做的修改隔離的程度。數據庫上的寫衝突取決於隔離級別。
Delta Lake在讀寫之間提供ACID事務保證。這意味著:
跨多個集群的多個寫入器可以同時修改一個表分區。寫入者會看到表的一致快照視圖,寫入以串行順序進行。
讀者可以繼續看到Databricks作業開始時使用的表的一致快照視圖,即使在作業期間對表進行了修改。
請注意
默認情況下,數據庫對所有表都使用Delta Lake。本文描述了Delta Lake在Databricks上的行為。
Delta Lake什麼時候不讀表就提交了?
三角洲湖插入
或者,如果滿足以下條件,追加操作在提交前不讀取表狀態:
邏輯是用
插入
SQL邏輯或追加模式。邏輯不包含引用寫操作所針對的表的子查詢或條件。
與其他提交一樣,Delta Lake在提交時使用事務日誌中的元數據驗證和解析表版本,但實際上不讀取表的版本。
請注意
許多常用模式使用合並
根據表條件插入數據的操作。盡管可以使用。重寫此邏輯插入
語句,對目標表中數據條件的任何引用都會觸發相同的並發限製合並
.
將衝突寫入數據庫
下表描述了每個操作中可能發生衝突的寫操作對隔離級別.
請注意
帶有標識列的表不支持並發事務。看到在Delta Lake中使用身份欄.
插入(1) |
更新、刪除、合並成 |
優化 |
|
---|---|---|---|
插入 |
不衝突 |
||
更新、刪除、合並成 |
可以在Serializable衝突,不能在WriteSerializable衝突 |
可以在Serializable和WriteSerializable衝突 |
|
優化 |
不衝突 |
可以在Serializable和WriteSerializable衝突 |
可以在Serializable和WriteSerializable衝突 |
重要的
(1)所有插入
上表中的操作描述了在提交前不從同一個表中讀取任何數據的追加操作。插入
包含讀同一表的子查詢的操作支持相同的並發性合並
.
編寫可序列化與可序列化的隔離級別
表的隔離級別定義了必須將事務與並發事務所做的修改隔離的程度。Databricks上的Delta Lake支持兩種隔離級別:Serializable和WriteSerializable。
可序列化的:最強隔離級別。它確保提交的寫操作和所有的讀操作都是正確的可序列化的.隻要存在一個每次執行一個操作的序列,生成與表中所示相同的結果,操作就被允許。對於寫操作,串行序列與表曆史中看到的完全相同。
WriteSerializable(默認):比Serializable隔離級別弱的隔離級別。它隻確保寫操作(也就是說,不是讀操作)是可序列化的。不過,這還是強於快照隔離。WriteSerializable是默認的隔離級別,因為它為大多數常見操作在數據一致性和可用性方麵提供了很好的平衡。
在這種模式下,Delta表的內容可能不同於從表曆史中看到的操作序列所期望的內容。這是因為這種模式允許某些並發寫對(比如,操作X和Y)繼續進行,這樣的結果就好像Y在X之前執行(也就是說,它們之間是可序列化的),即使曆史記錄顯示Y在X之後提交。設置表隔離級別Serializable,從而導致這些事務失敗。
讀操作始終使用快照隔離。寫隔離級別決定了讀寫器是否可以看到根據曆史記錄“從未存在過”的表的快照。
對於Serializable級別,讀取器總是隻看到符合曆史記錄的表。對於WriteSerializable級別,讀者可以看到Delta日誌中不存在的表。
例如,考慮txn1,一個長時間運行的刪除和txn2,它插入由txn1刪除的數據。Txn2和txn1是完整的,它們在曆史中按此順序被記錄。根據曆史記錄,txn2中插入的數據應該不存在於表中。對於Serializable級別,閱讀器永遠不會看到txn2插入的數據。但是,對於WriteSerializable級別,讀者在某些時候可以看到txn2插入的數據。
有關每個隔離級別中哪些類型的操作可能相互衝突以及可能的錯誤的詳細信息,請參見使用分區和分離命令條件避免衝突.
設置隔離級別
屬性設置隔離級別改變表格
命令。
改變表格<表格-的名字>集TBLPROPERTIES(“delta.isolationLevel”=<水平-的名字>)
在哪裏<級別名稱>
是可序列化的
或WriteSerializable
.
例如,將隔離級別更改為默認級別WriteSerializable
來可序列化的
運行:
改變表格<表格-的名字>集TBLPROPERTIES(“delta.isolationLevel”=“序列化”)
使用分區和分離命令條件避免衝突
在所有標記為“can conflict”的情況下,兩個操作是否會衝突取決於它們是否對同一組文件進行操作。通過按照操作條件中使用的列對表進行分區,可以使這兩組文件分離。例如,這兩個命令更新表格在哪裏日期>“2010-01-01”...
而且刪除表格在哪裏日期<“2010-01-01”
如果表沒有按日期分區,則會發生衝突,因為兩者都可以嚐試修改同一組文件。對表進行分區日期
會避免衝突。因此,根據命令上常用的條件對表進行分區可以顯著減少衝突。但是,根據具有高基數的列對表進行分區可能會導致其他性能問題,因為有大量的子目錄。
衝突異常
當事務衝突發生時,您將觀察到以下異常之一:
ConcurrentAppendException
當並發操作在您的操作讀取的同一個分區(或未分區表中的任何位置)中添加文件時,會發生此異常。文件添加可能由插入
,刪除
,更新
,或合並
操作。
使用默認隔離級別的WriteSerializable
,由盲目的插入
操作(即盲目地追加數據而不讀取任何數據的操作)不會與任何操作發生衝突,即使它們涉及相同的分區(或未分區表中的任何位置)。如果隔離級別設置為可序列化的
,那麼盲目的追加可能會衝突。
此異常通常在並發期間拋出刪除
,更新
,或合並
操作。雖然並發操作可能在物理上更新不同的分區目錄,但其中一個操作可能讀取另一個並行更新的相同分區,從而導致衝突。可以通過在操作條件中顯式地分離來避免這種情況。考慮下麵的例子。
//目標'deltaTable'按日期和國家劃分deltaTable.作為(“t”).合並(源.作為(“s”),“s.user_id = t.user_id AND s.date = t.date AND s.country = t.country”).whenMatched().updateAll().whenNotMatched().insertAll().執行()
假設您針對不同的日期或國家同時運行上述代碼。由於每個作業都在目標Delta表上的獨立分區上工作,因此不會有任何衝突。但是,這個條件不夠顯式,可以掃描整個表,並且可能與更新任何其他分區的並發操作衝突。相反,您可以重寫語句,向合並條件添加特定的日期和國家,如下例所示。
//目標'deltaTable'按日期和國家劃分deltaTable.作為(“t”).合並(源.作為(“s”),"s.user_id = t.user_id AND s.date = t.date AND s.country = t.country AND t.date = '"+<日期>+" AND t.country = '"+<國家>+“”).whenMatched().updateAll().whenNotMatched().insertAll().執行()
該操作現在可以安全地在不同的日期和國家同時運行。
ProtocolChangedException
此異常可在以下情況下發生:
當Delta表升級到新的協議版本時。為使以後的操作成功,您可能需要升級Databricks運行時。
當多個寫入器同時創建或替換一個表時。
當多個寫入器同時寫入一個空路徑時。
看到表協議版本控製欲知詳情。