【问题标题】:Best way to delete millions of rows by ID按 ID 删除数百万行的最佳方法
【发布时间】:2012-01-07 14:53:18
【问题描述】:

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

我尝试将它们放在一个表中并分批执行 100 个。4 天后,它仍在运行,只删除了 2972​​68 行。 (我必须从 ID 表中选择 100 个 ID,删除该列表中的位置,从 ids 表中删除我选择的 100 个)。

我试过了:

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

这也需要很长时间。很难衡量多长时间,因为在完成之前我看不到它的进度,但查询在 2 天后仍在运行。

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

【问题讨论】:

  • 还剩多少行?另一种方法是将剩余的行选择到工作表中,然后重命名表。

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


【解决方案1】:

这一切都取决于...

  • 假设没有对相关表的并发写入访问权限,或者您可能必须以独占方式锁定表,或者这条路线可能根本不适合您。

  • 删除所有索引(可能除了删除本身所需的索引)。
    之后重新创建它们。这通常比索引的增量更新快得多。

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

  • 外键是否引用您的表?它们可以被删除吗?暂时删除?

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

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

  • 如果您删除表的大部分并且其余部分适合 RAM,那么最快和最简单的方法可能是:

BEGIN; -- typically faster and safer wrapped in a single transaction

SET LOCAL temp_buffers = '1000MB'; -- enough to hold the temp table

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.
-- ORDER BY ?             -- optionally order favorably while being at it

COMMIT;

这样您就不必重新创建视图、外键或其他依赖对象。你会得到一个没有臃肿的原始(排序)表。

阅读temp_buffers setting in the manual。只要表格适合内存,或者至少大部分适合内存,这种方法就很快。如果您的服务器在此操作过程中崩溃,事务包装器会防止丢失数据。

之后运行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 触发器 表格。

【讨论】:

  • 不幸的是,我确实有一些外键,但是我可以通过杀死所有键/删除/重新创建来执行您的建议。不这样做需要更多的时间,然后才去做。谢谢!
  • 当然不是我想要做的,但是删除索引让我的删除现在飞起来了......现在只需要在所有链接表上执行此操作以删除链接行,但是地狱,一直在节拍我花了很多时间试图让它在没有的情况下工作
  • @AnthonyGreco:酷!之后不要忘记重新创建您仍然需要的索引。
  • 这是一个很好的解决方案,如果对某人不明显,只需添加忽略删除级联。
  • 非常非常聪明。我的 tbl 有 6000 万条记录,而 del_list 有 5600 万条记录。这样做花了我不到 3 分钟的时间。像原来的问题一样做,我不得不在运行 24 小时后中止它而没有完成。这是一个巨大的差异。
【解决方案2】:

我自己刚刚遇到了这个问题,对我来说,到目前为止,最快的方法是结合使用 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

【讨论】:

  • 您甚至可以创建多个相互引用的此类表,就像我在一种情况下必须做的那样,我想删除所有孤立的行并且不再被任何其他表引用。 (WITH existing_items AS ( ... ), to_delete AS ( SELECT item_id FROM table LEFT JOIN existing_items e ON table.item_id = e.item_id WHERE e.item_id IS NULL ) DELETE FROM ...)
【解决方案3】:

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

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

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

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

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

【讨论】:

  • 嗯,我基本上是在做一个批量处理的逻辑,但是由于我的索引,它需要很长时间。我终于删除了所有索引(这是我不想做的事情),并且行很快被清除了。现在建立我所有的索引备份。不过谢谢!
【解决方案4】:

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

这不是专家建议。

【讨论】:

  • 根据要保留的行数和其他外键的复杂程度,这可以工作。也可以将好的行复制到临时。截断当前表。然后从 temp 复制回来。
【解决方案5】:

两个可能的答案:

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

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

【讨论】:

  • 1.我确实有约束(外键),当表中的一行被删除时会自动删除
  • 尝试explain (analyze,buffers,timing) ... 并找出您缺少哪些索引。
【解决方案6】:

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

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

无需从删除 ID 表中删除任何内容。为批号添加一个新列,并为第 1 批填写 1000,为第 2 批填写 1000 等,并确保删除查询包含批号。

【讨论】:

  • 无论我怎么尝试,结果都是钥匙在杀死我。即使只有 15 个也需要一分钟左右,这就是为什么我只做了 100 个。一旦我杀死了索引,它就飞了。不过谢谢!
【解决方案7】:

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

【讨论】:

  • 我真的很想避免这种情况,因为那样我只需要在所有外键上重做这个过程,但我很可能不得不这样做。谢谢
【解决方案8】:

如果您要从中删除的表被 some_other_table 引用(并且您不想暂时删除外键),请确保您在 referenceencing 列上有一个索引在some_other_table

我遇到了类似的问题,将auto_explainauto_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)

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

【讨论】:

    猜你喜欢
    • 2019-02-15
    • 2023-03-22
    • 1970-01-01
    • 2020-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-12
    相关资源
    最近更新 更多