Performance 为什么这个计数查询这么慢?
大家好,我在Heroku上运行postgresql 9.1.6,运行的是他们的Ika计划(7,5gb ram)。我有一张桌子叫汽车。我需要做以下工作:Performance 为什么这个计数查询这么慢?,performance,postgresql,Performance,Postgresql,大家好,我在Heroku上运行postgresql 9.1.6,运行的是他们的Ika计划(7,5gb ram)。我有一张桌子叫汽车。我需要做以下工作: SELECT COUNT(*) FROM "cars" WHERE "cars"."reference_id" = 'toyota_hilux' 现在这需要非常长的时间(64秒!!!) 一点背景: 该表包含大约320万行,而我试图依赖的列具有以下设置: reference_id character varying(50); 和索引: CREA
SELECT COUNT(*) FROM "cars" WHERE "cars"."reference_id" = 'toyota_hilux'
现在这需要非常长的时间(64秒!!!)
一点背景:
该表包含大约320万行,而我试图依赖的列具有以下设置:
reference_id character varying(50);
和索引:
CREATE INDEX index_cars_on_reference_id
ON cars
USING btree
(reference_id COLLATE pg_catalog."default" );
我做错了什么?我认为这种表现不是我应该期望的——或者我应该?这是不完全正确的。在存在匹配索引的情况下,如果表统计数据表明它将返回大约5%(取决于)的表,则规划器仅选择完整表扫描,因为这样扫描整个表会更快
正如您从自己的问题中看到的,您的查询并非如此。它使用位图索引扫描,然后是位图堆扫描。虽然我本以为会有一个简单的索引扫描。(?)
我注意到在解释输出中还有两件事:第一次扫描发现832行,而第二次扫描将计数减少到739行。这表明索引中有许多死元组 用
解释分析检查每个步骤后的执行时间,并可能将结果添加到您的问题中:
首先,使用EXPLAIN ANALYZE重新运行查询两到三次以填充缓存。最后一次与第一次相比结果如何
下一步:
重播
如果表上有很多写操作,我会将填充因子设置为小于100。比如:
ALTER TABLE cars SET (fillfactor=90);
如果行大小较大或有大量写入操作,则降低。然后:
VACUUM FULL ANALYZE cars;
这需要一段时间。重播
或者,如果您有能力这样做(并且其他重要查询没有相互矛盾的要求):
这将按照索引的物理顺序重写表,这将使此类查询更快
规范化模式
如果您需要快速完成此操作,请使用serial
主键创建一个表car\u type
,并从表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
列上的集群表上,增益不会太大
一个受欢迎的副作用:如果您必须重命名一个类型,那么现在只需对一行进行一次小小的更新
,根本不会弄乱大表。afaik,您应该期待这样,任何where子句都会强制执行完整的表扫描,而不考虑条件/索引HMMM,但我如何才能利用已经存在的引用id上的索引?顺便说一句,为什么在解释中使用它呢?可能会有帮助。看起来您在分散的数据页上经历了许多磁盘查找(位图堆扫描)的高延迟。您可以尝试第二次立即重新执行查询,以查看数据在缓存中时的差异。“解释分析”的“缓冲区”选项在这里也很有用。@MikeChristensen:wiki页面仅用于计算表中没有任何(where)条件的所有行。使用条件计数是完全不同的事情。如果将cat_类型表连接到子查询中而不是子查询中,则最终查询是否有任何差异?@Clodoaldo:如果您只想计算一种类型(如示例中所示),子查询应该更快。但没什么大不了的。这是一个很好的答案!我试试看。你认为@ErwinBrandstetter认为cars表实际上有170列对这个计数的性能有什么意义吗?我不太了解postgres的时间间隔,但我的猜测是,我不会期望它,因为我在这个查询中没有显式地涉及这些列…@NielsKristian 170 columns可能是一个规范化问题。打开另一个关于表结构的问题。@NielsKristian:Clodoaldo说的,另外:是的,非常大的行意味着一个数据页上只能容纳很少的行。因此,必须访问更多的数据页才能计数,这是影响性能的最重要因素。
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')