我在PostgreSQL 9.2上有一个数据库,该数据库的主架构包含大约70个表,并且每个客户机模式的可变结构数目相同,每个表有30个表。客户端模式具有外键,这些外键引用主模式,而不是其他方式。当我不得不在主模式的非常中央的表中进行批量删除时,数据库已达到约1.5 GB(预计数周之内将增长到几十GB)。所有相关的外键都标记为DELETE CASCADE。

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

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

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

评论

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

#1 楼

我有一个类似的问题。事实证明,那些ON DELETE CASCADE触发器使事情放慢了很多,因为那些级联的删除非常慢。我从删除文件花了几个小时到几秒钟。

评论


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

–msrd0
18-09-20在21:01



这也为我解决了。对于尝试此操作的任何人,您都可以对单行删除执行EXPLAIN(分析,缓冲区)查询,它应该显示出哪些外键约束花费的时间最长(至少对我而言)。

–贾斯汀·沃克曼(Justin Workman)
18-10-4在17:34

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

–fillobotto
18-10-18在8:43

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

– Mikko Rantalainen
18/12/20在10:51

对于那些试图理解原因的人:考虑从表A到表B的外键。如果从表B删除行,数据库必须验证表A中没有行引用此行。如果表A在引用列上没有索引,则它必须顺序扫描整个表,如果表很大,可能会很慢。

–kleptog
19/12/19在17:01

#2 楼

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

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


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

评论


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

–尼克·伍德汉姆斯(Nick Woodhams)
18年4月12日在7:30

#3 楼

解决该问题的最简单方法是从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;

最后的rollback允许运行此命令而无需真正修改数据库。您仍然可以获得花费了多少时间的详细时间安排。运行该命令之后,您可能会在输出中发现某些触发器导致了巨大的延迟:您需要在必填列上添加新的time,以便可以有效地计算此触发器。对于外键引用,必须索引引用另一个表的列(即,源列,而不是目标列)。 PostgreSQL不会自动为您创建此类索引,并且INDEX是您真正真正需要该索引的唯一常见查询。结果,您可能已经积累了多年的数据,直到遇到DELETE由于缺少索引而变得太慢的情况。
一旦您对该约束具有固定的性能(或其他花费时间较长的事情),在DELETE / begin块中重复该命令,以便可以将新的执行时间与上一个执行时间进行比较。继续,直到您对单行删除响应时间感到满意为止(通过添加不同的索引,我得到了一个查询,它从25.6秒缩短到15 ms或快了大约1700x)。然后,您可以继续进行完整删除,而不会受到任何黑客攻击。
(请注意,rollback需要一个可以成功完成的查询。我曾经遇到一个问题,即PostgreSQL花了很长时间才弄清楚一个删除操作将违反外键约束,在这种情况下,由于无法发出时序,因此无法使用EXPLAIN对于失败的查询。在这种情况下,我不知道有什么简便的方法可以调试性能问题。)

#4 楼

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

 SET session_replication_role = replica;
 


在此处运行DELETE

要恢复触发器,请运行:

 SET session_replication_role = DEFAULT;
 


此处来源。

#5 楼

如果您有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)
);


#6 楼

对我来说,诀窍是从另一个引用表中删除fk约束。该引用表很大。但是要小心,我知道必须删除的记录的约束并不重要。因此,我可以暂时删除该约束以在之后添加它(在此期间,我确定没有其他数据库活动)。