4
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);

事实:

  1. product我们的表中有大约 1 亿条记录
  2. 有少量工厂。例如,1 个工厂可能有 500 万种产品。
  3. 有数百万个工厂密钥
  4. 只有少数行与每​​个工厂无关。例如,一家工厂有 500 万种产品,有大约 100 种不相关的产品。
  5. 但是,有数百万行不相关的行。因为,最常见的情况是一个工厂密钥,5 行产品,并且可能有 2 行不相关。

这是问题查询:

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)

问题:

这是有问题的,因为:

  1. 我相信planner之所以选择使用seq scan,是因为匹配这个工厂的行太多了。(约 320 万行匹配该工厂或约 3%)

  2. 但是,因为只有极少数的行不相关。我正在寻找不相关的。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)
4

2 回答 2

15

覆盖成本参数

你永远不能强迫 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;

那应该只读取索引,根本没有堆访问。

于 2014-04-19T06:45:35.137 回答
1

恕我直言,您查询中的问题是$1. 我想这意味着您正在使用准备好的语句,因为您在某处读过它是最佳实践。

这实际上很糟糕,因为 PG 无法提前知道您使用的标准的基数是高还是低。因此,它需要选择一个适合大多数情况的计划。如果某些值到处都是,以至于 btree 索引的最左边部分是无用的,那么它将有很好的理由进行 seq 扫描。

相反,如果您在没有先准备的情况下运行查询,它将根据您传递的值进行计划,并为该特定值选择最佳计划。因此,请考虑在准备的情况下运行相同的查询,如果它开始使用您现有的索引,那么您就处于这种病态的用例中。

如果不是,克雷格所说的……尤其是部分索引。

于 2014-04-19T07:30:43.973 回答