在PostgreSQL中非常慢的DELETE,解决方法?


30

我在PostgreSQL 9.2上有一个数据库,该数据库的主结构包含约70个表,并且每个客户机模式的结构相同的变量数量各不相同,每个表有30个表。客户端模式具有引用主模式的外键,而不是相反的方式。

我刚刚开始使用从先前版本中获取的一些真实数据填充数据库。当我不得不在主模式的非常中央的表中进行批量删除时,数据库已达到约1.5 GB(预计数周之内将增长到几十GB)。所有相关的外键都标记为ON DELETE CASCADE。

这将花费很长时间也就不足为奇了,但是在12个小时之后,很明显,我最好从头开始,删除数据库并再次启动迁移。但是,如果我需要在数据库正常运行并且更大时再重复此操作,该怎么办?是否有其他更快的方法?

如果我编写了一个脚本,该脚本将浏览从属表,从中央表最远的表开始,逐表删除从属行,会更快吗?

一个重要的细节是某些表上有触发器。


4
5年后,我将更改接受的答案。缓慢的DELETE几乎总是由直接或间接引用要从中删除的表的外键缺少索引引起的。在DELETE语句上触发的触发器也会使事情变慢,尽管解决方案几乎总是使它们运行得更快(例如,通过添加丢失的索引),并且几乎永远不会禁用所有触发器。
jd。

Answers:


30

我有一个类似的问题。事实证明,那些ON DELETE CASCADE触发器使事情放慢了很多,因为那些级联的删除非常慢。

我通过在引用表上的外键字段上创建索引来解决了这个问题,我的工作从花几个小时删除到几秒钟。


哇,这帮助我在几分钟内删除了800万条记录。但是我不明白的是,我的表仅保存对其他表的引用,没有其他表保存对我的表的引用。那么,这里的效果到底是什么?(我没有使用ON DELETE CASCADE
msrd0

2
这也为我解决了。对于尝试此操作的任何人,您都可以EXPLAIN (ANALYZE, BUFFERS)对单行删除进行查询,它应该显示出哪些外键约束花费的时间最长(至少对我而言)。
贾斯汀·沃克曼

同样,必须删除级联的60万行,开始时每次操作需要2-10个操作,CPU使用率为100%。现在只用了几分钟就删除了所有CPU使用率达80%的文件。
fillobotto

重要的是要注意,如果您对任何地方都有外部引用,则源列必须具有真实索引,否则性能会受到影响。我不确定PRIMARY索引是否足够,但UNIQUE索引绝对不能满足此目的。
米科·兰塔莱宁

26

您有几种选择。最好的选择是运行批处理删除,以免触发被击中。删除之前禁用触发器,然后重新启用它们。这样可以为您节省大量时间。例如:

ALTER TABLE tablename DISABLE TRIGGER ALL; 
DELETE ...; 
ALTER TABLE tablename ENABLE TRIGGER ALL;

这里的一个主要关键是您希望最小化子查询的深度。在这种情况下,您可能需要设置临时表来存储相关信息,以便避免在删除操作时出现较深的子查询。


就我而言,我在睡觉前启动了DELETE FROM命令,第二天返回计算机时仍然没有完成。始终在一个内核上100%使用CPU。禁用触发器并重试后,花了3秒钟删除了200k条记录。谢谢!
尼克·伍德汉姆斯

13

解决该问题的最简单的方法是从PostgreSQL查询详细时序:EXPLAIN。为此,您至少需要找到一个可以完成但比预期更长的查询。假设这条线看起来像

delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';

无需真正运行该命令,您可以执行

begin;
explain (analyze,buffers,timing) delete from mydata where id='897b4dde-6a0d-4159-91e6-88e84519e6b6';
rollback;

最后的回滚允许在不真正修改数据库的情况下运行此操作,但是您仍然可以详细了解花费了多少时间。运行该命令之后,您可能会在输出中发现某些触发器会导致巨大的延迟:

...
Trigger for constraint XYZ123: time=12311.292 calls=1
...

的单位time是毫秒(毫秒),因此检查此约束大约需要12.3秒。您需要INDEX在必填列上添加新的内容,以便可以有效地计算此触发器。对于外键引用,必须索引引用另一个表的列(即,源列,而不是目标列)。PostgreSQL不会自动为您创建此类索引,它DELETE是您真正真正需要该索引的唯一常见查询。结果,您可能已经积累了多年的数据,直到遇到DELETE由于缺少索引而导致速度太慢的情况。

一旦具有固定的约束性能(或花费过多时间的其他事情),请在begin/ rollback块中重复该命令,以便可以将新的执行时间与上一个执行的时间进行比较。继续,直到您对单行删除响应时间满意为止(只需添加不同的索引,我就可以使查询从25.6秒缩短到15 ms)。然后,您可以继续进行完整删除,而不会受到任何攻击。

(请注意,这EXPLAIN需要一个可以成功完成的查询。我曾经遇到一个问题,PostgreSQL花了很长时间才弄清楚一个删除将违反外键约束,在这种情况下EXPLAIN无法使用,因为它不会发出失败的时间在这种情况下,我不知道调试性能问题的任何简便方法。)


8

禁用触发器可能会对数据库完整性造成威胁,因此不建议使用;但是,如果您确定自己的操作可以防止约束失败,则可以使用以下命令禁用触发器:SET session_replication_role = replica;

DELETE这里运行。

要还原触发器,请运行: SET session_replication_role = DEFAULT;

来源在这里。


0

如果您有ON DELETE CASCADE触发器,则希望它们是有原因的,因此不应禁用。对我有用的另一个技巧(仍然添加索引)是创建一个delete函数,该函数手动从级联末尾的表开始删除数据,并朝着主表工作。(这与如果您具有“ ON DELETE RESTRICT”触发器一样)

CREATE TABLE tablea (
    tablea_uid integer
);

CREATE TABLE tableb (
    tableb_uid integer,
    tablea_rid integer REFERENCES tablea(tablea_uid)
);

CREATE TABLE tablec (
    tablec_uid integer,
    tableb_rid integer REFERENCES tableb(tableb_uid)
);

在这种情况下,先删除tablec中的数据,然后删除tableb,然后删除tablea

CREATE OR REPLACE FUNCTION delete_in_order()
 RETURNS void AS $$

    DELETE FROM tablec;
    DELETE FROM tableb;
    DELETE FROM tablea;

$$ LANGUAGE SQL;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.