对大 table 的第一次查询调用出奇地慢

First call of query on big table is surprisingly slow

我有一个查询,感觉它花费的时间比应该的要多。这仅适用于给定参数集的第一个查询,因此缓存时没有问题。

我不确定会发生什么,但是,考虑到设置和设置,我希望有人可以阐明一些问题,并深入了解可以做什么来加快查询速度。有问题的 table 相当大,Postgres 估计其中大约有 155963000 (14 GB)。

查询

    select ts, sum(amp) as total_amp, sum(230 * factor) as wh
    from data_cbm_aggregation_15_min
    where virtual_id in (1818) and ts between '2015-02-01 00:00:00' and '2015-03-31 23:59:59'
    and deleted is null
    group by ts
    order by ts

当我开始研究这个查询时,它花了大约 15 秒,经过一些更改后,我已经把它缩短到大约 10 秒,这对于像这样的简单查询来说仍然很长。以下是 explain analyze 的结果:http://explain.depesz.com/s/97V1。请注意 GroupAggregate returns 行数相同的原因是此示例仅使用了一个 virtual_id,但可以有更多行。

Table 和索引

Table 被查询,它每 15 分钟插入一次值

CREATE TABLE data_cbm_aggregation_15_min (
  virtual_id integer NOT NULL,
  ts timestamp without time zone NOT NULL,
  amp real,
  recs smallint,
  min_amp real,
  max_amp real,
  deleted boolean,
  factor real DEFAULT 0.25,
  min_amp_ts timestamp without time zone,
  max_amp_ts timestamp without time zone
)

ALTER TABLE data_cbm_aggregation_15_min ALTER COLUMN virtual_id SET STATISTICS 1000;
ALTER TABLE data_cbm_aggregation_15_min ALTER COLUMN ts SET STATISTICS 1000;

查询中使用的索引

CREATE UNIQUE INDEX idx_data_cbm_aggregation_15_min_virtual_id_ts
ON data_cbm_aggregation_15_min USING btree (virtual_id, ts DESC);

ALTER TABLE data_cbm_aggregation_15_min
CLUSTER ON idx_data_cbm_aggregation_15_min_virtual_id_ts;

Postgres 设置

其他设置默认。

default_statistics_target = 100 
maintenance_work_mem = 2GB 
effective_cache_size = 11GB
work_mem = 256MB
shared_buffers = 3840MB
random_page_cost = 1

我试过的

我一直在关注 https://wiki.postgresql.org/wiki/Slow_Query_Questions 中 post 之前要尝试的事情,更详细的结果如下:

  1. 摆弄 Postgres 设置,自索引扫描以来主要降低了 random_page_cost,虽然它看起来不是太特别,但它比位图堆扫描遥遥领先它尝试在 random_page_cost 更高时进行.
  2. 向索引和WHERE 条件所基于的virtual_idts 列添加增加的统计信息。查询规划器的估计行数在更改后更接近实际行数。
  3. the idx_data_cbm_aggregation_15_min_virtual_id_ts 索引上的集群似乎没有太大变化,我没有注意到。
  4. 运行 VACUUM 手动没有太大变化,我已经 运行 autovacuum 所以这并不奇怪。
  5. 运行 REINDEX 上的索引大大缩小了它(几乎缩小了 50%!),但它并没有提高多少速度。

几个小改进

SELECT ts, sum(amp) AS total_amp, sum(factor) * 230  AS wh
FROM   data_cbm_aggregation_15_min
WHERE  virtual_id = 1818
AND    ts >= '2015-02-01 00:00'
AND    ts <  '2015-04-01 00:00'
AND    deleted IS NULL
GROUP  BY ts
ORDER  BY ts;
  • sum(230 * factor) - 将总和 乘以一次 比乘以每个元素更便宜:sum(factor) * 230 结果是一样的,即使是 NULL 值。

  • ts between '2015-02-01 00:00:00' and '2015-03-31 23:59:59' 可能 不正确 。要包含 2015 年 3 月的 all,请使用提供的替代项。 BETWEEN 无论如何都会被翻译成 ts >= lower AND ts <= upper。拼写起来总是稍微快一些。

  • virtual_id in (1818) 只是 virtual_id = 1818.

  • 的一种不必要的复杂方式

