为什么这个计数查询这么慢?

时间:2012-10-22 15:07:13

标签: performance postgresql

您好我在Heroku上运行postgresql 9.1.6,他们的Ika计划(7,5gb ram)。我有一张叫做汽车的桌子。我需要做以下事情:

SELECT COUNT(*) FROM "cars" WHERE "cars"."reference_id" = 'toyota_hilux'

现在需要花费大量时间(64秒!!!)

Aggregate  (cost=2849.52..2849.52 rows=1 width=0) (actual time=63388.390..63388.391 rows=1 loops=1)
  ->  Bitmap Heap Scan on cars  (cost=24.76..2848.78 rows=1464 width=0) (actual time=1169.581..63387.361 rows=739 loops=1)
        Recheck Cond: ((reference_id)::text = 'toyota_hilux'::text)
        ->  Bitmap Index Scan on index_cars_on_reference_id  (cost=0.00..24.69 rows=1464 width=0) (actual time=547.530..547.530 rows=832 loops=1)
              Index Cond: ((reference_id)::text = 'toyota_hilux'::text)
Total runtime: 64112.412 ms

一点背景:

该表包含大约3.2米的行,而我正在尝试依赖的列具有以下设置:

reference_id character varying(50);

和索引:

CREATE INDEX index_cars_on_reference_id
  ON cars
  USING btree
  (reference_id COLLATE pg_catalog."default" );

我做错了什么?我希望这种表现不是我所期望的 - 或者我应该这样做?

1 个答案:

答案 0 :(得分:5)

@Satya claims in his comment并不完全正确。在存在匹配索引的情况下,如果表统计信息意味着它将返回超过表的大约5%(取决于),则计划程序仅选择全表扫描,因为这样可以更快地扫描整个表。

从您自己的问题中可以看出,您的查询并非如此。它使用位图索引扫描,然后使用位图堆扫描。虽然我本来期望一个简单的索引扫描。 (?)

我在解释输出中还注意到两件事:
第一次扫描找到832行,而第二次扫描将计数减少到739.这表示你的索引中有许多死元组。

使用EXPLAIN ANALYZE检查每个步骤后的执行时间,并将结果添加到您的问题中:

首先,使用EXPLAIN ANALYZE重新运行查询两到三次以填充缓存。与第一次运行相比,上次运行的结果是什么?

下一步:

VACUUM ANALYZE cars;

重新运行。

如果你在表上有很多写操作,我会将填充因子设置为低于100.喜欢:

ALTER TABLE cars SET (fillfactor=90);

如果您的行大小很大,或者您有很多的写操作,则降低。然后:

VACUUM FULL ANALYZE cars;

这需要一段时间。重新运行。

,如果你能负担得起(其他重要的查询没有相反的要求):

CLUSTER cars USING index_cars_on_reference_id;

这会以索引的物理顺序重写表格,这会使这种查询更多更快。


规范化架构

如果您需要非常快,请使用car_type主键创建一个表serial,并从表cars中引用它。这会将必要的索引缩小到现在的一小部分。

在您尝试任何此操作之前,不用说你做了备份

CREATE temp TABLE car_type (
   car_type_id serial PRIMARY KEY
 , car_type text
 );

INSERT INTO car_type (car_type)
SELECT DISTINCT car_type_id FROM cars ORDER BY car_type_id;

ANALYZE car_type;

CREATE UNIQUE INDEX car_type_uni_idx ON car_type (car_type); -- unique types

ALTER TABLE cars RENAME COLUMN car_type_id TO car_type; -- rename old col
ALTER TABLE cars ADD COLUMN car_type_id int; -- add new int col

UPDATE cars c
SET car_type_id = ct.car_type_id
FROM car_type ct
WHERE ct.car_type = c.car_type;

ALTER TABLE cars DROP COLUMN car_type; -- drop old varchar col

CREATE INDEX cars_car_type_id_idx ON cars (car_type_id);    

ALTER TABLE cars 
ADD CONSTRAINT cars_car_type_id_fkey FOREIGN KEY (car_type_id )
REFERENCES car_type (car_type_id) ON UPDATE CASCADE; -- add fk

VACUUM FULL ANALYZE cars;

或者,如果你想全力以赴:

CLUSTER cars USING cars_car_type_id_idx;

您的查询现在看起来像这样:

SELECT count(*)
FROM   cars
WHERE  car_type_id = (SELECT car_type_id FROM car_type
                      WHERE car_type = 'toyota_hilux')

应该更快。主要是因为索引和表现在较小,但也因为integer处理比varchar处理更快。但是,varchar列上的聚簇表的收益并不显着。

一个受欢迎的副作用:如果你必须重命名一个类型,那么它现在只有一个小UPDATE到一行,根本不会弄乱大表。