繁体   English   中英

在Postgres中使用CTE进行删除比使用temp表慢

[英]Delete using CTE slower than using temp table in Postgres

我想知道是否有人可以解释为什么使用CTE而不是临时表可以运行这么长时间……我基本上是从客户表中删除重复信息(为什么存在重复信息超出了本文的范围)。

这是Postgres 9.5。

CTE版本是这样的:

with targets as
    (
        select
            id,
            row_number() over(partition by uuid order by created_date desc) as rn
        from
            customer
    )
delete from
    customer
where
    id in
        (
            select
                id
            from
                targets
            where
                rn > 1
        );

我在运行了一个多小时后于今天早晨杀死了该版本。

临时表的版本是这样的:

create temp table
    targets
as select
    id,
    row_number() over(partition by uuid order by created_date desc) as rn
from
    customer;

delete from
    customer
where
    id in
        (
            select
                id
            from
                targets
            where
                rn > 1
        );

此版本完成大约7秒钟。

知道是什么原因造成的吗?

CTE速度较慢,因为它必须不更改地执行(通过CTE扫描)。

TFM(第7.8.2节)指出: WITH中的数据修改语句仅执行一次,并且始终执行至完成,而与主查询是否读取其所有(或实际上)任何输出无关。 请注意,这与WITH中的SELECT规则不同:如上一节所述,仅在主查询需要其输出时,才执行SELECT的执行。

因此这是一个优化障碍 ; 对于优化者,不允许拆除CTE,即使这样做会导致更明智的计划并获得相同的结果。

不过,CTE解决方案可以重构为联接的子查询(类似于问题中的临时表)。 在postgres中,如今,联接子查询通常比EXISTS()变体快。

DELETE FROM customer del
USING ( SELECT id
        , row_number() over(partition by uuid order by created_date desc)
                 as rn
        FROM customer
        ) sub
WHERE sub.id = del.id
AND sub.rn > 1
        ;

另一种方法是使用TEMP VIEW 这在语法上等效于temp table情况,但在语义上等效于联接的子查询形式(至少在这种情况下,它们产生完全相同的查询计划)。 这是因为Postgres的优化程序会分解视图,并将其与主查询结合起来(上 )。 您可能会在PG中将view视为一种宏。

CREATE TEMP VIEW targets
AS SELECT id
        , row_number() over(partition by uuid ORDER BY created_date DESC) AS rn
FROM customer;

EXPLAIN
DELETE FROM customer
WHERE id IN ( SELECT id
            FROM targets
            WHERE rn > 1
        );

[更新:我错了,因为CTE必须始终执行才能完成,只有数据修改CTE才是这种情况]

与使用临时表相比,使用CTE可能会导致不同的瓶颈。 我不熟悉PostgreSQL如何实现CTE,但是它很可能在内存中,因此,如果您的服务器内存不足,并且CTE的结果集很大,那么您可能会遇到问题。 我将在运行查询时监视服务器,并尝试查找瓶颈所在。

另一种执行删除的方法可能比两种方法都快:

DELETE C
FROM
    Customer C
WHERE
    EXISTS (SELECT * FROM Customer C2 WHERE C2.uuid = C.uuid AND C2.created_date > C.created_date)

那不会处理您与created_date完全匹配的情况,但是也可以通过将id添加到子查询中来解决。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM