为啥 Postgresql 使用过滤器而不是索引?

Posted

技术标签:

【中文标题】为啥 Postgresql 使用过滤器而不是索引?【英文标题】:Why is Postgresql using filter instead of index?为什么 Postgresql 使用过滤器而不是索引? 【发布时间】:2016-12-20 08:37:59 【问题描述】:

我的 postgresql ad_item 表包含以下列。

id | name | remaining | created_at

然后我的剩余指数小于或等于 300。

create index remaining_index on ad_item using btree(id) where remaining <= 300

但是当我在查询中使用explain analyze 时。

explain analyze select id from ad_item where remaining <= 300

它向我显示以下内容。

Seq Scan on ad_item  (cost=0.00..2.06 rows=1 width=4) (actual time=0.010..0.013 rows=1 loops=1)
  Filter: (remaining <= '300'::numeric)
  Rows Removed by Filter: 4
Planning time: 0.115 ms
Execution time: 0.026 ms

为什么不使用我的remaining_index?那个索引是多余的吗?

谢谢。

【问题讨论】:

因为你的表太小(只有5行),无法让索引的使用效率高。 您可以在查询之前执行set enable_seqscan=off 以确保它可以使用您的索引:) @a_horse_with_no_name,嗨,真棒。所以我相信当我获得更多行时,它将开始使用索引然后会更快?谢谢。 如果索引不是很有选择性(即如果大多数行符合您的remaining &lt;= '300' 条件)它可能根本不会被使用,即使您的表有大量行。 【参考方案1】:

当它开始使用你的索引时,你可以做一个非常 apx 的计算来了解它。

t=# drop table s07;
DROP TABLE
t=# create table s07 (i int, r int, t text);
CREATE TABLE
t=# insert into s07 select 1,1,'some text';
INSERT 0 1
t=# insert into s07 select 2,2,'some text';
INSERT 0 1
t=# insert into s07 select 3,3,'some text';
INSERT 0 1
t=# insert into s07 select 4,4,'some text';
INSERT 0 1
t=# create index s07i on s07 (i);
CREATE INDEX
t=# analyze s07;
ANALYZE
t=# SELECT relname, relkind, reltuples, relpages FROM pg_class WHERE relname LIKE 's07%';
 relname | relkind | reltuples | relpages
---------+---------+-----------+----------
 s07     | r       |         4 |        1
 s07i    | i       |         4 |        2
(2 rows)

尽管索引具有相同数量的行和更少的列,但在少量数据上它实际上需要两倍的空间!关系-1页,索引-2,所以planner进行seq扫描:

t=# explain analyze select i from s07 where i < 4;
                                         QUERY PLAN
---------------------------------------------------------------------------------------------
 Seq Scan on s07  (cost=0.00..1.05 rows=4 width=4) (actual time=0.003..0.004 rows=3 loops=1)
   Filter: (i < 4)
   Rows Removed by Filter: 1
 Planning time: 0.086 ms
 Execution time: 0.013 ms
(5 rows)

所以在填充了一些数据之后:

t=# insert into s07 select i,i,'some text' from generate_series(1,99,1) i;
INSERT 0 99
t=# analyze s07;
ANALYZE
t=# SELECT relname, relkind, reltuples, relpages FROM pg_class WHERE relname LIKE 's07%';
 relname | relkind | reltuples | relpages
---------+---------+-----------+----------
 s07     | r       |       103 |        1
 s07i    | i       |       103 |        2
(2 rows)

t=# explain analyze select i from s07 where i < 4;
                                         QUERY PLAN
---------------------------------------------------------------------------------------------
 Seq Scan on s07  (cost=0.00..2.29 rows=7 width=4) (actual time=0.008..0.016 rows=6 loops=1)
   Filter: (i < 4)
   Rows Removed by Filter: 97
 Planning time: 0.119 ms
 Execution time: 0.029 ms
(5 rows)

同一张照片。所以放更多的数据:

t=# insert into s07 select i,i,'some text' from generate_series(1,299,1) i;
INSERT 0 299
t=# analyze s07;
ANALYZE
t=# SELECT relname, relkind, reltuples, relpages FROM pg_class WHERE relname LIKE 's07%';
 relname | relkind | reltuples | relpages
---------+---------+-----------+----------
 s07     | r       |       402 |        3
 s07i    | i       |       402 |        2
(2 rows)

t=# explain analyze select i from s07 where i < 4;
                                                 QUERY PLAN
-------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on s07  (cost=4.22..7.33 rows=9 width=4) (actual time=0.005..0.007 rows=9 loops=1)
   Recheck Cond: (i < 4)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on s07i  (cost=0.00..4.21 rows=9 width=0) (actual time=0.002..0.002 rows=9 loops=1)
         Index Cond: (i < 4)
 Planning time: 0.099 ms
 Execution time: 0.017 ms
(7 rows)

使用条件索引,这样的计算当然会更复杂,但基本上在这里你可以看到,即使是 100 行,从一页过滤然后加载两页也更便宜。

现在你当然可以change this behavior with config,例如当我们有 100 行时,如果你运行:

t=# set cpu_tuple_cost to 1;
SET
t=# set cpu_index_tuple_cost to 0.000001;
SET
t=# explain analyze select i from s07 where i < 4;
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Index Only Scan using s07i on s07  (cost=0.14..15.16 rows=7 width=4) (actual time=0.012..0.014 rows=6 loops=1)
   Index Cond: (i < 4)
   Heap Fetches: 6
 Planning time: 0.058 ms
 Execution time: 0.028 ms
(5 rows)

虽然页数是用于 Seq 扫描的:

t=# SELECT relname, relkind, reltuples, relpages FROM pg_class WHERE relname LIKE 's07%';
 relname | relkind | reltuples | relpages
---------+---------+-----------+----------
 s07     | r       |       103 |        1
 s07i    | i       |       103 |        2
(2 rows)

当然你可以暂时用set enable_seqscan=off查看执行计划和时间

更新

docs 中甚至提到 100 行对于 indes 扫描来说太小了:

从 100 行中选择 1 行几乎不会(作为索引的候选),因为 100 行 可能适合单个磁盘页面,并且没有计划可以 依次获取 1 个磁盘页面。

【讨论】:

太棒了。感谢您的详细解释。

以上是关于为啥 Postgresql 使用过滤器而不是索引?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 PostgreSQL 数组索引没有被使用(Rails 4)?

为啥 PostgreSQL 选择这个索引?

为啥 PostgreSQL 不使用三元索引

为啥 PostgreSql 的 Hibernate 默认生成器是“SequenceGenerator”,而不是“IdentityGenerator”?

当返回类型是表名时,为啥 PostgreSQL 函数返回空列而不是没有行?

为啥 postgresql 不使用我的 group by 聚合索引?