在 PostgreSQL 中进行 DROP 操作时避免对引用表的独占访问锁

Avoid exclusive access locks on referenced tables when DROPping in PostgreSQL

为什么在 PostgreSQL 中删除 table 需要 ACCESS EXCLUSIVE 锁定任何引用的 table?我怎样才能将其减少为 ACCESS SHARED 锁或根本没有锁?即有没有办法在不锁定引用的 table?

的情况下删除关系

我在文档中找不到任何关于需要哪些锁的提及,但是除非我在并发操作期间删除多个 table 时以正确的顺序明确获得锁,否则我会看到死锁等待日志中的 AccessExclusiveLock,并且在删除 tables 时,在常用引用的 tables 上获取此限制性锁会导致其他进程暂时延迟。

澄清一下,

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?
        -- 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 = 
        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 只是为了简单起见而锁定它接触的所有内容 — 无论如何,您不应该 运行 在正常操作期间涉及非临时 table 的 DDL。


为避免死锁,您可以使用咨询锁:

start transaction;
select pg_advisory_xact_lock(0);
drop table main;
commit;

这将确保只有一个客户端同时 运行 宁 DDL 涉及引用 table,因此其他锁的获取顺序无关紧要。


您可以通过先删除外键来避免长时间锁定 table:

start transaction;
select pg_advisory_xact_lock(0);
alter table main drop constraint fk_main_base;
commit;
start transaction;
drop table main;
commit;

这仍然需要独占锁定 base,但时间要短得多。

对于任何使用谷歌搜索并试图理解为什么他们的 drop table(或删除外键或添加外键)卡住了很长时间的人:

PostgreSQL(我看了9.4到13的版本)外键约束其实是在外键两端使用触发器实现的 .

如果你有一个公司table(id作为主键)和一个bank_accounttable(id作为主键,company_id作为外键指向company.id),那么 bank_account table 上实际上有 2 个触发器,公司 table.

上也有 2 个触发器
table_name timing trigger_name function_name
bank_account AFTER UPDATE RI_ConstraintTrigger_c_1515961 RI_FKey_check_upd
bank_account AFTER INSERT RI_ConstraintTrigger_c_1515960 RI_FKey_check_ins
company AFTER UPDATE RI_ConstraintTrigger_a_1515959 RI_FKey_noaction_upd
company AFTER DELETE RI_ConstraintTrigger_a_1515958 RI_FKey_noaction_del

这些触发器的初始创建(在创建 foreing 键时)需要在那些 table 上使用 SHARE ROW EXCLUSIVE 锁(在 9.4 及更早版本中它曾经是 ACCESS EXCLUSIVE 锁)。此锁不与“数据读取锁”冲突,但会与所有其他锁冲突,例如简单的 INSERT/UPDATE/DELETE into company table.

删除这些触发器(删除外键或整个 table 时)需要对这些 table 进行访问独占锁定。此锁与其他所有锁冲突!

想象一个场景,你有一个事务 A 运行,它首先从公司 table 做了一个简单的 SELECT(导致它持有公司 [的 ACCESS SHARE 锁) =40=] 直到事务被提交或回滚)并且现在正在做一些其他工作 3 分钟。你尝试在事务B中drop bank_account table,这需要ACCESS EXCLUSIVE锁,需要等ACCESS SHARE锁先释放。 除此之外,所有其他想要访问公司 table(只是 SELECT,或者可能是 INSERT/UPDATE/DELETE)的事务都将排队等待 ACCESS EXCLUSIVE 锁,该锁正在等待在 ACCESS SHARE 锁上。

长 运行 事务和 DDL 更改需要精细处理。