【问题标题】:Postgresql slow delete from where existsPostgresql 从存在的地方缓慢删除
【发布时间】:2018-05-04 06:25:08
【问题描述】:

我在处理缓慢的删除查询时遇到了问题。我有一个模式,比如“目标”,其中包含在另一个表中都有等效表(相同的列和主键)的表,比如“delta”。我现在想从目标模式中删除 delta 模式中出现的所有行。我已经尝试过使用 DELETE FROM WHERE EXISTS 方法,但这似乎非常慢。这是一个示例查询:

DELETE FROM "target".name2phoneme
WHERE EXISTS(
  SELECT 1 FROM delta.name2phoneme d 
  WHERE name2phoneme.NAME_ID = d.NAME_ID 
  AND name2phoneme.PHONEME_ID = d.PHONEME_ID
);

这是两个表的布局(除了“delta”模式只有主键而没有外键)

CREATE TABLE name2phoneme
(
  name_id uuid NOT NULL,
  phoneme_id uuid NOT NULL,
  seq_num numeric(3,0),
  CONSTRAINT pk_name2phoneme PRIMARY KEY (name_id, phoneme_id),
  CONSTRAINT fk_name2phoneme_name_id_2_name FOREIGN KEY (name_id)
    REFERENCES name (name_id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION
    DEFERRABLE INITIALLY DEFERRED,
  CONSTRAINT fk_name2phoneme_phoneme_id_2_phoneme FOREIGN KEY (phoneme_id)
    REFERENCES phoneme (phoneme_id) MATCH SIMPLE
    ON UPDATE NO ACTION
    ON DELETE NO ACTION
    DEFERRABLE INITIALLY DEFERRED
)

“目标”表最初包含略多于 1800 万行,而增量表包含大约 370 万行(将从目标中删除)。

这是上述查询的 EXPLAIN 的输出:

"Delete on name2phoneme  (cost=154858.03..1068580.46 rows=6449114 width=12)"
"  ->  Hash Join  (cost=154858.03..1068580.46 rows=6449114 width=12)"
"        Hash Cond: ((name2phoneme.name_id = d.name_id) AND (name2phoneme.phoneme_id = d.phoneme_id))"
"        ->  Seq Scan on name2phoneme  (cost=0.00..331148.16 rows=18062616 width=38)"
"        ->  Hash  (cost=69000.01..69000.01 rows=3763601 width=38)"
"              ->  Seq Scan on name2phoneme d  (cost=0.00..69000.01 rows=3763601 width=38)"

我试图解释分析上述查询,但执行时间超过 2 小时,所以我将其杀死。

关于如何优化此操作的任何想法?

【问题讨论】:

  • 删除 370 万行有很多开销。我建议用你想要的行创建一个新表,截断现有的表,然后重新插入你想要的行。
  • 您可以尝试使用WHERE (name_id, phoneme_id) IN (SELECT name_id, phoneme_id FROM other_table) 并在(name_id, phoneme_id) 上添加索引,但考虑到表格的大小,我不希望有任何快速...
  • @jcaron 我尝试了explain DELETE FROM "target".name2phoneme WHERE (name_id, phoneme_id) in (SELECT d.name_id, d.phoneme_id FROM "delta".name2phoneme d);,结果与 WHERE EXISTS 方法的成本相同。
  • @GordonLinoff 创建一个包含我想要的行的新表需要一个 INSERT INTO ... WHERE NOT EXISTS 构造,它只会产生类似的结果(充其量),不是吗?我们每周都会创建增量表,因此增量模式中的行数可以少很多(甚至为 0)。如果只删除几行,效率会不会更低?

标签: sql postgresql sql-delete


【解决方案1】:

删除 370 万行非常耗时,因为查找每一行然后记录和删除行的开销很大。仅仅考虑所有脏页、日志记录和缓存未命中就令人难以置信——更不用说索引的更新了。

因此,这样的事情可以更快:

create temporary table temp_n2p as 
    select n2p.*
    from "target".name2phoneme n2p
    where not exists (select 1
                      from delta.name2phoneme d 
                      where n2p.NAME_ID = d.NAME_ID and
                            n2p.PHONEME_ID = d.PHONEME_ID
                     );

truncate table "target".name2phoneme;

insert into "target".name2phoneme
    select *
    from temp_n2p;

您还应该在截断之前删除索引,然后在之后重新创建它们。

【讨论】:

  • 有道理。我会试试这个方法,谢谢。但它不应该在不存在的地方吗?
  • @WouterVanDaele 。 . .是的,这是正确的逻辑。
【解决方案2】:

您是否尝试过以下任一方法:

DELETE 
FROM "target".name2phoneme t  
     USING delta.name2phoneme d 
WHERE t.NAME_ID = d.NAME_ID 
      AND t.PHONEME_ID = d.PHONEME_ID               
;

或者使用 WITH,但 Postgres 确实实现了 CTE,所以我不相信这对您的需求规模是否明智。

WITH cte AS (
      SELECT t.name_id, t.phoneme_id
      FROM "target".name2phoneme t  
      INNER JOIN delta.name2phoneme d ON t.NAME_ID = d.NAME_ID 
                            AND t.PHONEME_ID = d.PHONEME_ID               
      )
DELETE FROM "target".name2phoneme t
     USING cte d
WHERE t.NAME_ID = d.NAME_ID 
      AND t.PHONEME_ID = d.PHONEME_ID               
;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-11
    • 2021-10-19
    • 1970-01-01
    • 2019-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多