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

Posted

技术标签:

【中文标题】优化 Postgres 对时间戳范围的查询【英文标题】:Optimize Postgres query on timestamp range 【发布时间】:2012-12-09 12:08:37 【问题描述】:

我定义了下表和索引:

CREATE TABLE ticket (
  wid bigint NOT NULL DEFAULT nextval('tickets_id_seq'::regclass),
  eid bigint,
  created timestamp with time zone NOT NULL DEFAULT now(),
  status integer NOT NULL DEFAULT 0,
  argsxml text,
  moduleid character varying(255),
  source_id bigint,
  file_type_id bigint,
  file_name character varying(255),
  status_reason character varying(255),
  ...
)

我在created 时间戳上创建了一个索引,如下所示:

CREATE INDEX ticket_1_idx
  ON ticket
  USING btree
  (created );

这是我的查询:

select * from ticket 
where created between '2012-12-19 00:00:00' and  '2012-12-20 00:00:00'

在记录数量开始增长(大约 500 万条)之前一直运行良好,但现在需要很长时间才能返回。

解释分析揭示了这一点:

Index Scan using ticket_1_idx on ticket  (cost=0.00..10202.64 rows=52543 width=1297) (actual time=0.109..125.704 rows=53340 loops=1)
  Index Cond: ((created >= '2012-12-19 00:00:00+00'::timestamp with time zone) AND (created <= '2012-12-20 00:00:00+00'::timestamp with time zone))
Total runtime: 175.853 ms

到目前为止,我已经尝试过设置:

random_page_cost = 1.75 
effective_cache_size = 3 

同时创建:

create CLUSTER ticket USING ticket_1_idx;

没有任何作用。我究竟做错了什么?为什么选择顺序扫描?索引应该使查询快速。有什么可以优化的吗?

【问题讨论】:

它没有进行顺序扫描。它正在执行索引扫描。 另外它只需要 175 毫秒即可运行。如果需要很长时间,则 OP 可能有一个庞大的数据集,需要很长时间才能通过网络传输,而不是运行查询。\ 顺便说一句:effective_cache_size=3 可能有点太低了。 (但在这种情况下可能不会有害) 你为什么认为 175ms 是“年龄”? 不要使用select *,因为它会增加要传输到客户端的结果集大小。 【参考方案1】:

CLUSTER

如果您打算使用CLUSTER,则显示的语法无效。

create CLUSTER ticket USING ticket_1_idx;

运行一次:

CLUSTER ticket USING ticket_1_idx;

可以对更大的结果集有很大帮助。返回的单行或几行并没有那么多。 Postgres 记住用于后续调用的索引。如果您的表不是只读的,效果会随着时间的推移而恶化,您需要每隔一段时间重新运行:

CLUSTER ticket;

可能仅在易失性分区上。见下文。

然而,如果您有大量更新,CLUSTER(或VACUUM FULL)实际上可能对性能不利。适当的膨胀量允许UPDATE 将新的行版本放置在同一数据页上,并避免过于频繁地物理扩展底层物理文件的需要。您可以使用经过仔细调整的FILLFACTOR 来获得两全其美:

Fill factor for a sequential index that is PK

pg_repack / pg_squeeze

CLUSTER 对表进行排他锁,这在多用户环境中可能是个问题。 Quoting the manual:

当一个表被集群时,一个ACCESS EXCLUSIVE锁被获取 在上面。这可以防止任何其他数据库操作(读取和 写)从对表的操作直到CLUSTER完成。

我的大胆强调。考虑替代方案!

pg_repack:

CLUSTERVACUUM FULL 不同,它可以在线工作,无需持有 处理过程中对已处理表的排他锁。 pg_repack 是 启动效率高,性能堪比直接使用CLUSTER

和:

pg_repack 需要在重组结束时采取排他锁。

当前版本 1.4.7 适用于 PostgreSQL 9.4 - 14。

pg_squeeze 是一个较新的替代方案,声称:

实际上我们尝试替换 pg_repack 扩展名。

当前版本 1.4 适用于 Postgres 10 - 14。

查询

查询很简单,不会导致任何性能问题。

然而,关于正确性的一句话:BETWEEN 构造包括边界。您的查询从 12 月 20 日 00:00 时选择了 12 月 19 日的所有加上条记录。这是一个极不可能的要求。很有可能,你真的想要:

SELECT *
FROM   ticket 
WHERE  created >= '2012-12-19 0:0'
AND    created <  '2012-12-20 0:0';

性能

首先,你问:

为什么选择顺序扫描?

您的EXPLAIN 输出清楚地显示索引扫描,而不是顺序表扫描。一定是有什么误会。

您也许可以提高性能,但必要的背景信息不存在问题。可能的选项包括:

仅查询所需的列而不是 * 以降低传输成本(和其他性能优势)。

查看partitioning 并将实际时间片放入单独的表中。根据需要为分区添加索引。

如果分区不是一种选择,另一种相关但侵入性较小的技术是添加一个或多个partial indexes。 例如,如果您主要查询当前月份,则可以创建以下部分索引:

  CREATE INDEX ticket_created_idx ON ticket(created)
  WHERE created >= '2012-12-01 00:00:00'::timestamp;

CREATE 一个新的索引就在新的一个月开始之前之前。您可以使用 cron 作业轻松地自动执行任务。 (可选)DROP 部分索引,用于以后的几个月。

CLUSTER 保留total 索引(不能对部分索引进行操作)。如果旧记录永远不会更改,那么表分区将对这项任务有很大帮助,因为您只需要重新集群新的分区。 再说一次,如果记录从不改变,你可能不需要CLUSTER

性能基础

您可能缺少其中一项基础知识。所有常用的性能建议都适用:

https://wiki.postgresql.org/wiki/Slow_Query_Questions https://wiki.postgresql.org/wiki/Performance_Optimization

【讨论】:

以上是关于优化 Postgres 对时间戳范围的查询的主要内容,如果未能解决你的问题,请参考以下文章

范围 COUNT 查询基于 Hibernate 中纪元时间戳的 DATE

按时间戳分钟在 postgres 中查询

在 postgres 中,如何从事件日志类型表(具有时间戳)中获取特定时间范围内字段的总和(或汇总)

Postgres 时间戳

提取时间戳间隔最小的记录的优化函数

如何在 Postgres 中使用时间戳字段对日期进行分组?