sql - 使用 CTE 删除比在 Postgres 中使用临时表慢

标签 sql postgresql common-table-expression

我想知道是否有人可以解释为什么使用 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 节)指出: Data-modifying statements in WITH are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule for SELECT in WITH: as stated in the previous section, execution of a SELECT is carried only as far as the primary query demands its output.

因此它是一个优化障碍;对于优化者来说,拆除 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。这在语法上等同于临时表的情况,但在语义上等同于联合子查询形式(它们产生完全相同的查询计划,至少在这种情况下)。这是因为 Postgres 的优化器拆解 View 并将其与主查询结合(上拉)。您可以将 view 视为 PG 中的一种宏。

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 的情况]

关于sql - 使用 CTE 删除比在 Postgres 中使用临时表慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35751276/

相关文章:

sql - PostgreSQL:如何使用包含时间戳的字符列

sql - IN() 子句中的 PostgreSQL ORDER BY 值

tsql - 如何将 CTE 用作循环?

sql - 如何将查询的 CTE 输出保存到临时表或表变量中

sql - 查询数据如何使用 kusto(Azure 数据资源管理器)KQL 中的偏移量进行分页

sql - 如何在 SQL 中获取最接近请求参数的值?

postgresql - 为什么 char 数据类型会自动转换为 bpchar?

postgresql - 如何在数据库外使用斜杠命令?

ruby-on-rails - 数据库中的大数组不会在 Rails View 中加载

sql-server - 如何让 CTE 在 T-SQL/MSSQL 中再次搜索我的数据?