【问题标题】:Postgres multi-column index (integer, boolean, and array)Postgres 多列索引(整数、布尔值和数组)
【发布时间】:2016-02-19 15:22:30
【问题描述】:

我有一个 Postgres 9.4 数据库,其中包含这样的表:

| 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

此查询运行良好,但我确信使用智能索引会更快。表中有大约 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. 用于此查询的正确复合索引类型是什么?我根本不在乎空间。

  2. 我如何对WHERE 子句中的术语进行排序重要吗?

【问题讨论】:

  • 您的谓词是否是不可变的?比如:总是current = FALSE?每个谓词有多少行以及选择性如何?表中有多少行,结果中有多少行(最小/最大/典型)?你的数组中有多少项?表定义:最好提供一个标准形式:一个完整​​的CREATE TABLE 脚本或者你在psql 中使用\d mytable 得到的东西。查询计划? ...还有更多,请考虑tag info of [postgresql-performance]中的说明。
  • 1.我只是定期选择current = FALSE,但其他部分有所不同。 2. 表中有大约 250,000 行,我一次选择 0-25,0000 之间的任何地方,但 LIMIT 有几百个 3. 存储数组中最多有 9 个项目,我'正在比较我的输入数组中的 0-9 个项目
  • 请添加您的实际查询。 LIMIT 改变了它的性质。并考虑我的其余观点。
  • 谢谢,欧文。我刚刚用CREATE TABLE 语法完成了我的编辑。

标签: postgresql indexing postgresql-9.4 postgresql-performance


【解决方案1】:
  1. 什么是用于该查询的正确复合索引类型?我根本不在乎空间。

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

安装附加模块btree_gin(或分别为btree_gist)后,您可以与integer 列结合使用。

但是,这不包括 boolean 数据类型,这通常作为索引列没有意义。只有两个(三个包括NULL)可能的值,它的选择性不够。

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

最后,虽然索引可以用于排序的输出,但这可能不会像您显示的那样申请查询(除非您选择表的大部分)。
不适用于更新的问题。

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

我会完全删除boolean 列current 上的无意义索引,而仅rank 上的索引可能永远不会用于此查询。

  1. 我如何对WHERE 子句中的术语进行排序是否重要?

WHERE 条件的顺序完全不相关。

问题更新后的补充

索引的效用受选择性标准的约束。如果选择了超过大约 5%(取决于各种因素)的表,则对整个表的顺序扫描通常比处理任何索引上的开销要快 - 预排序输出除外,这是索引在这种情况下仍然有用的一件事。

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

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

即使添加了您的信息,很多相关信息仍然一无所知。我将假设:

  1. 您的谓词NOT (ARRAY[100,200] && dn_ids) 不是非常有选择性。排除 1 到 10 个 ID 值通常应该保留大部分行,除非您在 dn_ids 中具有非常少的不同元素。
  2. 最具选择性的谓词是other_id = 5。
  3. 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 并在访问堆(表)之前过滤行。如果索引中没有dn_ids,甚至可能会更快:

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

GiST 索引在Postgres 9.5 due to this new feature 中可能会变得更有趣:

允许 GiST 索引执行仅索引扫描(Anastasia Lubennikova, Heikki Linnakangas, Andreas Karlsson)

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

【讨论】:

  • 感谢您的详细撰写。我已经按照建议用您的部分复合索引替换了我的索引,并且看到了明显的加速。我还在随机查询样本上对该索引和其他索引的各种组合进行了基准测试。有趣的是,rank 和other_id 的存在带来了最大的改进,而dn_ids 和current 上的部分索引进行了较小的改进。
  • @Devin:部分索引的有用性主要取决于索引条件排除的行的份额 - 越多越好。通常,重复查询执行(只要索引被缓存)只会略微改善,btree 索引的深度增长非常缓慢。第一次调用可以产生更大的差异,并且较小的内存占用增加了留在缓存中的机会。最后,索引只更新包含的行(更便宜的写入)。
【解决方案2】:

不幸的是,我认为您不能将 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 子句,所以很难说。如果您使用大于 1/5 的表,则 SEQ 扫描可能是最快的选择,特别是如果您使用标准 HDD 而不是固态硬盘。如果您知道 IDX 扫描更快(您已经使用 enable_seqscan false 测试过,您可能需要尝试微调您的 random_page_cost 或 effective_cache_size。

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2016-04-08
      • 2010-12-16
      • 1970-01-01
      • 2020-03-25
      • 2019-06-18
      • 1970-01-01
      • 1970-01-01
      • 2017-11-17
      • 2018-04-01
      相关资源
      最近更新 更多