大表上的第一次查询调用速度非常慢
Posted
技术标签:
【中文标题】大表上的第一次查询调用速度非常慢【英文标题】:First call of query on big table is surprisingly slow 【发布时间】:2015-09-02 17:25:50 【问题描述】:我有一个查询,感觉它需要的时间比它应该的要多。这仅适用于给定参数集的第一次查询,因此缓存时没有问题。
我不确定会发生什么,但是,考虑到设置和设置,我希望有人可以阐明一些问题,并深入了解可以采取哪些措施来加快查询速度。有问题的表相当大,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
返回相同数量的行的原因是这个例子只使用了一个virtual_id
,但可以有更多。
表格和索引
正在查询的表,每 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 发帖之前要尝试的事情,更详细的结果如下:
-
摆弄 Postgres 设置,主要是在索引扫描后降低
random_page_cost
,虽然它似乎并不太特别,但它比 random_page_cost
更高时尝试执行的位图堆扫描领先几英里。
向索引和WHERE
条件所基于的virtual_id
和ts
列添加更多统计信息。更改后,查询规划器的估计行数更接近实际行数。
the idx_data_cbm_aggregation_15_min_virtual_id_ts
索引上的聚类似乎没有太大变化,我没有注意到。
手动运行 VACUUM
并没有太大变化,我已经在运行 autovacuum 所以这不足为奇。
在索引上运行 REINDEX
大大缩小了它(几乎 50%!),但速度并没有提高多少。
【问题讨论】:
数据是如何分布的?即,virtual_id
有多少不同的值,它们的行数是否大致相同?您通常是按整月查询,还是可以任意查询时间范围?
截至目前,表中有 15256 个不同的 virtual_id
值。与每个virtual_id
关联的总行数各不相同,但它们都以相同的速率获取数据,因此变化取决于它们的创建时间。有问题的查询将具有任意时间范围。
【参考方案1】:
几个小的改进
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 值.
可能不正确。要包括 2015 年 3 月的所有,请使用提供的替代方法。 ts between '2015-02-01 00:00:00' and '2015-03-31 23:59:59'
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。我附加的两个索引列(amp
、factor
)只有在您从中获得仅索引扫描时才有意义。
既然您显然对已删除的行不感兴趣,请将其设为部分索引。仅当表中有多个已删除行时才需要付费。 如果您还有其他大部分可以排除的表,请添加更多条件 - 并记住在查询中重复条件(即使它看起来是多余的),以便 Postgres 了解该索引是适用的。
表定义
像这样重新排序表列将每行节省 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
);
相关:
Configuring PostgreSQL for read performance最后的最重要信息
对于非常大的表,第一次查询调用的开销可能会大得多,因为无法缓存整个表。随后的调用将从填充的缓存中受益。 Postgres 缓存块,不一定是整个表。
还有一件对 first 调用很重要的事情。由于 Postgres 的 MVCC 模型,它必须维护可见性信息。当自上次写入操作以来第一次读取表的页面时,Postgres 机会性地更新可见性信息,这可能会为第一次访问带来一些额外的成本(并且对后续调用有很大帮助)。 More in the manual here。 dba.SE 上的相关答案:
Why does a SELECT statement dirty cache buffers in Postgres?关于你到目前为止所尝试的内容
SET STATISTICS 1000
for ts
和 virtual_id
是一个绝妙的主意,但是通过设置 random_page_cost = 1
基本上无效,这基本上是强制对该查询进行索引扫描。
random_page_cost = 1
告诉 Postgres 随机访问与顺序访问一样便宜。这对于(几乎)完全驻留在缓存中的数据库是有意义的。对于像您这样具有巨大表的数据库,此设置似乎太极端(即使它让 Postgres 支持所需的索引扫描)。将其设置为 random_page_cost = 1.1
或可能更高。
位图索引扫描通常是一个好的计划,用于您提供的查询的第一次调用 - 对于在表中随机分布的数据。由于您按照此查询的需要对表进行了聚类,因此索引扫描更有效。问题是:您的表会保持集群吗?
work_mem
和其他资源的设置取决于您拥有多少 RAM、磁盘速度、访问模式、您通常拥有多少并发连接、服务器上的其他程序竞争资源等。work_mem = 256MB
似乎太高了。对于呈现的查询,您几乎不需要那么多。将其设置得那么高实际上可能损害性能,因为它会减少可用于缓存的 RAM。
REINDEX
在CLUSTER
之后并不是多余的,因为无论如何都会重新创建所有索引。您必须在集群之前运行REINDEX
,否则您对表的写入权限很重,已经再次变得如此臃肿。
各种
升级到 Postgres 9.4(或即将推出的 9.5,目前是 alpha)。 9.2 版本到现在已经 3 年了,最新版本已经有了很多改进。
query plan 表明没有实际上是聚合的。 rows=4,117
从索引中读取,rows=4,117
保留在 GroupAggregate
之后。看起来 ts
上的行已经是唯一的了?然后你就可以完全去掉聚合,把它变成一个简单的SELECT
...
如果这只是一个误导性的EXPLAIN
输出,并且您通常输出的行数比读取的行数少得多,那么在ts
上具有索引的MATERIALIZED VIEW
将是另一种选择。尤其是结合 Postgres 9.4,它引入了REFRESH MATERIALIZED VIEW CONCURRENTLY
。
【讨论】:
一个了不起的答案。但它不也是一个文件系统缓存,可以使后续查询更快吗? @zerkms:是的,文件系统缓存和 Postgres 缓存在一起。 这些建议看起来很有帮助,我会尝试并报告结果,非常感谢!在virtual_id
上使用IN
运算符是存在的,因为可以有多个。但是就像您提到的那样,当我只有一个 virtual_id
时,可能值得使用 =
并跳过 group by
,因为它们在特定情况下什么都不做。以上是关于大表上的第一次查询调用速度非常慢的主要内容,如果未能解决你的问题,请参考以下文章