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

时间:2011-11-28 02:29:31

标签: sql postgresql bigdata sql-delete postgresql-performance

我需要从PG数据库中删除大约200万行。我有一个我需要删除的ID列表。但是,我尝试这样做的任何方式都需要几天时间。

我尝试将它们放在一个表中,并在100个批次中进行.4天后,这仍然在运行,只删除了297268行。 (我必须从ID表中选择100个id,删除该列表中的IN,从ids表中删除我选择的100个。)

我试过了:

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

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

当我知道要删除的特定ID时,只需要寻找从表中删除的最有效方法,并且有数百万个ID。

8 个答案:

答案 0 :(得分:74)

一切都取决于......

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

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

  • 外键是否引用了您的表格?他们可以删除吗?暂时删除?

  • 根据您的autovacuum设置,可能有助于在操作前运行VACUUM ANALYZE

  • 假设 没有并发写入权限 到所涉及的表,或者您可能必须独占锁定表,或者此路由可能根本不适合您。

  • 根据您的设置,手册Populating a Database相关章节中列出的一些要点也可能有用。

  • 如果您删除表格的大部分而其余部分适合RAM,最快最简单的方法是:

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.

这样您就不必重新创建视图,外键或其他依赖对象。 阅读temp_buffers setting in the manual。只要表适合内存或至少大部分内存,此方法就很快。请注意,如果服务器在此操作过程中崩溃,则可能会丢失数据。您可以将所有内容包装到事务中以使其更安全。

之后运行ANALYZE。或VACUUM ANALYZE如果你没有去截断路线,或VACUUM FULL ANALYZE如果你想把它变成最小尺寸。对于大表,请考虑替代方案CLUSTER / pg_repack

对于小型表格,简单的DELETE代替TRUNCATE通常更快:

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

阅读 Notes section for TRUNCATE in the manual。特别是(作为Pedro also pointed out in his comment):

  

TRUNCATE不能用于具有外键引用的表   来自其他表,除非所有这些表也被截断   同样的命令。 [...]

  

TRUNCATE不会触发可能存在的任何ON DELETE触发器   表格。

答案 1 :(得分:4)

我们知道PostgreSQL的更新/删除性能不如Oracle强大。什么时候 我们需要删除数百万或数百万行,这真的很难 需要很长的时间。

但是,我们仍然可以在生产dbs中执行此操作。以下是我的想法:

首先,我们应该创建一个包含2列的日志表 - id& flagid是指您要删除的ID; flag可以是YnullY表示记录成功删除)。

稍后,我们创建一个函数。我们每10,000行执行一次删除任务。您可以在my blog上查看更多详细信息。虽然它是中文的,但你仍然可以从那里的SQL代码中获得你想要的信息。

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

答案 2 :(得分:2)

您可以尝试将表中除之外的所有数据复制到新表中,然后重命名然后交换表(前提是您有足够的资源来执行此操作)。< / p>

这不是专家建议。

答案 3 :(得分:2)

两个可能的答案:

  1. 当您尝试删除记录时,您的表可能会附加大量约束或触发器。它会产生很多处理器周期并从其他表中检查。

  2. 您可能需要将此声明放入交易中。

答案 4 :(得分:2)

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

一次100个似乎太小了。尝试1000或10000。

无需从删除ID表中删除任何内容。为批次编号添加新列,并将其填入1000用于批次1,1000用于批次2等,并确保删除查询包括批次编号。

答案 5 :(得分:1)

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

答案 6 :(得分:1)

我自己碰到了这个问题,到目前为止,最快的方法是结合使用WITH QueriesUSING

基本上,WITH查询会创建一个临时表,该表具有要删除的主键。

WITH to_delete AS (
   SELECT item_id FROM other_table WHERE condition_x = true
)
DELETE FROM table 
USING to_delete 
WHERE table.item_id = to_delete.item_id 
  AND NOT to_delete.item_id IS NULL;

当然,WITH查询中的SELECT可以和具有多个联接的任何其他选择一样复杂。它只需要返回一个或多个列即可用于标识目标表中需要的项目要删除。

注意AND NOT to_delete.item_id IS NULL很有可能不是必需的,但我不敢尝试。

要考虑的其他事情是

  1. creating indexes on other tables referring to this one via foreign key。在某些情况下,这可以将删除工作从数小时减少到几秒钟
  2. deferring constraint checks:目前尚不清楚能实现多少改进,但是根据this可以提高性能。缺点是,如果您遇到外键违规,您只会在最后一刻才学会它。
  3. 危险,但可能带来的巨大提升:disable constaint checks and triggers during the delete

答案 7 :(得分:0)

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

我遇到了类似的问题并将auto_explain用于auto_explain.log_nested_statements = true,这表明delete实际上在some_other_table上进行了seq_scans:

    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)

显然它正在尝试锁定另一个表中的引用行(不应该存在,否则删除将失败)。在引用表上创建索引后,删除速度提高了几个数量级。