通过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列的日志表 – id
和flag
( id
是指你想删除的id; flag
可以是Y
或null
, Y
表示logging被成功删除)。
之后,我们创build一个函数。 我们每10,000行执行一次删除任务。 你可以在我的博客上看到更多的细节。 虽然它是中文的,但是你仍然可以从SQL代码中得到你想要的信息。
确保两个表的id
列是索引,因为它运行得更快。
您可以尝试复制表中除了要删除的ID 之外的所有数据到新表,然后重命名然后交换表(提供了足够的资源)。
这不是专家的build议。
最简单的方法是删除所有约束,然后删除。
两个可能的答案:
-
当您尝试删除logging时,您的表可能会附加很多约束或触发器。 这会引起很多处理器周期并从其他表中检查。
-
您可能需要将此语句放入事务中。
首先,确保在要删除的表中的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索引之后,删除速度要快几个数量级。