OR 运算符的索引:a=x 或 b=x

Ale*_*pov 4 postgresql performance index postgresql-performance

我有一个包含三个整数列ida的表b
我想获取所有记录,其中ab匹配指定参数排序id

select id, a, b from t where a=x or b=x order by id
Run Code Online (Sandbox Code Playgroud)

请注意,和 的x值相同。ab

这里最合适的索引是什么?

更新:我们总是在列和中寻找相同的值,这一事实有什么用处吗?我们可以为此创建一个表达式索引吗?ab

Erw*_*ter 5

简单快速的解决方案是(a, id)和上的两个索引(b, id)。确保a分别b作为前导列:

添加的内容id不会对您的特定查询有帮助,您也可以只创建它们(a)(b)获取@shx 解释的位图索引扫描。但是两个整数的索引与一列的索引完全相同。可能还有其他用例实际上从添加的id.

如果您的实际SELECT列表很窄,就像您的示例一样,并且满足一些先决条件,我会选择仅索引扫描。在和
上创建索引。(a, b, id)(b, a, id)

这个等效查询(假设id或至少(id,a,b)是唯一的)帮助 Postgres 选择仅索引扫描的查询计划:

EXPLAIN
SELECT id,a,b FROM t WHERE a = 10
UNION
SELECT id,a,b FROM t WHERE b = 10
ORDER  BY id;
Run Code Online (Sandbox Code Playgroud)
排序(成本=110.45..112.92行=988宽度=12)
  排序键:t.id
  -> HashAggregate (成本=51.42..61.31 行=988 宽度=12)
        组密钥:t.id、ta、tb
        - >追加(成本= 0.42..44.02行= 988宽度= 12)
              -> 在 t 上使用 t_ab_id_idx 仅索引扫描(成本=0.42..17.07 行=494 宽度=12)
                    指数条件:(a = 10)
              -> 在 t t_1 上使用 t_ba_id_idx 仅索引扫描(成本=0.42..17.07 行=494 宽度=12)
                    指数条件:(b = 10)

性能取决于数据分布、写入模式和值频率等。在我对 pg 9.5 的测试中,我看到与 @shx 的答案中的解决方案类似的性能 - 只要我们选择整行(即堆元组并不比索引元组大很多)。

通常,基础表中有额外的列 - 这根本不会影响此查询的性能,而替代方案会失去优势,因为它必须读取更多页面以获取基础表中更宽的行。

回答添加的问题

我们总是在 a 列和 b 列中寻找相同的值,这一事实有什么用处吗?我们可以为此创建一个表达式索引吗?

我想不出一种方法可以按原样利用这一点。索引条目只能引用单个表行。从理论上讲,GIN 索引(ARRAY[a,b]) 可能会起作用,但我无法用它获得有用的结果(我也没想到)。

您需要基表中每行两行(where 除外a = b才能为作业启用 b 树索引的单次传递。实际上可以在MATERIALIZED VIEW. 仅当您的读取活动多于写入活动并且需要优化给定查询的性能时,增加的开销和维护成本才显得合理。你需要了解MV并知道何时刷新。

CREATE MATERIALIZED VIEW mv_t AS
SELECT a AS x, id, a, b FROM t
UNION  -- eliminate dupes
SELECT b AS x, id, a, b FROM t
ORDER  BY x, id;
Run Code Online (Sandbox Code Playgroud)

x是统一的搜索键。对于 中的每个不同值,基表中的行都会列出一次[a, b]。此查询返回与原始结果相同的结果,但速度更快:

SELECT id,a,b FROM mv_t WHERE x = 10 ORDER BY id;
Run Code Online (Sandbox Code Playgroud)

现在,查询可以从单个索引上的单次传递中受益。另外,MV 中的行像返回时一样进行物理排序,这有助于使用简单索引的方法:

CREATE INDEX mv_t_x_idx ON mv_t (x); -- simple
Run Code Online (Sandbox Code Playgroud)
排序(成本=88.01..90.48行=990宽度=12)
  排序键:id
  -> 在 mv_t 上使用 mv_t_x_idx 进行索引扫描(成本=0.42..38.75 行=990 宽度=12)
        指数条件:(x = 10)

也非常适合大型表的 BRIN 索引(Postgres 9.5+):

您可以再次进行仅索引扫描:

CREATE INDEX mv_t_full_idx ON mv_t (x, id, a, b);  -- covering index
Run Code Online (Sandbox Code Playgroud)
在 mv_t 上使用 mv_t_full_idx 仅索引扫描(成本=0.42..33.75 行=990 宽度=12)
  指数条件:(x = 10)