Postgres 多列索引(整数、布尔值和数组)

Postgres multi-column index (integer, boolean, and array)

我有一个 Postgres 9.4 数据库 table 像这样:

| id | other_id | current | dn_ids                                | rank |
|----|----------|---------|---------------------------------------|------|
| 1  | 5        | F       | {123,234,345,456,111,222,333,444,555} | 1    |
| 2  | 7        | F       | {123,100,200,900,800,700,600,400,323} | 2    |

(更新) 我已经定义了几个索引。这是 CREATE TABLE 语法:

CREATE TABLE mytable (
    id integer NOT NULL,
    other_id integer,
    rank integer,
    current boolean DEFAULT false,
    dn_ids integer[] DEFAULT '{}'::integer[]
);

CREATE SEQUENCE mytable_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;

ALTER TABLE ONLY mytable ALTER COLUMN id SET DEFAULT nextval('mytable_id_seq'::regclass);
ALTER TABLE ONLY mytable ADD CONSTRAINT mytable_pkey PRIMARY KEY (id);

CREATE INDEX ind_dn_ids ON mytable USING gin (dn_ids);
CREATE INDEX index_mytable_on_current ON mytable USING btree (current);
CREATE INDEX index_mytable_on_other_id ON mytable USING btree (other_id);
CREATE INDEX index_mytable_on_other_id_and_current ON mytable USING btree (other_id, current);

我需要像这样优化查询:

SELECT id, dn_ids
FROM mytable
WHERE other_id = 5 AND current = F AND NOT (ARRAY[100,200] && dn_ids)
ORDER BY rank ASC
LIMIT 500 OFFSET 1000

此查询运行良好,但我确信使用智能索引会更快。 table 中大约有 250,000 行,我总是将 current = F 作为谓词。我正在与存储数组进行比较的输入数组也将具有 1-9 个整数。 other_id 可以变化。但通常,在限制之前,扫描将匹配 0-25,000 行。

这是一个例子EXPLAIN:

Limit  (cost=36944.53..36945.78 rows=500 width=65)
  ->  Sort  (cost=36942.03..37007.42 rows=26156 width=65)
        Sort Key: rank
        ->  Seq Scan on mytable  (cost=0.00..35431.42 rows=26156 width=65)
              Filter: ((NOT current) AND (NOT ('{-1,35257,35314}'::integer[] && dn_ids)) AND (other_id = 193))

此站点上的其他答案和 Postgres docs 建议可以添加复合索引以提高性能。 [other_id, current] 我已经有一个了。我还在各个地方读到,除了 WHERE 子句之外,索引还可以提高 ORDER BY 的性能。

  1. 用于此查询的复合索引的正确类型是什么?我根本不关心space。

  2. 我如何排列 WHERE 子句中的条款很重要吗?

最简单的查询索引是 mytable(other_id, current)。这处理前两个条件。这将是一个普通的 b 树类型索引。

您可以使用 mytable(dn_ids) 上的 GIST 索引来满足数组条件。

但是,我认为您不能在一个索引中混合使用不同的数据类型,至少不能没有扩展。

  1. What's the right type of compound index to use for this query? I don't care about space at all.

这个要看完整的情况。无论哪种方式,在您的情况下,您已经拥有的 GIN 索引很可能优于 GiST 索引:

