【问题标题】:Why can't Index Only Scan be used on index created with COALESCE?为什么不能对使用 COALESCE 创建的索引使用仅索引扫描?
【发布时间】:2015-10-16 08:59:33
【问题描述】:

PostgreSQL 9.4 创建表如下:

CREATE TABLE foo (
    id integer,
    date date,
    value numeric(14,3)
);

我正在使用ROW_NUMBER() 窗口函数和COALESCE 优化查询。为了最有效,我倾向于在以下查询中使用Index Only Scan

SELECT id, c_val
FROM (
    SELECT id, COALESCE(value, 0) c_val, ROW_NUMBER() OVER(PARTITION BY id ORDER BY date DESC NULLS LAST) rn
    FROM foo) sbt
WHERE sbt.rn = 1;

所以,如果我按如下方式创建索引:

CREATE INDEX ON foo (id, date DESC NULLS LAST, value);

策划者选择使用Index Only Scan,但如果我这样做:

CREATE INDEX ON foo (id, date DESC NULLS LAST, COALESCE(value, 0));

计划者只会做Index Scan

为什么?我试图避免在执行查询时评估COALESCE 函数的成本。为什么它不适用于Index Only Scan

【问题讨论】:

  • coalesce() 的“成本”几乎为零,不用担心。

标签: sql postgresql indexing internals


【解决方案1】:

我认为您错误地认为您的SELECT 中的COALESCE(value, 0) 在索引使用方面很重要。说实话,只有在返回行值之后进行视图转换。

就索引使用而言,重要的是您的WINDOW FUNCTION。首先,您按id 进行分区,然后您按date DESC NULLS LAST 对每个分区中的值进行排序。这两件事决定了像CREATE INDEX ON foo (id, date DESC NULLS LAST, ...) 这样的索引是有用的,无论你放在下一个位置。 请注意,如果您在创建索引时更改iddate 的顺序,PostgreSQL 将根本不使用该索引。

现在,您必须知道 INDEX ONLY SCAN 只有在索引本身存储了查询请求的整个未触及的行值时才能使用。 PostgreSQL manual之后:

如果索引存储原始索引数据值(而不是它们的一些有损表示),则支持仅索引扫描很有用,其中索引返回实际数据...

在您的情况下,您的第二个索引存储行的一些有损表示,因为最后一列的值由函数转换,并且查询要求 idvaluedate。 PostgreSQL 并不是很聪明地看到它只是将NULLs 替换为0。对他来说,这不是原始价值。所以我们需要访问表来获取原始的行值(最后使用普通的INDEX SCAN)。之后,值被格式化为输出,COALESCE(values, 0) 发生。

编辑:

我认为就您关于内部的问题而言,这种解释对您来说已经足够了。关于COALECE() 评估成本,我同意a_horse_with_no_name 的观点,您可能不应该担心这个。

【讨论】:

  • 啊,我不知道在COALESCE 的情况下首先发生提取,如果我可以这么说的话......
  • 既然我准确地回答了你的问题并解释了事情,并且你学到了一些你不知道的东西(正如你所说的),为什么不接受我的回答?
猜你喜欢
  • 2020-09-09
  • 2020-09-06
  • 1970-01-01
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 1970-01-01
  • 2021-08-15
  • 2011-09-25
相关资源
最近更新 更多