具有许多不使用部分索引的值的 Postgres IN 子句

Posted

技术标签:

【中文标题】具有许多不使用部分索引的值的 Postgres IN 子句【英文标题】:Postgres IN clause with many values not using partial index 【发布时间】:2018-06-07 00:29:45 【问题描述】:

我使用的是 Postgres 9.2.24。

我有一个名为 _order 的表,大约有 100,000,000 行。该表有一个名为merged_id int8 的列。 _order 行中约有 2,000,000 行具有 merged_id 值,其余为 null。

我在搜索 _order 时发现了两种不同的 Postgres 行为,使用查询

select * from _order where merged_id in ( 10001 ,10002 ,10003 ....., 11000);

如果我创建这样的索引:

create index order_merged_id_index on _order(merged_id);

无论 in 子句中有多少个 id(测试从 1 到 50 到 100 到 200 到 1000)EXPLAIN 显示搜索将使用 index_scan

但如果我改为创建这个部分索引:

create index order_merged_id_index on _order(merged_id) where merged_id is not null;

EXPLAINWHERE 子句中显示seq_scan 表示超过100 个ID 号。

这是为什么? 有什么办法可以解决吗?

【问题讨论】:

【参考方案1】:

您正在运行outdated version of Postgres。考虑尽快升级。

有很多可能的原因。我怀疑过时版本的选择性估计存在弱点。我依稀记得最多 100 个值用于涉及数组的查询计划,这些值后来得到了改进。 IN 表达式通常在内部转换为 = ANY (ARRAY[...]):

How to use ANY instead of IN in a WHERE clause with Rails?

无论哪种方式,您都可以通过在查询中重复部分索引的谓词来修复该行为:

SELECT * FROM _order 
WHERE merged_id IN ( 10001 ,10002 ,10003 ....., 11000)
AND   merged_id is not null;  -- logically redundant

您的服务器配置可能存在其他问题,例如成本设置或表格统计信息:

Keep PostgreSQL from sometimes choosing a bad query plan

并且不要忘记在创建部分索引后在您的表上运行ANALYZE 至少一次。或者,最好是VACUUM ANALYZE,但这对你的大桌子来说更贵。

PostgreSQL partial index unused when created on a table with existing data

但是,对于较长的值列表,可以使用更有效的查询变体:

SELECT o.*
FROM   unnest('10001 ,10002 ,10003 ....., 11000'::int8[]) merged_id
JOIN   _order o USING (merged_id);

见:

Optimizing a Postgres query with a large IN

【讨论】:

非常感谢您给我的建议。

以上是关于具有许多不使用部分索引的值的 Postgres IN 子句的主要内容,如果未能解决你的问题,请参考以下文章

JQ:查找具有特定属性值的对象的数组索引

添加具有基于从文件名中提取的日期的值的列(值的长度 (1) 与索引的长度 (50) 不匹配)

如何对具有纪元值的列进行 JOIN 查询,忽略 postgres 中的时间部分

合并具有部分相同值的行

如何找到具有一定数量大于阈值的值的窗口的索引?

POSTGRES:如何仅在另一个值不存在时选择具有某个值的行,在这种情况下选择另一个值?