安装附加模块 btree_gin (or btree_gist 后,您可以分别与 integer 列组合。

  • Multicolumn index on 3 fields with heterogenous data types

但是,这不包括 boolean 数据类型,该数据类型通常作为索引列开头没有意义。只有两个(三个,包括 NULL)个可能值,select 不够。

并且普通的 btree 索引对于 integer 更有效。虽然两个 integer 列上的多列 btree 索引肯定会有所帮助,但您必须仔细测试在多列 GIN 索引中组合 (other_id, dn_ids) 是否物有所值。可能不会。 Postgres 可以相当有效地在位图索引扫描中组合多个索引。

最后,虽然索引可用于排序输出,但申请像您这样的查询可能不值得显示(除非您 select table).
不适用于更新的问题。

部分索引可能是一个选项。除此之外,您已经拥有所需的所有索引

我会完全删除 booleancurrent 上毫无意义的索引,而 rank 上的索引可能从未用于此查询。

  1. Does it matter much how I order the terms in the WHERE clause?

WHERE 条件的顺序完全无关。

问题更新后的附录

索引的效用受限于 selective 标准。如果 table 的大约 5%(取决于各种因素)被 select 编辑,则对整个 table 的顺序扫描通常比处理任何索引的开销更快 -除了 预排序输出 ,这是索引在这种情况下仍然有用的一件事。

对于从 250,000 行中获取 25,000 行的查询,索引主要用于此 - 如果附加 [=23,这将变得更加有趣=] 子句。一旦满足 LIMIT,Postgres 就可以停止从索引中获取行。

请注意,Postgres 始终需要读取 OFFSET + LIMIT 行,因此性能会随着两者的总和而下降。

即使有了您添加的信息,很多相关内容仍然不为人知。我将假设

  1. 您的谓词 NOT (ARRAY[100,200] && dn_ids) 非常 select 。排除 1 到 10 个 ID 值通常应该保留大部分行,除非 dn_ids.
  2. 中的不同元素很少
  3. 最 selective 谓词是 other_id = 5
  4. 大部分行已被 NOT current 删除。
    旁白:current = F 在标准 Postgres 中不是有效语法。必须是 NOT currentcurrent = FALSE;

虽然 GIN 索引可以比任何其他索引类型更快地识别 少数 具有匹配数组的行,但这似乎与您的查询无关。我最好的猜测是这个部分,多列 btree 索引:

CREATE INDEX foo ON mytable (other_id, rank, dn_ids)
WHERE NOT current;

btree 索引中的数组列 dn_ids 不支持 && 运算符,我只是包含它以允许 index-only scans 并在访问堆之前过滤行(table).索引中没有 dn_ids 甚至可能更快:

CREATE INDEX foo ON mytable (other_id, rank) WHERE NOT current;

GiST 索引在 Postgres 9.5 due to this new feature:

中可能会变得更有趣

Allow GiST indexes to perform index-only scans (Anastasia Lubennikova, Heikki Linnakangas, Andreas Karlsson)

旁白:current is a reserved word 在标准 SQL 中,即使它在 Postgres 中被允许作为标识符。
除了 2:我假设 id 是一个实际的 serial 列,并设置了列默认值。只是像您演示的那样创建一个序列,什么也做不了。

  • Auto increment SQL function

不幸的是,我认为您不能将 BTree 和 GIN/GIST 索引组合成一个复合索引,因此计划者将不得不在使用 other_id 索引或dn_ids 指数。正如您所指出的,使用 other_id 的一个优点是您可以使用多列索引来提高排序性能。你会这样做的方式是

 CREATE INDEX index_mytable_on_other_id_and_current
           ON mytable (other_id, rank) WHERE current = F;

这是使用部分索引,当您按排名排序并在 other_id 上查询时,将允许您跳过排序步骤。

根据 other_id 的基数,这样做的唯一好处可能是排序。因为您的计划有一个 LIMIT 子句,所以很难分辨。如果您使用 table 的 1/5 以上,则 SEQ 扫描可能是最快的选择,尤其是当您使用标准 HDD 而不是固态硬盘时。如果您的规划人员在知道 IDX 扫描速度更快时坚持使用 SEQ 扫描(您已经使用 enable_seqscan false 进行了测试,您可能想尝试微调 random_page_costeffective_cache_size

最后,我建议不要保留所有这些索引。找到你需要的,然后剔除其余的。索引会导致插入的性能大幅下降(尤其是多列和 GIN/GIST 索引)。