【问题标题】:Postgres combining multiple IndexesPostgres 组合多个索引
【发布时间】:2012-09-23 22:48:12
【问题描述】:

我有以下表格/索引 -

CREATE TABLE test
(
   coords geography(Point,4326), 
   user_id varchar(50), 
   created_at timestamp
);
CREATE INDEX ix_coords ON test USING GIST (coords);
CREATE INDEX ix_user_id ON test (user_id);
CREATE INDEX ix_created_at ON test (created_at DESC);

这是我要执行的查询:

select * 
from updates 
where ST_DWithin(coords, ST_MakePoint(-126.4, 45.32)::geography, 30000) 
and user_id='3212312' 
order by created_at desc
limit 60

当我运行查询时,它只使用ix_coords 索引。如何确保 Postgres 使用 ix_user_idix_created_at 索引以及查询?

这是一个新表,我在其中批量插入了生产数据。 test 表中的总行数:15,069,489

我正在使用 (effective_cache_size = 2GB) 运行 PostgreSQL 9.2.1(带有 Postgis)。这是我的本地 OSX,配备 16GB RAM、Core i7/2.5 GHz、非 SSD 磁盘。

添加EXPLAIN ANALYZE 输出 -

Limit  (cost=71.64..71.65 rows=1 width=280) (actual time=1278.652..1278.665 rows=60 loops=1)
   ->  Sort  (cost=71.64..71.65 rows=1 width=280) (actual time=1278.651..1278.662 rows=60 loops=1)
         Sort Key: created_at
         Sort Method: top-N heapsort  Memory: 33kB
         ->  Index Scan using ix_coords on test  (cost=0.00..71.63 rows=1 width=280) (actual time=0.198..1278.227 rows=178 loops=1)
               Index Cond: (coords && '0101000020E61000006666666666E63C40C3F5285C8F824440'::geography)
               Filter: (((user_id)::text = '4f1092000b921a000100015c'::text) AND ('0101000020E61000006666666666E63C40C3F5285C8F824440'::geography && _st_expand(coords, 30000::double precision)) AND _st_dwithin(coords, '0101000020E61000006666666666E63C40C3F5285C8F824440'::geography, 30000::double precision, true))
               Rows Removed by Filter: 3122459
 Total runtime: 1278.701 ms

更新:

根据下面的建议,我尝试了关于 cords + user_id 的索引:

CREATE INDEX ix_coords_and_user_id ON updates USING GIST (coords, user_id);

..但得到以下错误:

ERROR:  data type character varying has no default operator class for access method "gist"
HINT:  You must specify an operator class for the index or define a default operator class for the data type.

更新:

所以CREATE EXTENSION btree_gist; 解决了 btree/gist 复合索引问题。现在我的索引看起来像

CREATE INDEX ix_coords_user_id_created_at ON test USING GIST (coords, user_id, created_at);

注意:btree_gist 不接受 DESC/ASC。

新的查询计划:

Limit  (cost=134.99..135.00 rows=1 width=280) (actual time=273.282..273.292 rows=60 loops=1)
   ->  Sort  (cost=134.99..135.00 rows=1 width=280) (actual time=273.281..273.285 rows=60 loops=1)
         Sort Key: created_at
         Sort Method: quicksort  Memory: 41kB
         ->  Index Scan using ix_updates_coords_user_id_created_at on updates  (cost=0.00..134.98 rows=1 width=280) (actual time=0.406..273.110 rows=115 loops=1)
               Index Cond: ((coords && '0101000020E61000006666666666E63C40C3F5285C8F824440'::geography) AND ((user_id)::text = '4e952bb5b9a77200010019ad'::text))
               Filter: (('0101000020E61000006666666666E63C40C3F5285C8F824440'::geography && _st_expand(coords, 30000::double precision)) AND _st_dwithin(coords, '0101000020E61000006666666666E63C40C3F5285C8F824440'::geography, 30000::double precision, true))
               Rows Removed by Filter: 1
 Total runtime: 273.331 ms

