PostgreSQL未按预期使用覆盖索引

wd2eg0qa  于 2022-12-03  发布在  PostgreSQL
关注(0)|答案(1)|浏览(119)

给定以下PostgreSQL 12.3服务器模式

create table records
(
    id                           serial                                           primary key,
    number                       varchar(20)                                      not null,
    owner_id                     integer                                          not null,
    state                        varchar(16)    default 'open'::character varying not null,
    created_at                   date,
    updated_at                   date,
    finished_at                  date
)

我正在执行使用存储状态和一些时间戳属性对记录进行分页的查询。

EXPLAIN (ANALYSE, BUFFERS)
SELECT "records".*
FROM "records"
WHERE "records"."trashed_at" IS NULL
  AND "records"."owner_id" = 11
  AND "records"."state" IN ('fresh', 'processing')
ORDER BY "records"."created_at" DESC, "records"."number" DESC
LIMIT 20 OFFSET 0;

=>

 Limit  (cost=1241.09..1447.85 rows=20 width=1326) (actual time=1266.202..26013.831 rows=6 loops=1)
   Output: ....
   Buffers: shared hit=84977 read=132675 dirtied=4
   ->  Index Scan using index_records_on_owner_id_and_created_at_and_number on public.records  (cost=0.56..254588.42 rows=24627 width=1326) (actual time=116.749..26013.765 rows=126 loops=1)
         Output: ......
         Index Cond: (records.owner_id = 14759)
         Filter: ((records.trashed_at IS NULL) AND ((records.state)::text = ANY ('{fresh,processing}'::text[])))
         Rows Removed by Filter: 228669
         Buffers: shared hit=84977 read=132675 dirtied=4
 Planning Time: 0.682 ms
 Execution Time: 26013.889 ms
(11 rows)

由于缓冲区读取,执行时间很慢。当它们都在缓存中时,时间减少到约300毫秒。从解释中,我们可以看到使用了索引index_records_on_owner_id_and_created_at_and_number。它的定义如下

create index index_records_on_owner_id_and_created_at_and_number
    on records (owner_id asc, created_at desc, number desc);

请注意,计划程序的估计值非常差(是的,VACUUM ANALYZE在解释之前执行)。
我希望创建下面的覆盖索引index_records_optimize_sort_on_created_at_and_number_in将有所帮助,并将导致索引扫描没有过滤器部分。然而,规划器只使用旧的计划,并没有受益于新的索引。

create index index_records_optimize_sort_on_created_at_and_number_in
    on records (owner_id asc, created_at desc, number desc)
    include (state)
    where (trashed_at IS NULL);

我认为这是覆盖索引最佳候选项,因为所有筛选/排序属性都包含在一个索引中
我可以创建另一个索引,这有助于这个特定的查询。但有一个ceavant。从用户界面,我允许选择不同的状态。所以索引适合单一的场景,但有多个状态组合。

create index index_records_optimize_sort_on_created_at_with_where
    on records (owner_id asc, created_at desc, number desc)
    where (trashed_at IS NULL AND records.state IN ('fresh', 'processing'));

我在文档中遗漏了什么吗?可以修改单个索引以便计划者使用它吗?我已经在文档/赛博技术博客上花了很多时间(谢谢!),但无法取得任何进展。

vsdwdz23

vsdwdz231#

只扫描索引并不聪明。通过使用*,您选择了trashed_at列,但该列在索引中不可用。现在,它可以基于WHERE子句限制合成trashed_at的返回值,但它还不够聪明。因此,它不愿意使用只扫描索引。这违背了“覆盖”索引的目的(也就是说,它不是 * 真正 * 的覆盖索引)。要么将trashed_at放入索引列列表中,要么枚举所有需要返回的列,而不将trashed_at放入该列表中。

相关问题