我们有一个包含统计数据的数据库。表是分区的 根据时间使用继承。根据需要创建新的子表 根据传入的数据,应用程序运行一个夜间工作 丢掉旧儿童桌。
为了保持一致性,与一次关联的所有子表 期间在一次交易中被删除。
我们发现我们现在正在DROP序列和正常之间出现死锁 SELECT查询。下面的(大大简化)示例说明了 问题:
DDL&插入虚拟行
CREATE TABLE a(id serial primary key, t timestamp with time zone, i int);
CREATE TABLE a1 () inherits (a);
CREATE TABLE a2 () inherits (a);
CREATE TABLE b(id serial primary key, id_a int, x int, y int);
CREATE TABLE b1 () inherits (b);
CREATE TABLE b2 () inherits (b);
INSERT INTO a1(t,i) VALUES (CURRENT_TIMESTAMP, 100);
INSERT INTO b1(id_a,x,y) VALUES (1,200,300);
在一个psql会话中,运行以下命令(模拟长时间运行的查询):
SELECT pg_sleep(90)
FROM b
LEFT JOIN a on a.id=b.id_a;
在第二个psql会话中,运行以下命令:
BEGIN;
DROP TABLE a2;
DROP TABLE b2;
在第三个会话中,运行此命令(与第一个会话中相同的查询):
SELECT pg_sleep(90)
FROM b
LEFT JOIN a on a.id=b.id_a;
当在步骤2中启动的查询完成或中断时,会发生死锁。
DETAIL: Process 19894 waits for AccessExclusiveLock on relation 129716 of database 44449; blocked by process 20017.
Process 20017 waits for AccessShareLock on relation 129700 of database 44449; blocked by process 19894.
问题很容易发现:DROP事务所采取的锁定是a2然后是b2的顺序,但SELECT的顺序是相反的。 声明。似乎SELECT语句按顺序采用它们的锁 查询中的连接。
我们尝试在删除表之前使用单个LOCK命令锁定表:这没有帮助,它们按照LOCK命令中列出的顺序被锁定,如果与命令不同,死锁仍然会发生它们出现在所有SELECT查询中。
我们不希望为所有用户查询强制执行特定的加入订单,以确保我们在过期旧数据时不会出现死锁。
有一件事我们发现,根据我们试图执行的SELECT查询,我们使用一个连接顺序与另一个连接顺序相比会得到更糟糕的性能(我知道这不应该是,但是当我们选择时,规划者会选择更好的计划使用一个连接顺序与另一个连接顺序。)
另一方面,查询是根据用户输入动态生成的 并全面强制执行连接顺序将限制可以进行的查询 执行(现实生活中的查询要复杂得多,并且可能涉及很多 表,视图,子查询等)。
我认为连接顺序决定了SELECT查询的锁定顺序是正确的吗?
有没有办法在不强制执行特定连接的情况下避免这些死锁 只读SELECT查询的顺序?
我们正在使用Postgresql 9.6.6。
答案 0 :(得分:0)
处理表的顺序不一定是它们在查询中出现的顺序。但是,在计划期间确定该连接顺序,并且在计划期间,将首先按照它们在查询中出现的顺序处理(和锁定)表。
您说要删除的所有表都是继承子项,因此我很惊讶您的查询直接访问分区而不是父表。如果所有查询都访问父表,则应该能够通过在删除任何继承子项之前将父表锁定在ACCESS EXCLUSIVE
模式来避免死锁。