我最近注意到一个包含 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 时没有这个问题。
(不可写)CTE 现在通过类似子查询等进行优化,除非对它们应用
NOT MATERIALIZED
。请参阅https://www.postgresql.org/docs/current/queries-with.html。因此,您将不会再在第 12 页上看到此行为。
是否可以让优化器注意到我已将 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;