【问题标题】:CTE optimisationsCTE 优化
【发布时间】:2023-03-20 01:10:02
【问题描述】:

我最近注意到一个包含 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 转换架构时没有这个问题。

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

【问题讨论】:

  • 如果您在过滤器中也使用 WHERE id = '0000000001' 来读取_log_instantaneous_schedule 会怎样?
  • 我的示例已简化,您的建议并不总是适用于我的用例。
  • 它甚至被过度简化了:thing_readings CTE 在选择列表中没有 thing_id。
  • 尽管SELECT在子查询中使用了reading,但您并没有使用它。正如所写,我认为您的“简化”查询仍然只是进行EXISTS 测试的一种令人难以置信的迂回方式。 readings.reading 应该包含在最终选择列表中还是应该在外部 where 子句中进行测试?严重匿名的查询是在浪费每个人的时间,-1。 请解释查询的目的

标签: postgresql common-table-expression


【解决方案1】:

是否有可能让优化器注意到我已将 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;

【讨论】:

  • 我刚刚尝试了这种方法,但同样的问题仍然存在 - EXPLAIN 仍然显示出巨大的性能损失。显然,如果我再次将 WHERE 子句硬编码到外连接中,那么该计划几乎不会花费任何成本,但我试图避免这样做。
  • @GrahamB 是的,问题是WHERE 子句不会被向下推,它是横向的,而 Pg 不会这样做。看看更新的方法。 ORDER BY ... LIMIT 1 的性能也可能很糟糕。
  • @GrahamB 另外,在匿名化您的查询时,您已经让它变得荒谬了。 thing_readings.thing_id 不在子查询的选择列表中,并且未使用 thing_timestamp。我怀疑您可以完全取消所有这些子查询和LIMIT,但是对于虚假的无效查询,我真的无法分辨。请发布 working 查询,以及 create table 和 insert 语句以提供虚拟数据以供其处理。
  • @CraigRinger:是的,这就是我在一小时前对 OP 的评论中所说的。
  • 查看我的编辑以了解我在使用这种方法时也遇到的问题。
猜你喜欢
  • 1970-01-01
  • 2021-12-12
  • 1970-01-01
  • 1970-01-01
  • 2023-02-26
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
相关资源
最近更新 更多