簡體   English   中英

在 PostgreSQL 中 DROPping 時避免對引用表的獨占訪問鎖

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM