[英]Avoid exclusive access locks on referenced tables when DROPping in PostgreSQL
為什么刪除 PostgreSQL 中的表需要對任何引用的表使用ACCESS EXCLUSIVE
鎖? 如何將其減少為ACCESS SHARED
鎖定或根本不鎖定? 即有沒有辦法在不鎖定引用表的情況下刪除關系?
我在文檔中找不到任何關於需要哪些鎖的提及,但是除非我在並發操作期間刪除多個表時以正確的順序明確獲取鎖,否則我可以看到死鎖在日志中等待 AccessExclusiveLock,並獲取此限制當表被刪除時,對常用表的鎖定會導致其他進程的暫時延遲。
澄清,
CREATE TABLE base (
id SERIAL,
PRIMARY KEY (id)
);
CREATE TABLE main (
id SERIAL,
base_id INT,
PRIMARY KEY (id),
CONSTRAINT fk_main_base (base_id)
REFERENCES base (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
DROP TABLE main; -- why does this need to lock base?
對於任何使用谷歌搜索並試圖了解為什么他們的刪除表(或刪除外鍵或添加外鍵)長時間卡住的人:
PostgreSQL (我查看了 9.4 到 13 版)外鍵約束實際上是使用外鍵兩端的觸發器來實現的。
如果您有一個公司表(id 作為主鍵)和一個 bank_account 表(id 作為主鍵,company_id 作為指向 company.id 的外鍵),那么實際上在 bank_account 表上有 2 個觸發器,在公司上也有 2 個觸發器桌子。
表名 | 定時 | 觸發器名稱 | 函數名 |
---|---|---|---|
銀行賬戶 | 更新后 | RI_ConstraintTrigger_c_1515961 | RI_FKey_check_upd |
銀行賬戶 | 插入后 | RI_ConstraintTrigger_c_1515960 | RI_FKey_check_ins |
公司 | 更新后 | RI_ConstraintTrigger_a_1515959 | RI_FKey_noaction_upd |
公司 | 刪除后 | RI_ConstraintTrigger_a_1515958 | RI_FKey_noaction_del |
這些觸發器的初始創建(在創建前鍵時)需要在這些表上使用 SHARE ROW EXCLUSIVE 鎖(它曾經是 9.4 版及更早版本中的 ACCESS EXCLUSIVE 鎖)。 此鎖與“數據讀取鎖”不沖突,但會與所有其他鎖沖突,例如對公司表的簡單 INSERT/UPDATE/DELETE。
刪除這些觸發器(刪除外鍵或整個表時)需要在這些表上使用 ACCESS EXCLUSIVE 鎖。 此鎖與其他所有鎖沖突!
所以想象一個場景,你有一個事務 A 運行,它首先從公司表中執行一個簡單的 SELECT(導致它持有公司表的 ACCESS SHARE 鎖,直到事務被提交或回滾),現在正在做一些其他的工作3分鍾。 您嘗試刪除事務 B 中的 bank_account 表。這需要 ACCESS EXCLUSIVE 鎖,這需要等到 ACCESS SHARE 鎖先被釋放。 除此之外,所有其他想要訪問公司表的事務(只是 SELECT,或者可能是 INSERT/UPDATE/DELETE),將排隊等待 ACCESS EXCLUSIVE 鎖,它正在等待 ACCESS SHARE 鎖。
長時間運行的事務和 DDL 更改需要精細處理。
-- SESSION#1
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
BEGIN;
CREATE TABLE base (
id SERIAL
, dummy INTEGER
, PRIMARY KEY (id)
);
CREATE TABLE main (
id SERIAL
, base_id INTEGER
, PRIMARY KEY (id)
, CONSTRAINT fk_main_base FOREIGN KEY (base_id) REFERENCES base (id)
-- comment the next line out ( plus maybe tghe previous one)
ON DELETE CASCADE ON UPDATE CASCADE
);
-- make some data ...
INSERT INTO base (dummy)
SELECT generate_series(1,10)
;
-- make some FK references
INSERT INTO main(base_id)
SELECT id FROM base
WHERE random() < 0.5
;
COMMIT;
BEGIN;
DROP TABLE main; -- why does this need to lock base?
SELECT pg_backend_pid();
-- allow other session to check the locks
-- and attempt an update to "base"
SELECT pg_sleep(20);
-- On rollback the other session will fail.
-- On commit the other session will succeed.
-- In both cases the other session must wait for us to complete.
-- ROLLBACK;
COMMIT;
-- SESSION#2
-- (Start this after session#1 from a different terminal)
SET search_path = tmp, pg_catalog;
PREPARE peeklock(text) AS
SELECT dat.datname
, rel.relname as relrelname
, cat.relname as catrelname
, lck.locktype
-- , lck.database, lck.relation
, lck.page, lck.tuple
-- , lck.virtualxid, lck.transactionid
-- , lck.classid
, lck.objid, lck.objsubid
-- , lck.virtualtransaction
, lck.pid, lck.mode, lck.granted, lck.fastpath
FROM pg_locks lck
LEFT JOIN pg_database dat ON dat.oid = lck.database
LEFT JOIN pg_class rel ON rel.oid = lck.relation
LEFT JOIN pg_class cat ON cat.oid = lck.classid
WHERE EXISTS(
SELECT * FROM pg_locks l
JOIN pg_class c ON c.oid = l.relation AND c.relname = $1
WHERE l.pid =lck.pid
)
;
EXECUTE peeklock( 'base' );
BEGIN;
-- attempt to perfom some DDL
ALTER TABLE base ALTER COLUMN id TYPE BIGINT;
-- attempt to perfom some DML
UPDATE base SET id = id+100;
COMMIT;
EXECUTE peeklock( 'base' );
\d base
SELECT * FROM base;
我想 DDL 只為了簡單起見鎖定它接觸的所有東西——無論如何,你不應該在正常操作期間運行涉及非臨時表的 DDL。
為了避免死鎖,您可以使用咨詢鎖:
start transaction;
select pg_advisory_xact_lock(0);
drop table main;
commit;
這將確保只有一個客戶端同時運行涉及引用表的 DDL,因此獲取其他鎖的順序無關緊要。
您可以通過先刪除外鍵來避免長時間鎖定表:
start transaction;
select pg_advisory_xact_lock(0);
alter table main drop constraint fk_main_base;
commit;
start transaction;
drop table main;
commit;
這仍然需要以獨占方式鎖定base
,但時間要短得多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.