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

Rey*_*Rey 6 sql covering-index query-performance database-indexes postgresql-9.5

我有一个简单的计数查询,可以使用仅索引扫描,但在 PostgresQL 中仍然需要很长时间!

我有一个cars包含 2 列的表,type bigint并且active 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);
Run Code Online (Sandbox Code Playgroud)

我插入了一些有 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));
Run Code Online (Sandbox Code Playgroud)

让我们运行 VACUUM ANALYZE 并强制 PostgresQL 使用仅索引扫描

VACUUM ANALYSE;
SET enable_seqscan = OFF;
SET enable_bitmapscan = OFF;
Run Code Online (Sandbox Code Playgroud)

好的,我有一个关于type和的简单查询active

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

结果:

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);
Run Code Online (Sandbox Code Playgroud)

查看查询解释结果,

  • 它使用Index Only Scan,仅使用索引扫描,具体取决于visibilities map,PostgresQL 有时需要获取表堆以检查元组的可见性,但我已经运行了VACUUM ANALYZE所以您可以看到Heap fetch = 0,因此阅读索引足以回答此查询。

  • 索引的大小非常小,可以全部放入Buffer缓存(Buffers: shared hit=2806)中,PostgresQL不需要从磁盘中获取页面。

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

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 的新手,并试图将我的知识与真实案例联系起来。非常感谢,

Rey*_*Rey 0

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

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

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