更好的指数,可能更大的改进

CREATE INDEX data_cbm_aggregation_15_min_special_idx
ON data_cbm_aggregation_15_min (virtual_id, ts, amp, factor)
WHERE deleted IS NULL;
  • 我在您的问题中看不到任何会在您的原始索引中暗示 DESC 的内容。虽然 Index Scan Backward 几乎和普通的 Index Scan 一样快,但最好还是去掉修饰符。

  • 最重要的是,自 Postgres 9.2 以来有 index-only scans。我附加的两个索引列(ampfactor)只有在您从中进行仅索引扫描时才有意义。

  • 由于您显然对已删除的行不感兴趣,所以将其设为部分索引。仅当您在 table.
    中有多个已删除的行时才付费 如果您还有 table 的其他大部分可以排除,请添加更多条件 - 并记住在查询中重复该条件(即使它看起来多余)以便 Postgres 理解该索引适用。

Table定义

像这样重新排序 table 列将每行节省 8 个字节:

CREATE TABLE data_cbm_aggregation_15_min (
   virtual_id integer NOT NULL,
   recs smallint,
   deleted boolean,
   ts timestamp NOT NULL,
   amp real,
   min_amp real,
   max_amp real,
   factor real DEFAULT 0.25,
   min_amp_ts timestamp,
   max_amp_ts timestamp
);

相关:

最后的最重要信息

  • 对于非常大的 table,第一次查询调用的开销可能会大得多,因为无法缓存整个 table。后续调用将从填充的缓存中获益。 Postgres 缓存块,不一定是整个 tables.

  • 还有一点对 第一次 调用很重要。由于 Postgres 的 MVCC 模型,它必须维护可见性信息。自上次写入操作后第一次读取 table 的页面时,Postgres 会机会性地更新可见性信息,这可能会给第一次访问带来一些额外的成本(并且对后续调用有很大帮助)。 More in the manual here。 dba.SE 上的相关回答:

关于您目前的尝试

  • SET STATISTICS 1000 用于 tsvirtual_id 是一个绝妙的主意,但设置 random_page_cost = 1 后效果在很大程度上被抵消了,这基本上强制以任何一种方式对该查询进行索引扫描。

  • random_page_cost = 1 告诉 Postgres 随机访问和顺序访问一样便宜。这对于(几乎)完全驻留在缓存中的数据库来说是有意义的。对于像您这样具有巨大 table 的数据库,此设置似乎 过于极端 (即使它让 Postgres 支持所需的索引扫描)。将其设置为 random_page_cost = 1.1 或可能更高。

  • 位图索引扫描通常是 良好的 计划,用于首次调用您提供的查询 - 对于随机分布在 table 中的数据.由于您根据此查询的需要对 table 进行了聚类,因此索引扫描效率更高。问题是:你的 table 会保持 集群吗?

  • 您对 work_mem 和其他资源的设置取决于您拥有多少 RAM、磁盘速度、访问模式、通常有多少并发连接以及其他程序服务器上争资源等work_mem = 256MB好像太高了。您几乎不需要提供的查询那么多。将其设置得过高实际上可能 损害 性能,因为它会减少可用于缓存的 RAM。

  • REINDEXCLUSTER 之后并不是多余的,因为无论如何都会重新创建所有索引。在 集群之前,您必须有 运行 REINDEX ,否则您在 table 上有大量的写访问权限已经再次变得如此膨胀。

各种

  • 升级到 Postgres 9.4(或即将推出的 9.5,目前为 alpha)。 9.2版本已经3岁了,最新版本有了很多改进。

  • query plan表明没有实际上是聚合rows=4,117 从索引中读取,rows=4,117 保留在 GroupAggregate 之后。看起来 ts 上的行已经是唯一的了?然后你可以完全删除聚合并使其成为简单的 SELECT ...

  • 如果这只是一个误导性的 EXPLAIN 输出,并且您通常输出的行数比读取的行数少得多,那么 MATERIALIZED VIEW with index on ts would be another option. Especially in combination with Postgres 9.4, which introduces REFRESH MATERIALIZED VIEW CONCURRENTLY.