可序列化隔離 - Amazon Redshift

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

可序列化隔離

有些應用程式需要的不僅僅是並行查詢和載入,還有並行寫入多個資料表或相同的資料表的能力。在此情況下,並行表示重疊,而不是排程在完全相同的時間執行。如果第二個交易在第一個認可之前開始,則會把兩個交易會視為並行。並行操作可能源自受相同使用者或不同使用者控制的不同工作階段。

注意

Amazon Redshift 支援預設的自動遞交行為,其中每個分開執行的 SQL 命令都會個別遞交。如果您在交易區塊中含括一組命令 (由 BEGIN結束 陳述式定義),該區塊會以一個交易的形式認可,因此您可以在必要時加以復原。這種行為的例外是 TRUNCATE 和 VACUUM 命令,這兩種命令會自動遞交所有目前交易中未完成的變更。

某些 SQL 用戶端會自動發出 BEGIN 和 COMMIT 命令,讓用戶端控制要將陳述式群組做為交易執行,還是將每個個別的陳述式做為單獨的交易執行。請查看您正在使用的界面文件。例如,使用 Amazon Redshift JDBC 驅動程式時,附帶其中包含多個 (以分號區隔) SQL 命令查詢字串的 JDBC PreparedStatement 會將所有陳述式做為單一交易執行。相反地,如果您使用 SQL Workbench/J 並設定 AUTO COMMIT ON,則當您執行多個陳述式時,每個陳述式都會做為單獨的交易執行。

Amazon Redshift 中是使用資料表上的寫入鎖定及可序列化隔離的原則,以保護的方式支援並行寫入操作。可序列化隔離可維持對資料表執行的交易對該資料表執行的唯一交易的錯覺。例如,兩個並行執行的交易,T1 和 T2,必須產生與下列至少一項相同的結果:

  • T1 和 T2 會依照該順序循序執行。

  • T2 和 T1 會依照該順序循序執行。

並行交易彼此看不見對方;它們無法偵測彼此的變更。每個並行交易將在交易開始時建立資料庫的快照。資料庫快照會在多數 SELECT 陳述式、DML 命令 (例如 COPY、DELETE、INSERT、UPDATE 和 TRUNCATE),以及下列 DDL 命令的第一個出現的交易內建立:

  • ALTER TABLE (用來新增或捨棄資料欄)

  • CREATE TABLE

  • DROP TABLE

  • TRUNCATE TABLE

如果並行交易的任何序列執行會產生與其並行執行相同的結果,這些交易會被視為「可序列化」並且可安全地執行。如果這些交易中沒有任何序列執行會產生相同的結果,系統會停止和還原所執行陳述式可能破壞序列化可行性的交易。

系統目錄資料表 (PG) 和其他 Amazon Redshift 系統資料表 (STL 和 STV) 不會在交易中鎖定。因此,DDL 和 TRUNCATE 操作所產生的資料庫物件變更,都會在認可至任何並行交易時顯示出來。

例如,假設當兩個並行交易 T1 和 T2 開始時,資料表 A 存在於資料庫。假設 T2 從 PG_TABLES 目錄資料表中進行選取,傳回了資料表清單。然後 T1 捨棄資料表 A 並遞交,然後 T2 再次列出資料表。資料表 A 現在不會再出現在清單中。如果 T2 嘗試查詢已捨棄的資料表,Amazon Redshift 會傳回「關聯不存在」錯誤。傳回資料表的清單至 T2 或檢查資料表 A 存在的類別查詢,不會受限於與對使用者資料表執行的相同隔離規則。

對這些資料表更新的交易會在讀取已認可隔離模式中執行。PG 字首類別資料表不支援快照隔離。

系統資料表和類別資料表的可序列化隔離

資料庫快照也會在參考使用者建立的資料表或 Amazon Redshift 系統資料表 (STL 或 STV) 的任何 SELECT 查詢的交易中建立。不參考任何資料表的 SELECT 查詢不會建立新的交易資料庫快照。僅在系統目錄資料表 (PG) 上運作的 INSERT、DELETE 和 UPDATE 陳述式也不會建立新的交易資料庫快照。

如何修正可序列化隔離錯誤

錯誤:1023 詳細資訊:Redshift 中的資料表上發生可序列化隔離違規

當 Amazon Redshift 偵測到可序列化隔離錯誤時,您會看到錯誤訊息,如下所示。

ERROR:1023 DETAIL: Serializable isolation violation on table in Redshift