查询的性能比以前好,几乎快了一秒,但仍然不是很好。我想这是我能得到的最好的??我希望在 60-80 毫秒左右。还从查询中获取order by created_at desc,又减少了 100 毫秒,这意味着它无法使用索引。无论如何要解决这个问题?

【问题讨论】:

  • Postgres 使用基于成本的计划器。即使它可以使用索引,它也可能不如不使用它快。您可以使用 random_page_cost 和 cpu* 成本变量来查看是否可以使用这些索引进行讨论。使用 explain analyze 查看它决定做什么以及它有多快。
  • 索引的使用还取决于可用的统计信息。实际上有多少行 user_id='3212312' ?在此查询之前(至少在填充表之后)您是否做过vacuum analyze
  • 要查看当ix_coords 索引不可用时它会做什么——它是否可以使用其他索引以及成本是多少——试试BEGIN; DROP INDEX ix_coords ON thetable; EXPLAIN ANALYZE the_query; ROLLBACK;

标签: postgresql postgis


【解决方案1】:

我不知道 Pg 是否可以将 GiST 索引和常规 b-tree 索引与位图索引扫描结合起来,但我怀疑不能。在不向 GiST 索引添加 user_id 列的情况下,您可能会获得最佳结果(因此对于不使用 user_id 的其他查询,它会变得更大且更慢)。

作为一个实验,你可以:

CREATE EXTENSION btree_gist;
CREATE INDEX ix_coords_and_user_id ON test USING GIST (coords, user_id);

这可能会导致一个大索引,但可能会提升该查询 - 如果它有效。请注意,维护这样的索引会显着减慢INSERTUPDATEs。如果您放弃旧的ix_coords,您的查询将使用ix_coords_and_user_id,即使它们没有过滤user_id,但它会比ix_coords 慢。两者都保留会使INSERTUPDATE 的减速更加严重。

btree-gist


已被完全改变问题的问题编辑废止;在编写时,用户有一个多列索引,他们现在已分成两个独立的索引):

您似乎没有在user_id 上进行过滤或排序,而只是在create_date 上。 Pg 不会(不能?)只使用像(user_id, create_date) 这样的多列索引的第二项,它也需要使用第一项。

如果你想索引create_date,为它创建一个单独的索引。如果您使用并需要(user_id, create_date) 索引并且通常不单独使用user_id,请查看是否可以反转列顺序。交替创建两个独立的索引,(user_id)(create_date)。当需要两列时,Pg 可以使用位图索引扫描组合两个独立的索引。

【讨论】:

  • 对不起,我的问题中有一些拼写错误,混合了 id 和 user_id,基本上它只是“user_id”。
  • 我添加了解释分析输出。感谢您的帮助。
  • @user310525 您似乎完全改变了您的索引定义,将ix_created_atuser_id 组件拆分为一个新索引。是不是老的错了?还是您更改了设置但没有解释?如果你改变它,最好解释和添加新材料,而不是默默地改变那里的东西,所以旧的答案在上下文中不再有意义。
  • PostgreSQL 可以使用多列索引的第二项,但它通常比完全忽略索引并直接进入表更昂贵。请参阅the manual 了解更多信息。
  • @CraigRinger postgres 不允许我在 coords + user_id 上创建要点索引?我已经更新了原来的问题。
【解决方案2】:

我认为 Craig 的回答是正确的,但我只是想添加一些内容(评论不适合)

您必须非常努力地强制 PostgreSQL 使用索引。查询优化器很聪明,有时它会认为顺序表扫描会更快。通常是对的! :) 但是,您可以使用一些设置(例如 seq_page_cost、random_page_cost 等)来尝试让它有利于索引。这是一些configurations 的链接,如果您觉得它没有做出正确的决定,您可能想要检查它。但是,再一次……我的经验是,大多数时候,Postgres 比我聪明! :)

希望这对您(或将来的某人)有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-15
    • 2018-04-08
    • 2021-10-30
    • 2015-11-11
    • 1970-01-01
    • 2016-09-25
    相关资源
    最近更新 更多