PostgreSQL 多列索引未完全使用

Posted

技术标签:

【中文标题】PostgreSQL 多列索引未完全使用【英文标题】:PostgreSQL multicolumn index not fully used 【发布时间】:2020-10-19 00:43:55 【问题描述】:

我在 PostgreSQL 12.3 上有一个大型(约 1.1 亿行)表,其相关字段可以由以下 DDL 描述:

CREATE TABLE tbl
(
    item1_id integer,
    item2_id integer,
    item3_id integer,
    item4_id integer,
    type_id integer
)

我们经常执行的查询之一是:

SELECT type_id, item1_id, item2_id, item3_id, item4_id
FROM tbl
WHERE
    type_id IS NOT NULL
    AND item1_id IN (1, 2, 3)
    AND (
        item2_id IN (4, 5, 6)
        OR item2_id IS NULL
    )
    AND (
        item3_id IN (7, 8, 9)
        OR item3_id IS NULL
    )
    AND (
        item4_id IN (10, 11, 12)
        OR item4_id IS NULL
    )

虽然我们对每个单独的列都有索引,但查询仍然相对较慢(几秒钟)。为了优化这一点,我创建了以下索引:

CREATE INDEX tbl_item_ids
    ON public.tbl USING btree
    (item1_id ASC, item2_id ASC, item3_id ASC, item4_id ASC)
    WHERE type_id IS NOT NULL;

不幸的是,查询性能几乎没有提高 - EXPLAIN 告诉我这是因为虽然使用这个新创建的索引完成了索引扫描,但只有 item1_id 用作 Index Cond,而所有其他过滤器都应用在表级别(即普通的Filter)。

我不确定为什么没有完整使用索引(或者至少不使用item1_id 列)。这有明显的原因吗?有没有办法可以重组索引或查询本身以帮助提高性能?

【问题讨论】:

此查询平均返回多少行(或行的百分比)?数字 1 到 12 的参数是否可以具有任何值并经常更改? @TheImpaler 查询将返回 100 到 30k 行之间的任何位置。平均而言,大约 5k。 IN 子句中的值的数量最多会在几到 100 之间变化,平均约为 20(根据我的实验,这似乎对计划没有影响)。 我会做几件事:1)将item1_id IS NOT NULL 添加到部分索引的WHERE 子句中,以使索引更小。 2)我会将 item_ids 从最具选择性到最不选择性(根据您的统计数据和经验)排序,并将它们按该顺序放置在索引中。作为旁注,ASC 并没有真正产生任何影响,因为您没有执行范围扫描。 感谢@TheImpaler。根据我的经验, item1 应该是最具选择性的,但我尝试了其他一些排列都无济于事。到目前为止,我能做的最好的事情就是在item1_id, type_idINCLUDE (item2_id, item3_id, item4_id) 上建立索引。我使用了type_id 并且没有使用WHERE 子句,因此如果我选择此选项,我可以将单列索引放在item1_id 上。这导致仅索引扫描比我的原始索引加速约 33%。不过,我会尝试升级到 12.4 看看是否有什么不同。 【参考方案1】:

如果第一列的条件使用相等比较 (=),则多列索引只能用于多于第一列。 IN= ANY 不符合条件。

因此,您最好为每列使用单独的索引,这些索引可以与位图或结合使用。

你应该尽量避免 ORWHERE 条件下,也许与

WHERE coalesce(item2_id, -1) IN (-1, 4, 5, 6)

其中 -1 是一个不存在的值。然后您可以在coalesce 表达式上使用索引。

【讨论】:

"如果第一列的条件使用相等比较,多列索引只能用于多于第一列" - 谢谢,我不是意识到这一点。这实际上有点令人失望。是否有技术限制,或者只是“没有人愿意实施它”的情况 当我说“只能使用”时,我的意思是“只能有效地使用”,我省略了仅索引扫描的用例。它需要像“索引跳过扫描”这样的东西才能做得更好;有一个补丁正在开发中,但我们还没有。

以上是关于PostgreSQL 多列索引未完全使用的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL 多列索引,包括数组

选择最小值时不使用索引的PostgreSQL多列组

postgresql:具有外键的多个多列索引?

带表达式的多列索引(PostgreSQL 和 Rails)

Postgresql:适用于(时间戳,字符串)的多列索引

postgresql 9.6 建立多列索引测试