为啥即使使用仅索引扫描 PostgresQL 计数也如此缓慢

Posted

技术标签:

【中文标题】为啥即使使用仅索引扫描 PostgresQL 计数也如此缓慢【英文标题】:Why PostgresQL count is so slow even with Index Only Scan为什么即使使用仅索引扫描 PostgresQL 计数也如此缓慢 【发布时间】:2018-10-09 13:12:33 【问题描述】:

我有一个可以使用 Index Only Scan 的简单计数查询,但在 PostgresQL 中仍然需要很长时间!

我有一个 cars 表,其中有 2 列 type bigintactive boolean,我在这些列上也有一个多列索引

CREATE TABLE cars
(
id BIGSERIAL NOT NULL
    CONSTRAINT cars_pkey PRIMARY KEY ,
type BIGINT NOT NULL ,
name VARCHAR(500) NOT NULL ,
active            BOOLEAN DEFAULT TRUE NOT NULL,
created_at        TIMESTAMP(0) WITH TIME ZONE default NOW(),
updated_at        TIMESTAMP(0) WITH TIME ZONE default NOW(),
deleted_at        TIMESTAMP(0) WITH TIME ZONE
);
CREATE INDEX cars_type_active_index ON cars(type, active);

我插入了一些有 950k 记录的测试数据,type=1 有 600k 记录

INSERT INTO cars (type, name) (SELECT 1, 'car-name' FROM generate_series(1,600000));
INSERT INTO cars (type, name) (SELECT 2, 'car-name' FROM generate_series(1,200000));
INSERT INTO cars (type, name) (SELECT 3, 'car-name' FROM generate_series(1,100000));
INSERT INTO cars (type, name) (SELECT 4, 'car-name' FROM generate_series(1,50000));

让我们运行 VACUUM ANALYZE 并强制 PostgresQL 使用 Index Only Scan

VACUUM ANALYSE;
SET enable_seqscan = OFF;
SET enable_bitmapscan = OFF;

好的,我有一个关于typeactive 的简单查询

EXPLAIN (VERBOSE, BUFFERS, ANALYSE) 
SELECT count(*) 
FROM cars 
WHERE type = 1 AND active = true;

结果:

Aggregate  (cost=24805.70..24805.71 rows=1 width=0) (actual time=4460.915..4460.918 rows=1 loops=1)
Output: count(*)
Buffers: shared hit=2806
->  Index Only Scan using cars_type_active_index on public.cars (cost=0.42..23304.23 rows=600590 width=0) (actual time=0.051..2257.832 rows=600000 loops=1)
        Output: type, active
        Index Cond: ((cars.type = 1) AND (cars.active = true))
        Filter: cars.active
        Heap Fetches: 0
        Buffers: shared hit=2806
Planning time: 0.213 ms
Execution time: 4461.002 ms
(11 rows)

查看查询解释结果,

它使用了Index Only Scan,只扫描索引,取决于visibilities map,PostgresQL 有时需要获取表堆来检查元组的可见性,但我已经运行了VACUUM ANALYZE,所以你可以看到@ 987654334@,所以阅读索引就足以回答这个查询了。

索引的大小很小,可以全部放在Buffer缓存(Buffers: shared hit=2806)上,PostgresQL不需要从磁盘取页。

从那里,我无法理解为什么 PostgresQL 需要那么长时间(4.5s)来回答查询,1M 记录并不是很多记录,所有内容都已经缓存在内存中,并且索引上的数据是可见的,它不需要获取Heap。

x86_64-pc-linux-gnu 上的 PostgreSQL 9.5.10,由 gcc (Debian 4enter code here.9.2-10) 4.9.2 编译,64 位

我在 docker 17.09.1-ce、Macbook pro 2015 上对其进行了测试。

我还是 PostgresQL 的新手,并试图将我的知识与真实案例联系起来。 非常感谢,

【问题讨论】:

部分索引对于这个查询可能更有效:CREATE INDEX cars_type_active_index ON cars(type) where active; 是的,创建部分索引会有所帮助,但作用不大,因为几乎 99% 的表都处于活动状态。但是我更好奇为什么它的 Index Only Scan 这么慢,当一切都已经缓存在内存中并且数据都是新的时。 这里是带有部分索引的解释结果:Aggregate (cost=24099.88..24099.89 rows=1 width=0) (actual time=4565.551..4565.554 rows=1 loops=1) Output: count(*) Buffers: shared hit=7250 -> Index Scan using cars_type_on_active_index on public.cars (cost=0.42..22606.33 rows=597423 width=0) (actual time=0.025..2324.765 rows=600000 loops=1) Output: id, type, name, active, created_at, updated_at, deleted_at Index Cond: (cars.type = 1) Buffers: shared hit=7250 Planning time: 0.095 ms Execution time: 4565.599 ms 【参考方案1】:

看来我找到了原因,不是 PostgresQL 问题,而是因为在 docker 中运行。当我直接在我的mac中运行时,时间会在100ms左右,这已经足够快了。

我发现的另一件事是 PostgresQL 仍然使用 seq scan 而不是 index only scan 的原因(这就是为什么我必须在测试中禁用 seq_scan 和 bitmapscan):

表的大小与索引的大小相比并没有那么大,如果我向表中添加更多的列或列的长度更长,则表的大小越大,使用索引的机会就越大。 random_page_cost 默认值为 4,我的磁盘很快,所以我可以将它设置为 1-2,这将有助于 psql 的解释器更准确地估算成本。

【讨论】:

以上是关于为啥即使使用仅索引扫描 PostgresQL 计数也如此缓慢的主要内容,如果未能解决你的问题,请参考以下文章

为啥 PostgreSQL 对索引列执行顺序扫描?

Postgresql 仅索引扫描在 Group By 上无法正常工作

为啥我在 postgresql 中的视图不使用索引?

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

MongoDB:即使查询的所有字段都被索引,为啥我的扫描对象值很高?

小表上的仅索引扫描非常慢