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

Ant*_*eco 63 sql postgresql bigdata postgresql-performance sql-delete

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

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

我试过了:

DELETE FROM tbl WHERE id IN (select * from ids)
Run Code Online (Sandbox Code Playgroud)

那也是永远的.很难判断多久,因为我看不到它的进展直到完成,但查询仍然在2天后运行.

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

Erw*_*ter 84

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

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

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

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

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

  • 假设没有对所涉及的表进行并发写访问,或者您可能必须独占锁定表,或者此路由可能根本不适合您.

  • 根据您的设置,填充数据库手册相关章节中列出的一些要点也可能有用.

  • 如果你删除表的大部分,其余部分适合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.
Run Code Online (Sandbox Code Playgroud)

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

ANALYZE之后运行.或者,VACUUM ANALYZE如果您没有采用截断路线,或者VACUUM FULL ANALYZE您希望将其降至最小尺寸.对于大表,考虑替代品CLUSTER/ pg_repack:

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

DELETE FROM tbl t
USING  del_list d
WHERE  t.id = d.id;
Run Code Online (Sandbox Code Playgroud)

阅读手册中的" 注释"部分TRUNCATE.特别是(正如佩德罗在他的评论中指出的那样):

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

和:

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

  • 当然不是我想要做的,但是删除索引使我的删除现在变得飞快...现在只需在所有链接表上执行此操作以删除链接行,但是地狱,击败了我花在尝试让它工作上的所有时间没有 (2认同)
  • @AnthonyGreco:酷!不要忘记事后重新创建您仍然需要的索引。 (2认同)
  • 这是一个很好的解决方案,如果对某人来说不明显的话,只需添加忽略删除级联即可。 (2认同)
  • 非常非常聪明。我的 tbl 有 6000 万条记录,del_list 有 5600 万条记录。这样做我花了不到3分钟。像原来的问题一样,我不得不在运行 24 小时后中止它而没有完成。这是一个巨大的差异。 (2认同)

Tor*_*rge 8

我自己刚刚遇到这个问题,对我来说,到目前为止,最快的方法是将WITH查询USING结合使用

基本上,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;
Run Code Online (Sandbox Code Playgroud)

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

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

其他需要考虑的事情是

  1. 通过外键在引用此表的其他表上创建索引。在某些情况下,这可以将删除所需的时间减少到几秒钟
  2. 推迟约束检查:尚不清楚这会带来多少改进(如果有的话),但据此可以提高性能。缺点是,如果你有外键违规,你只会在最后一刻才知道。
  3. 危险但可能有很大的提升:在删除期间禁用约束检查和触发器