(如何)表继承会干扰 postgres 中的锁定?

Han*_*etz 6 postgresql partitioning locking

我有一个表继承设置,可以像这样简化:

CREATE TABLE p (
    id BIGSERIAL PRIMARY KEY,
    type_id BIGINT,
    approved BOOLEAN
);

CREATE INDEX ON p(approved);

CREATE TABLE a (
    a_field VARCHAR,
    PRIMARY KEY (id),
    CHECK(type_id = 1::BIGINT)
) INHERITS (p);

CREATE TABLE b (
    b_field INT,
    PRIMARY KEY (id),
    CHECK(type_id = 2::BIGINT)
) INHERITS (p);

CREATE INDEX ON b(approved);

CREATE TABLE c (
    c_field NUMERIC,
    PRIMARY KEY (id)
    -- this table is missing the check constraint (for no good reason)
) INHERITS (p);
Run Code Online (Sandbox Code Playgroud)

我现在有一个锁定 table 的长期运行事务a。当它运行时,我观察b到等待释放锁的表上的查询,即使它们保证不使用a. 例如:

SELECT TRUE FROM p WHERE id = 12345 AND approved = false AND type_id = 2 LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

EXPLAIN在该查询上运行会显示如下内容:

                              QUERY PLAN                                                                  
----------------------------------------------------------------------------
Limit  (cost= ...)
    ->  Result  (cost= ...)
       ->  Append  (cost= ...)
         ->  Seq Scan on p  (cost= ...)
                 Filter: ((NOT approved) AND (id = 12345) AND (type_id = 2))
         ->  Index Scan using b_approved on b p  (cost= ...)
                 Index Cond: (approved = false)
                 Filter: ((NOT approved) AND (id = 12345) AND (type_id = 2))
Run Code Online (Sandbox Code Playgroud)

我不确定我是否正确阅读,但在我看来,查询规划器认为它需要通过父表,因为它不知道表是否c可能包含它正在寻找的行。实际上,有不止一个表(实际上,有几十个) like c,它缺少 CHECK 约束,因此在我看来,计划者认为对父级进行 Seq Scan 可能是最有效的路线似乎是合理的。

如果我将适当的检查约束添加到(c 类表都可以得到一个,但这将是一些工作),我会避免锁定a干扰不相关的(就数据而言)查询吗?bc

关于锁定的这种设置还有其他评论吗?

请注意,此分区存在的原因之一是运行查询的应用程序具有与其正在处理的子表无关的部分,因此显示的查询转到p。此外,这样的查询可能会影响少量的表 (3-5) 而不仅仅是b,即,查询计划显示额外的Index Scan行,如此处b所示。

dez*_*zso 5

显然,您希望在工作时看到约束排除。在类似的设置中,父表没有约束(顺便提一下,通常没有行),它总是会被父表上的查询访问。使用您的示例架构,查看输出:

test=# EXPLAIN SELECT count(1) FROM p;
                            QUERY PLAN                             
???????????????????????????????????????????????????????????????????
 Aggregate  (cost=576.00..576.01 rows=1 width=0)
   ->  Append  (cost=0.00..501.00 rows=30001 width=0)
         ->  Seq Scan on p  (cost=0.00..0.00 rows=1 width=0)
         ->  Seq Scan on a  (cost=0.00..164.00 rows=10000 width=0)
         ->  Seq Scan on b  (cost=0.00..164.00 rows=10000 width=0)
         ->  Seq Scan on c  (cost=0.00..173.00 rows=10000 width=0)

test=# EXPLAIN SELECT count(1) FROM p WHERE type_id = 1;
                            QUERY PLAN                             
???????????????????????????????????????????????????????????????????
 Aggregate  (cost=412.00..412.01 rows=1 width=0)
   ->  Append  (cost=0.00..387.00 rows=10002 width=0)
         ->  Seq Scan on p  (cost=0.00..0.00 rows=1 width=0)
               Filter: (type_id = 1)
         ->  Seq Scan on a  (cost=0.00..189.00 rows=10000 width=0)
               Filter: (type_id = 1)
         ->  Seq Scan on c  (cost=0.00..198.00 rows=1 width=0)
               Filter: (type_id = 1)
Run Code Online (Sandbox Code Playgroud)

在第二种情况下,表b没有被访问,因为规划器肯定知道不可能有任何行匹配条件。同时,p在这两种情况下都存在。这同样适用于c:它没有简单的方法(检查)从计划中排除该表。在这种情况下,它必须访问所有可能包含实际行的表。

让我们也添加一些检查:

ALTER TABLE c ADD CHECK (type_id = 3);

test=# EXPLAIN SELECT count(1) FROM p WHERE type_id = 1;
                            QUERY PLAN                             
???????????????????????????????????????????????????????????????????
 Aggregate  (cost=214.00..214.01 rows=1 width=0)
   ->  Append  (cost=0.00..189.00 rows=10001 width=0)
         ->  Seq Scan on p  (cost=0.00..0.00 rows=1 width=0)
               Filter: (type_id = 1)
         ->  Seq Scan on a  (cost=0.00..189.00 rows=10000 width=0)
               Filter: (type_id = 1)
Run Code Online (Sandbox Code Playgroud)

现在好多了。

现在让我们来看看被采取的锁。

BEGIN; -- to be able to see the locks

SELECT count(1) FROM p WHERE type_id = 1;

SELECT relname, locktype, mode
  FROM pg_locks
  JOIN pg_class c ON relation = c.oid 
  JOIN pg_namespace n ON c.relnamespace = n.oid
 WHERE nspname = 'dba' 
   AND relkind = 'r';

 relname ? locktype ?      mode       
??????????????????????????????????????
 c       ? relation ? AccessShareLock
 b       ? relation ? AccessShareLock
 a       ? relation ? AccessShareLock
 p       ? relation ? AccessShareLock
Run Code Online (Sandbox Code Playgroud)

这不是那么好......显然(并且有点令人惊讶),转到父表也会锁定所有子表。

但:

ROLLBACK;
BEGIN; -- to be able to see the locks

SELECT count(1) FROM a WHERE type_id = 1; -- touching only "a"

-- the locks taken:
 a       ? relation ? AccessShareLock
Run Code Online (Sandbox Code Playgroud)

这意味着,如果您可以将查询限制为仅涉及表的一个子集,它将为您节省对其他子项的锁定。稍微玩一下,它表明这也适用于其他语句(例如, an UPDATE),只是锁定模式不同。

  • 你有什么关于为什么所有的孩子都被锁上的猜测吗?我认为这也很令人惊讶,但它完全符合我的观察。谢谢你这么准确地指出它。 (2认同)