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
的性能。
用于此查询的复合索引的正确类型是什么?我根本不关心space。
我如何排列 WHERE
子句中的条款很重要吗?
最简单的查询索引是 mytable(other_id, current)
。这处理前两个条件。这将是一个普通的 b 树类型索引。
您可以使用 mytable(dn_ids)
上的 GIST 索引来满足数组条件。
但是,我认为您不能在一个索引中混合使用不同的数据类型,至少不能没有扩展。
- 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).
不适用于更新的问题。
部分索引可能是一个选项。除此之外,您已经拥有所需的所有索引。
我会完全删除 boolean
列 current
上毫无意义的索引,而 rank
上的索引可能从未用于此查询。
- 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
行,因此性能会随着两者的总和而下降。
即使有了您添加的信息,很多相关内容仍然不为人知。我将假设:
- 您的谓词
NOT (ARRAY[100,200] && dn_ids)
不 非常 select 。排除 1 到 10 个 ID 值通常应该保留大部分行,除非 dn_ids
. 中的不同元素很少
- 最 selective 谓词是
other_id = 5
。
- 大部分行已被
NOT current
删除。
旁白:current = F
在标准 Postgres 中不是有效语法。必须是 NOT current
或 current = 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_cost
或 effective_cache_size
。
最后,我建议不要保留所有这些索引。找到你需要的,然后剔除其余的。索引会导致插入的性能大幅下降(尤其是多列和 GIN/GIST 索引)。
我有一个 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
的性能。
用于此查询的复合索引的正确类型是什么?我根本不关心space。
我如何排列
WHERE
子句中的条款很重要吗?
最简单的查询索引是 mytable(other_id, current)
。这处理前两个条件。这将是一个普通的 b 树类型索引。
您可以使用 mytable(dn_ids)
上的 GIST 索引来满足数组条件。
但是,我认为您不能在一个索引中混合使用不同的数据类型,至少不能没有扩展。
- 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).
不适用于更新的问题。
部分索引可能是一个选项。除此之外,您已经拥有所需的所有索引。
我会完全删除 boolean
列 current
上毫无意义的索引,而 rank
上的索引可能从未用于此查询。
- 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
行,因此性能会随着两者的总和而下降。
即使有了您添加的信息,很多相关内容仍然不为人知。我将假设:
- 您的谓词
NOT (ARRAY[100,200] && dn_ids)
不 非常 select 。排除 1 到 10 个 ID 值通常应该保留大部分行,除非dn_ids
. 中的不同元素很少
- 最 selective 谓词是
other_id = 5
。 - 大部分行已被
NOT current
删除。
旁白:在标准 Postgres 中不是有效语法。必须是current = F
NOT current
或current = 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_cost
或 effective_cache_size
。
最后,我建议不要保留所有这些索引。找到你需要的,然后剔除其余的。索引会导致插入的性能大幅下降(尤其是多列和 GIN/GIST 索引)。