2016-03-02 66 views
0

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

这是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秒。

任何想法可能会导致此?

+0

。一定是。在你的情况下,它会产生所有的元组,即使那些rn = 1。作为临时表的替代方法,您可以将CTE重写为子查询。 (这可能会是最快的) – wildplasser

回答

1

使用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以及子查询。

+0

您对CTE实施错了。您的EXISTS()查询看起来不错,除了翻转的<<'。 – wildplasser

+0

我注意到翻转的比较。但是如果你想解决这个问题的话,其中一个别名是错误的。 – shawnt00

+0

谢谢,我纠正了别名。我做了一个关于PostgeSQL如何实现CTE的简单搜索,但找不到任何其他文档说“将它们视为临时表”,但这并不意味着它们必须以这种方式实现。 –

2

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。这是语法相当于temp table情况,但是语义相当于接合子查询形式(它们产生恰好相同的查询计划,至少在这种情况下)。这是因为Postgres的优化器剔除了的视图,并将其与主查询(拉起)结合起来。你可以看到一个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的情况下由于一个CTE被无条件地执行

+0

CTE无条件执行意味着什么?谷歌搜索“CTE无条件执行”返回的相关结果很少...... –

+0

TFM说:http://www.postgresql.org/docs/9.5/static/queries-with.html 7.8.2'WITH中的数据修改语句是执行一次,并且总是完成,与主要查询是否读取其所有(或者甚至是任何)输出无关。“ – wildplasser

+0

不需要发愁。我曾假设“无条件”是某种数据库术语。顺便提一下,你引用的语句与我的情况无关(因为我的CTE中没有使用数据修改语句),但手册中还说“WITH查询的一个有用属性是每次执行只评估一次的父查询......但是,这个硬币的另一方面是,优化器不太可能将限制从父查询推送到WITH查询而不是普通的子查询中。' –