PostgreSQL 范围查询索引

Posted

技术标签:

【中文标题】PostgreSQL 范围查询索引【英文标题】:PostgreSQL range query index 【发布时间】:2018-10-30 14:10:40 【问题描述】:

假设我在 PostgreSQL 中有一个简单的表:

CREATE TABLE bingest.some_data (
      report_date DATE NOT NULL,
      client_id UUID NOT NULL,
      value_1 INT, value_2 INT, value_3 INT,
      value_4 INT, value_5 INT, value_6 INT,
      value_7 INT, value_8 INT, value_9 INT,
      value_10 INT, value_11 INT, value_12 INT,
      value_13 INT, value_14 INT, value_15 INT,
      value_16 INT, value_17 INT, value_18 INT,
      value_19 INT,
PRIMARY KEY (report_date, client_id));

我想在下面的查询中使用索引

SELECT * FROM some_data WHERE report_date > '2018-10-30';

对于上述查询,EXPLAIN 命令给了我这个

Seq Scan on some_data  (cost=0.00..18.12 rows=217 width=96)
    Filter: (report_date > '2018-10-30'::date)
Planning time: 0.061 ms
Execution time: 0.019 ms

我指定上下界时使用索引

SELECT * FROM some_data WHERE report_date > '2018-10-30' AND < '2019-10-30'

对于这个 EXPLAIN 给出以下输出:

Bitmap Heap Scan on some_data  (cost=4.18..11.30 rows=3 width=96)
    Recheck Cond: ((report_date > '2018-10-30'::date) AND (report_date < '2019-10-30'::date))
        ->  Bitmap Index Scan on some_data_pkey  (cost=0.00..4.18 rows=3 width=0)
                Index Cond: ((report_date > '2018-10-30'::date) AND (report_date < '2019-10-30'::date))
Planning time: 0.072 ms
Execution time: 0.027 ms

我还没有任何数据,但我想为这个表准备正确的索引。

【问题讨论】:

表格包含多少行?您能否也为第一个查询添加explain (analyze, buffers) 输出? 目前还没有行,我正准备将数据批量加载到此表中,但希望首先建立正确的索引。如果表中有一些行,对第一个查询计划会有影响吗? 在第一个查询中使用索引可能不值得。查询的选择性需要相当低(比如表的 3-5% 或类似的东西),以便 postgres 选择使用索引。您可以尝试使用 set enable_seqscan = off; 暂时禁用顺序扫描;看看强制它使用索引时的结果是什么样的 如果没有行那么最好进行顺序扫描 如果表是空的,Postgres 使用哪个执行计划都没有关系。不要只用几行来测试性能。始终使用与您的生产环境相匹配的实际行数来检查性能。 【参考方案1】:

如果一个表完全为空,PostgreSQL 估计它包含 10 个页面(参见 src/backend/optimizer/util/plancat.c 中的 estimate_rel_size)。

这样如果表包含几行并且尚未自动清空,我们就不会被严重低估。

结果是空表的估计完全是假的,你的观察没有任何意义。

要测试索引是否可以使用,请将enable_seqscan 设置为关闭并解释查询。如果它仍然选择昂贵的顺序扫描,它这样做是因为它不能使用索引。

【讨论】:

【参考方案2】:

对于空表,任何关于性能调优的讨论都是理论上的。要真正确定任何事情,您需要将大量数据放入表中,并让这些数据与您可以合理提出的生产数据集一样真实。

也就是说,进入理论......

表扫描与索引

对于空表,最有效的执行计划通常是全表扫描。为什么?完整的启动成本/开销非常低。

索引的使用具有很高的开销(这意味着,无论找到什么,简单地搜索它的基线成本),但每行成本极低。全表扫描的开销极低,每行成本最高。话虽如此,有两条经验法则:

表越小,索引的用处就越少。 索引搜索的选择性越多,它就越有用。搜索索引以匹配表中 90% 的行效率非常低,而 RDBMS 通常足够聪明,不会这样做。

估计和统计

鉴于上述经验法则,RDBMS 要决定哪个计划最有效,它需要知道数据的一些细节,具体而言,表中的行数,以及查询的行数可能匹配。

我们得到了一个 catch-22,因为它需要查询数据以找出将返回多少数据,以便它可以选择最有效的查询计划。

所以这一切的工作方式是,RDBMS 之类的 Postgres 存储有关数据的统计信息以供查询计划器使用。这些统计数据在特定时间点是准确的,它们是估计值。示例统计:

    表的近似行数 对于索引,每个键的近似行数 列中特定值的频率

同样,请记住这些是估计值。对于 Postgres 来说,保证统计数据在任何时候都非常精确是非常昂贵的,但是我们不需要高精度来选择执行计划。 1 行表和 2 行表之间的区别是无关紧要的。但是 1 行表与 100 行表或 100 万行表的关系非常重要。

这里有一些不错的阅读:https://www.postgresql.org/docs/9.6/static/planner-stats.html

总结

尽管如此,Postgres 可能不确定您的表中有多少行,但知道它很小。因此,何时使用索引的阈值很高,因此您的第一个查询会进行表扫描。在第二次日期检查中,很可能在检查主键索引时,查询将匹配较少的行数,因此它适用于索引。

【讨论】:

以上是关于PostgreSQL 范围查询索引的主要内容,如果未能解决你的问题,请参考以下文章

在 postgres 中创建一个范围

postgres 空间索引

优化 Postgres 对时间戳范围的查询

如何优化在 postgresql 中查询这些数据?

索引扫描不适用于 postgres 中的 json 数据集

PostgreSQL索引分类及使用