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

时间:2015-11-18 02:24:38

标签: postgresql indexing postgresql-9.4 postgresql-performance

我有一个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]上有一个。我还在不同的地方读过,除了ORDER BY子句之外,索引还可以提高WHERE的性能。

  1. 用于此查询的复合索引的正确类型是什么?我根本不在乎空间。

  2. 我在WHERE条款中订购条款的方式是否重要?

3 个答案:

答案 0 :(得分:4)

  
      
  1. 用于此查询的正确类型的复合索引是什么?我根本不在乎空间。
  2.   

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

一旦安装了附加模块btree_gin(或btree_gist),您就可以与integer列合并。

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

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

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

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

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

  
      
  1. 我在WHERE条款中订购条款的方式是否重要?
  2.   

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

问题更新后的附录

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

对于获取 25,000个250,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 currentcurrent = FALSE;
  4. 虽然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)

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

答案 1 :(得分:2)

不幸的是,我认为你不能将BTree和GIN / GIST索引合并为一个复合索引,因此规划者必须在使用other_id索引或{{ 1}}索引。正如您所指出的,使用other_id的一个优点是您可以使用多列索引来提高排序性能。你这样做的方式是

dn_ids

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

取决于other_id的基数,唯一的好处可能是排序。因为你的计划有LIMIT条款,所以很难说。如果您使用> SEQ扫描可能是最快的选择。表格的1/5,特别是如果您使用标准硬盘而不是固态硬盘。如果您知道IDX扫描速度更快(您已使用 CREATE INDEX index_mytable_on_other_id_and_current ON mytable (other_id, rank) WHERE current = F; 进行了测试),那么您的规划师会坚持使用SEQ扫描,您可能需要尝试微调enable_seqscan false或{{1 }}

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

答案 2 :(得分:0)

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

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

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