对大 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 之前要尝试的事情,更详细的结果如下:
- 摆弄 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%!),但它并没有提高多少速度。
几个小改进
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。我附加的两个索引列(amp
、factor
)只有在您从中进行仅索引扫描时才有意义。
由于您显然对已删除的行不感兴趣,所以将其设为部分索引。仅当您在 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
用于 ts
和 virtual_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。
REINDEX
在 CLUSTER
之后并不是多余的,因为无论如何都会重新创建所有索引。在 集群之前,您必须有 运行 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
.
我有一个查询,感觉它花费的时间比应该的要多。这仅适用于给定参数集的第一个查询,因此缓存时没有问题。
我不确定会发生什么,但是,考虑到设置和设置,我希望有人可以阐明一些问题,并深入了解可以做什么来加快查询速度。有问题的 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 之前要尝试的事情,更详细的结果如下:
- 摆弄 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%!),但它并没有提高多少速度。
几个小改进
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 月的 all,请使用提供的替代项。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
)只有在您从中进行仅索引扫描时才有意义。由于您显然对已删除的行不感兴趣,所以将其设为部分索引。仅当您在 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
用于ts
和virtual_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。REINDEX
在CLUSTER
之后并不是多余的,因为无论如何都会重新创建所有索引。在 集群之前,您必须有 运行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 onts
would be another option. Especially in combination with Postgres 9.4, which introducesREFRESH MATERIALIZED VIEW CONCURRENTLY
.