在 PostgreSQL 中的 CREATE TABLE 上檢測到死鎖
當我檢測到死鎖時,我正在非同步創建多個表。兩個表(表 A 和 B)對一個表(表 C)都有 FK,我在與表 C 的關係上出現死鎖。
腳本
CREATE TABLE "TableA" ( "Id" uuid NOT NULL, "TableCId" uuid NOT NULL, CONSTRAINT "PK_TableA_Id" PRIMARY KEY ("Id"), CONSTRAINT "FK_TableA_TableC" FOREIGN KEY ("TableCId") REFERENCES "TableC" ("Id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ) WITH ( OIDS=FALSE ); CREATE TABLE "TableB" ( "Id" uuid NOT NULL, "TableCId" uuid NOT NULL, CONSTRAINT "PK_TableB_Id" PRIMARY KEY ("Id"), CONSTRAINT "FK_TableB_TableC" FOREIGN KEY ("TableCId") REFERENCES "TableC" ("Id") MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ) WITH ( OIDS=FALSE );
該表存在於上述腳本執行的時刻
CREATE TABLE "TableC" ( "Id" uuid NOT NULL, CONSTRAINT "PK_TableC_Id" PRIMARY KEY ("Id"), ) WITH ( OIDS=FALSE );
因此,當我查看日誌時,會出現以下錯誤,並且關係 87066是 TableC。請注意,TableA 和 TableB 是使用 READ COMMITTED 隔離在單獨的事務中創建的。
程序 464 等待數據庫 86965 的關係 87066 上的 AccessExclusiveLock;被程序 1180 阻塞。程序 1180 等待數據庫 86965 的關係 87066 上的 AccessExclusiveLock;被程序 464 阻止。
當我查看 PostgreSQL 文件時,它說應該以相同的順序執行創建表以避免死鎖,並且它們以相同的順序執行,因此不會發生死鎖。
有什麼理由會以死鎖告終嗎?我認為如果 464 是第一個執行的程序,那麼程序 464 應該等待 1180 嗎?
編輯
另一個注意事項是我在創建表腳本之前在同一事務中執行一個插入。
INSERT INTO public."TableD"("Id", "TableCId", ....) VALUES (:p0, :p1, :p2, :p3, :p4, :p5, :p6, :p7, :p8, :p9)
PostgreSQL 9.3
讓我們假設 INSERT 的目標表(“TableD”)有一個帶有“TableC”外鍵的列。對該表的 INSERT 必須獲取“TableC”上的共享鎖,並將保持該鎖直到事務結束。
假設兩個並發事務 T1 和 T2 以這樣的 INSERT 開始。每個都在“TableC”上共享一個鎖。
稍後,為了創建一個涉及到“TableC”的外鍵的 CREATE TABLE,T1 必須獲取“TableC”上的訪問排他鎖。
由於 T2 已經在“TableC”上擁有一個共享鎖,因此 T1 將等待直到 T2 送出或回滾。
此時,如果 T2 嘗試創建一個還包含“TableC”的外鍵的表(這再次意味著獲取“TableC”上的訪問排他鎖),它必須等待 T1 送出或回滾,因為 T1 持有一個共享的鎖定“TableC”。
所以現在 T1 正在等待 T2 完成,而 T2 正在等待 T1 完成:有死鎖。
為避免這種情況,可以在事務開始時或至少在並發事務無法安全並行執行的指令序列開始時採用顯式和排他鎖。