优化 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
来获得两全其美:
pg_repack
/ pg_squeeze
CLUSTER
对表进行排他锁,这在多用户环境中可能是个问题。 Quoting the manual:
当一个表被集群时,一个
ACCESS EXCLUSIVE
锁被获取 在上面。这可以防止任何其他数据库操作(读取和 写)从对表的操作直到CLUSTER
完成。
我的大胆强调。考虑替代方案!
pg_repack
:
与
CLUSTER
和VACUUM 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