CREATE TABLE product (
product_id SERIAL,
factory_key VARCHAR(60),
relevant BOOLEAN
)
Indexes:
"product_factory_key_key" btree (factory_key);
"product_factory_key_relevant_key" btree (factory_key, relevant) WHERE relevant = false;
"product_relevant_key" btree (relevant);
事实:
product
表这是问题查询:
SELECT * FROM product WHERE factory_key='some_product_key' AND relevant=false LIMIT 10;
解释分析:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.00..23.06 rows=10 width=188) (actual time=2709.654..32252.961 rows=10 loops=1)
-> Seq Scan on product (cost=0.00..7366785.34 rows=3194759 width=188) (actual time=2709.634..32252.904 rows=10 loops=1)
Filter: ((NOT relevant) AND ((product_key)::text = 'some_product_key'::text))
Rows Removed by Filter: 449486
Total runtime: 32253.150 ms
(5 rows)
问题:
这是有问题的,因为:
我相信规划师选择使用seq扫描,因为有很多行与这个工厂相匹配。 (约320万行与此工厂匹配或约3%)
但是,因为只有极少数行是不相关的。我正在寻找不相关的。 seq扫描结果非常昂贵。
我已经创建了一个复合索引product_factory_key_relevant_key
,但它没有利用索引。
修改
我试图强制postgres使用复合键:product_factory_key_relevant_key
SET enable_seqscan=off
虽然现在正在使用索引扫描。它实际上仍然比seqscan慢。 (所以我猜计划员在进行seq扫描时是正确的)
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=0.57..34.03 rows=10 width=188) (actual time=8.088..469974.692 rows=10 loops=1)
-> Index Scan using product_factory_key_relevant_key on product (cost=0.57..10689307.49 rows=3194776 width=188) (actual time=8.083..469974.655 rows=10 loops=1)
Index Cond: (relevant = false)
Filter: ((NOT relevant) AND ((product_key)::text = 'some_product_key'::text))
Rows Removed by Filter: 2205295
Total runtime: 469974.820 ms
(6 rows)
答案 0 :(得分:12)
你不能强迫PostgreSQL使用特定的索引,或完全阻止它进行seqscan。
但是,您可以通过将相关的enable_
参数设置为off
来告诉它避免执行某些扫描类型(如果可能)。它实际上只是用于调试的功能。
测试时,请尝试:
SET enable_seqscan = off;
如果Pg可以使用索引扫描(或其他东西),它将会。
您可能还想考虑:
SET random_page_cost = 1.1
即。告诉PostgreSQL,随机I / O只比顺序I / O略贵。这通常适用于具有SSD的系统,或者大多数DB都缓存在RAM中的情况。在这种情况下,更有可能选择一个索引。
当然,如果你的系统的随机I / O实际上更贵,那么使用索引可能会更慢。
你应该做的是遵循你已经给出的建议。按选择性顺序创建索引 - 如果relevant
不太常见,请使用它。您甚至可以更进一步创建部分索引:
CREATE INDEX idx_name_blah ON tbl_name_blah (factory_key) WHERE (NOT relevant);
此索引仅包含relevant = 'f'
的值。它只能用于规划器知道相关的查询是错误的查询。另一方面,它将是多更小,更快的索引。
您可能还有不准确的统计信息,导致PostgreSQL认为价值频率与您的桌子实际不同。 explain analyze
将有助于展示这一点。
如果统计数据刚刚过时,您也可以ANALYZE my_table
;如果是这样,增加autovacuum运行的频率,因为它没有跟上。
如果统计数据是最新的,但规划人员仍在进行基于统计数据的误估计,那么增加表格的统计目标(参见手册)并重新分析可能会有所帮助,如果它实际上是统计误估计问题
较旧的PostgreSQL版本在成本估算,查询优化,统计信息,查询执行方法以及其他所有内容方面往往不那么聪明。
如果您没有使用最新版本,请升级。
例如,9.2的仅索引扫描将允许您创建部分索引
(product_id, factory_key) WHERE (NOT relevant)
然后运行查询:
SELECT product_id, factory_key FROM my_table WHERE NOT relevant;
应该只读取索引,根本没有堆访问权。
答案 1 :(得分:0)
Imho,您的查询中的问题是$1
。我想这意味着你正在使用一个准备好的陈述,因为你已经阅读过某个地方,这是最好的做法。
这实际上很糟糕,因为PG无法提前知道您使用的标准的基数是高还是低。因此,它需要选择一个适合大多数情况的计划。如果某些值以这样的方式存在,使得btree索引的最左边部分无用,那么seq扫描就有很好的理由。
相反,如果您在没有先准备好的情况下运行查询,它将根据您传递的值进行规划,并为该特定值选择最佳计划 。因此,考虑运行相同的查询而不准备它,如果它开始使用您现有的索引,那么您就处于这种病态用例中。
如果没有,克雷格说什么......特别是部分指数。