可序列化隔離 - Amazon Redshift

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

可序列化隔離

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

注意

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

某些 SQL 用戶端會自動發出 BEGIN 和 COMMIT 命令,讓用戶端控制要將陳述式群組做為交易執行,還是將每個個別的陳述式做為單獨的交易執行。請查看您正在使用的界面文件。例如,使用 Amazon Redshift JDBC 驅動程序時,JDBCPreparedStatement使用包含多個(分號分隔)的查詢字符串 SQL 命令將所有語句作為單個事務運行。相反地,如果您使用 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 陳述式影響的資料表。

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

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

錯誤: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 取得快照集,且資料表已被認可的工作階段 1 卸除時,就會發生這個錯誤。換句話說,即使並發 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 已經提交,並且該表位於數據庫中。會話 2 可以從表中讀取而不會出現任何錯誤。