若要解決可序列化隔離錯誤,您可以嘗試以下方法:

  • 重試已取消的交易。

    Amazon Redshift 偵測到並行工作負載不可序列化。其表明應用程序邏輯中有差距,此問題通常可以透過重試遇到錯誤的交易來解決。如果問題仍然存在,請嘗試使用其他方法。

  • 將任何無須位於同一不可分割交易中的操作,移到交易之外。

    當兩個交易中的個別操作以可能影響另一個交易結果的方式交叉參考時,適用此方法。例如,以下兩個工作階段各自啟動一個交易。

    Session1_Redshift=# begin;
    Session2_Redshift=# begin;

    每個交易中 SELECT 陳述式的結果,可能會受到另一個交易中 INSERT 陳述式的影響。換句話說,假設您以任何順序,依序執行以下陳述式。在每種情況下,結果都是其中一個 SELECT 陳述式會比同時執行全部交易多傳回一行。依序執行的操作中,沒有任何執行順序可以產生與並行執行相同的結果。因此,最後一個執行的操作會導致可序列化隔離錯誤。

    Session1_Redshift=# select * from tab1; Session1_Redshift=# insert into tab2 values (1);
    Session2_Redshift=# insert into tab1 values (1); Session2_Redshift=# select * from tab2;

    在許多情況下,SELECT 陳述式的結果並不重要。換句話說,交易中操作的不可分割性並不重要。在這些情況下,將 SELECT 陳述式移到交易之外,如以下範例所示。

    Session1_Redshift=# begin; Session1_Redshift=# insert into tab1 values (1) Session1_Redshift=# end; Session1_Redshift=# select * from tab2;
    Session2_Redshift # select * from tab1; Session2_Redshift=# begin; Session2_Redshift=# insert into tab2 values (1) Session2_Redshift=# end;

    在這些範例中,交易中沒有交叉參考。兩個 INSERT 陳述式不會互相影響。在這些範例中,至少有一個順序,可讓交易依序執行並產生與並行執行相同的結果。這表示交易是可序列化的。

  • 請透過鎖定每個工作階段中的所有資料表來強制序列化。

    LOCK 命令能封鎖可能導致可序列化隔離錯誤的操作。使用 LOCK 命令時,請務必執行以下操作:

    • 鎖定受交易影響的所有資料表,包括受交易內部唯讀 SELECT 陳述式影響的資料表。

    • 無論操作的執行順序為何,均以相同的順序鎖定資料表。

    • 在交易開頭,執行任何操作之前,先鎖定所有資料表。

  • 對並行交易使用快照隔離

    使用 ALTER DATABASE 命令搭配快照隔離。若要深入了解 ALTER DATABASE 的 SNAPSHOT 參數,請參閱 參數

錯誤:1018 詳細資訊:關聯不存在

當您在不同的工作階段中執行並行 Amazon Redshift 操作時,您會看到類似如下所示的錯誤訊息。

ERROR: 1018 DETAIL: Relation does not exist.

Amazon Redshift 中的交易會遵循快照隔離。交易開始後,Amazon Redshift 會擷取資料庫的快照。對於交易的整個生命週期,交易會根據快照中反映的資料庫狀態操作。如果交易從快照集中不存在的資料表進行讀取,則會擲回先前顯示的 1018 錯誤訊息。即使另一個並行交易在交易擷取快照集後建立資料表,交易也無法從新建立的資料表中進行讀取。

為了解決此序列化隔離錯誤,您可以嘗試將交易的開始移至您知道該資料表存在的位置。

如果該資料表是由另一個交易建立,則這一個位置點應至少在已遞交該交易之後。此外,請確定沒有遞交任何可能捨棄資料表的並行交易。

session1 = # BEGIN; session1 = # DROP TABLE A; session1 = # COMMIT;
session2 = # BEGIN;
session3 = # BEGIN; session3 = # CREATE TABLE A (id INT); session3 = # COMMIT;
session2 = # SELECT * FROM A;

session2 執行為讀取操作的最後一個操作會導致可序列化隔離錯誤。當 session2 擷取快照集,而資料表已被遞交的 session1 捨棄時,就會發生這個錯誤。換句話說,即使並行的 session3 已經建立資料表,但 session2 仍看不到該資料表,因為其不在快照中。

若要解決此錯誤,您可以按照下方式重新排序工作階段。

session1 = # BEGIN; session1 = # DROP TABLE A; session1 = # COMMIT;
session3 = # BEGIN; session3 = # CREATE TABLE A (id INT); session3 = # COMMIT;
session2 = # BEGIN; session2 = # SELECT * FROM A;

現在,當 session2 擷取其快照時,session3 已經遞交,且該資料表位於資料庫中。Session2 可以從資料表中讀取而不會出現任何錯誤。