CTE 优化

问题描述 投票:0回答:1

我最近注意到一个包含 CTE 的性能不佳的查询。

从运行

EXPLAIN
看来,如果我在 CTE 中有 2 个连续的表,其中第一个应用了
WHERE
过滤器,postgres 优化器实际上并不限制行集,因此第二个表查找是非常慢:

WITH thing_data AS (
    SELECT * FROM things WHERE id = '0000000001'
), thing_readings AS (
    SELECT  thing_timestamp
    FROM reading_log_instantaneous_schedule
    INNER JOIN thing_data
    ON thing_id = thing_data.id
    ORDER BY thing_timestamp DESC LIMIT 1
),                   
SELECT thing_data.*
FROM thing_data
LEFT OUTER JOIN thing_readings
ON thing_data.id = thing_readings.thing_id 

基本上,读数表中的内部联接并没有受益于

INNER JOIN thing_data on thing_id = thing_data.id
,并且实际上是对读数表中的所有行进行扫描。

是否可以让优化器注意到我已将

thing_data
记录集限制为只有一行,从而使后续连接快速,而不是超级慢?

编辑:对匿名性较差的查询表示歉意。

我创建了一个 SQLFiddle 来演示我遇到的问题 - 我仍然需要添加 2 个

WHERE
子句(不利于代码可维护性等) - 即使忘记 CTE 并按照 Craig 建议使用常规连接表,问题仍然存在。我更习惯 SQL Server,当我转换 schema 时没有这个问题。

http://sqlfiddle.com/#!15/17c82/1

postgresql common-table-expression
1个回答
4
投票

PostgreSQL 12 及以上版本的答案

(不可写)CTE 现在通过类似子查询等进行优化,除非对它们应用

NOT MATERIALIZED
。请参阅https://www.postgresql.org/docs/current/queries-with.html。因此,您将不会再在第 12 页上看到此行为。

PostgreSQL 11 及以下版本的答案:

是否可以让优化器注意到我已将 thing_data 记录集限制为只有一行,从而使后续连接快速而不是超级慢?

PostgreSQL 中没有,至少在 9.4 或更早版本中是这样。希望以后会有所改变。

PostgreSQL 中的 CTE 是优化栅栏 - 本质上,规划器无法将限定符推入其中,或将限定符从其中拉出。

当出现问题时,您需要返回到在

FROM
子句中使用旧式子查询。

SELECT thing_data.*
FROM (
    SELECT * FROM things WHERE id = '0000000001'
) data_thing
LEFT OUTER JOIN (
    SELECT  thing_timestamp 
    FROM reading_log_instantaneous_schedule 
    INNER JOIN thing_data on thing_id = thing_data.id 
    ORDER BY thing_timestamp DESC LIMIT 1)
thing_readings 
ON thing_data.id = thing_readings.thing_id;

因为

FROM
中的子查询允许限定符下推/上拉。但在这种情况下,您确实想横向应用
WHERE
子句。最好通过进一步简化、消除子查询来完成:

SELECT thing_data.*
FROM things
LEFT OUTER JOIN (
    SELECT  thing_timestamp 
    FROM reading_log_instantaneous_schedule 
    INNER JOIN thing_data on thing_id = thing_data.id 
    ORDER BY thing_timestamp DESC LIMIT 1)
thing_readings 
ON thing_data.id = thing_readings.thing_id
WHERE things.id = '0000000001'

但是,整个事情似乎是一种极其复杂的方式(基于您的 SQLFiddle http://sqlfiddle.com/#!15/17c82/3):

SELECT  things.*, thingreadings.reading 
FROM things 
LEFT OUTER JOIN thingreadings ON thingreadings.thingid = things.id
WHERE things.id = '1'
ORDER BY reading DESC LIMIT 1;
© www.soinside.com 2019 - 2024. All rights reserved.