85

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

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

我试过:

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

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

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

4

8 回答 8

118

这一切都取决于...

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

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

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

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

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

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

  • 如果您删除表的大部分并将其余部分放入 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
-- ORDER BY ?             -- optionally order favorably while being at it

TRUNCATE tbl;             -- empty table - truncate is very fast for big tables

INSERT INTO tbl
TABLE tmp;        -- insert back surviving rows.

COMMIT;

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

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

之后跑VACUUM ANALYZE。或者(通常在走完路线后不需要)使其达到最小尺寸(采用排他锁)。对于大表考虑替代方案/或类似的:TRUNCATEVACUUM FULL ANALYZECLUSTERpg_repack

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

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

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

TRUNCATE不能用于具有来自其他表的外键引用的表,除非所有此类表也在同一命令中被截断。[...]

和:

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

于 2011-11-28T02:42:46.380 回答
5

我自己刚刚遇到了这个问题,对我来说,到目前为止,最快的方法是将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. 通过外键在引用该表的其他表上创建索引。在某些情况下,这可以将需要数小时的删除时间减少到几秒钟
  2. 延迟约束检查:目前尚不清楚这有多少改进,但据此可以提高性能。缺点是,如果你有外键违规,你只会在最后一刻才知道。
  3. 危险但可能很大的提升:在删除期间禁用约束检查和触发器
于 2020-06-28T20:52:38.550 回答
4

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

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

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

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

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

于 2011-11-28T06:37:16.757 回答
2

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

这不是专家建议。

于 2011-11-28T02:34:45.490 回答
2

两个可能的答案:

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

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

于 2011-11-28T02:40:51.970 回答
2

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

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

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

于 2011-11-28T02:42:10.063 回答
1

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

于 2011-11-28T02:34:15.937 回答
0

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

我遇到了类似的问题并使用auto_explainwith auto_explain.log_nested_statements = true,这表明delete实际上是在 seq_scans 上进行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)

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

于 2017-11-10T17:53:19.973 回答