通过ID删除数百万行的最佳方法

我需要从我的PG数据库中删除大约200万行。 我有我需要删除的ID列表。 然而,我试图做到这一点的任何方式都需要数天时间。

我尝试把它们放在一个表中,并以100的批次执行。4天后,这个文件仍在运行,只有297268行被删除。 (我必须从一个ID表中select100个ID,删除IN列表中,从我select的100个ID表中删除)。

我试过了:

DELETE FROM tbl WHERE id IN (select * from ids) 

这也是永远的。 很难衡量多久,因为我看不到它的进展,直到完成,但查询仍在运行2天后。

只要find从表中删除的最有效的方法时,我知道要删除的具体ID,并有数以百万计的ID。

这完全取决于…

  • 删除所有索引(除了需要删除的ID)
    之后重新创build它们(=比索引的增量更新快得多)

  • 检查是否有可以安全地删除/禁用的触发器

  • 外键引用你的表吗? 他们可以被删除吗? 暂时删除?

  • 根据您的自动清理设置, 可能会帮助您在操作之前运行VACUUM ANALYZE

  • 如果你删除大部分的表,其余的适合内存,最快和最简单的方法是这样的:

 SET temp_buffers = 1000MB -- or whatever you can spare temporarily CREATE TEMP TABLE tmp AS SELECT t.* FROM tbl t LEFT JOIN del_list d USING (id) WHERE d.id IS NULL; -- copy surviving rows into temporary table TRUNCATE tbl; -- empty table - truncate is very fast for big tables INSERT INTO tbl SELECT * FROM tmp; -- insert back surviving rows. 

这样,您不必重新创build视图,外键或其他依赖对象。 阅读手册中的temp_buffers设置 。 只要表格适合内存,或者至less大部分内存,这种方法是快速的。 请注意,如果服务器在此操作过程中崩溃,则可能会丢失数据。 你可以把它全部包装到一个交易中来使它更安全。

另外, build议 :

TRUNCATE不能在具有其他表的外键引用的表上使用,除非所有这样的表在同一个命令中也被截断。

之后运行ANALYZE 。 或者如果你没有VACUUM ANALYZE截断线路,就selectVACUUM FULL ANALYZE如果你想把它放到最小,则选VACUUM FULL ANALYZE 。 对于大桌子考虑替代品CLUSTER / pg_repack

  • 优化Postgres时间戳查询范围

对于小型表格,简单的DELETE而不是TRUNCATE通常更快:

 DELETE FROM tbl t USING del_list d WHERE t.id = d.id; 

我们知道PostgreSQL的更新/删除性能不如Oracle强大。 当我们需要删除数百万或数十万行时,这是非常困难的,需要很长时间。

但是,我们仍然可以在生产dbs中做到这一点。 以下是我的想法:

首先,我们应该创build一个有2列的日志表 – idflagid是指你想删除的id; flag可以是YnullY表示logging被成功删除)。

之后,我们创build一个函数。 我们每10,000行执行一次删除任务。 你可以在我的博客上看到更多的细节。 虽然它是中文的,但是你仍然可以从SQL代码中得到你想要的信息。

确保两个表的id列是索引,因为它运行得更快。

您可以尝试复制表中除了要删除的ID 之外的所有数据到新表,然后重命名然后交换表(提供了足够的资源)。

这不是专家的build议。

最简单的方法是删除所有约束,然后删除。

两个可能的答案:

  1. 当您尝试删除logging时,您的表可能会附加很多约束或触发器。 这会引起很多处理器周期并从其他表中检查。

  2. 您可能需要将此语句放入事务中。

首先,确保在要删除的表中的ID字段和用于删除ID的表中都有索引。

100一次看起来太小了。 尝试1000或10000。

没有必要从删除ID表中删除任何东西。 为一个批号添加一个新的列,并填写1000批1,1000批2等,并确保删除查询包括批号。

如果您要删除的表由some_other_table引用(并且您不想暂时丢弃外键),请确保您在some_other_table引用列上有索引!

我有一个类似的问题,并使用auto_explain auto_explain.log_nested_statements = true ,它显示delete实际上是在some_other_table上执行some_other_table

  Query Text: SELECT 1 FROM ONLY "public"."some_other_table" x WHERE $1 OPERATOR(pg_catalog.=) "id" FOR KEY SHARE OF x LockRows (cost=[...]) -> Seq Scan on some_other_table x (cost=[...]) Filter: ($1 = id) 

显然它试图locking其他表中的引用行(不应该存在,否则删除将失败)。 在引用表上创build索引之后,删除速度要快几个数量级。