优化Postgres时间戳查询范围

use*_*724 9 postgresql indexing query-optimization database-partitioning postgresql-performance

我有以下表和索引定义:

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),
  ...
)
Run Code Online (Sandbox Code Playgroud)

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

CREATE INDEX ticket_1_idx
  ON ticket
  USING btree
  (created );
Run Code Online (Sandbox Code Playgroud)

这是我的疑问

select * from ticket 
where created between '2012-12-19 00:00:00' and  '2012-12-20 00:00:00'
Run Code Online (Sandbox Code Playgroud)

这个工作正常,直到记录数量开始增长(约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"
Run Code Online (Sandbox Code Playgroud)

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

random_page_cost = 1.75 
effective_cache_size = 3 
Run Code Online (Sandbox Code Playgroud)

也创建了

create CLUSTER ticket USING ticket_1_idx;
Run Code Online (Sandbox Code Playgroud)

什么都行不通.我究竟做错了什么?为什么选择顺序扫描?索引应该使查询快速.有什么可以做的来优化它吗?

Erw*_*ter 21

CLUSTER

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

create CLUSTER ticket USING ticket_1_idx;

运行一次:

CLUSTER ticket USING ticket_1_idx;
Run Code Online (Sandbox Code Playgroud)

对于更大的结果集,这可以帮助很多.返回单行并非如此.
Postgres会记住用于后续调用的索引.如果您的表格不是只读的,则效果会随着时间的推移而恶化,您需要以一定的间隔重新运行:

CLUSTER ticket;
Run Code Online (Sandbox Code Playgroud)

可能只在易失性分区上.见下文.

但是,如果您有大量更新,CLUSTER(或VACUUM FULL)可能实际上不利于性能.适量的膨胀允许UPDATE在同一数据页面上放置新的行版本,并且避免了太频繁地在OS中物理扩展底层文件的需要.您可以使用经过精心调整FILLFACTOR以获得两全其美的效果:

pg_repack

CLUSTER对表进行独占锁定,这可能是多用户环境中的问题.引用手册:

当表正在聚集时,ACCESS EXCLUSIVE会在其上获取锁.这可以防止任何其他数据库操作(读取和写入)在表上运行,直到CLUSTER完成为止.

大胆强调我的.考虑替代方案pg_repack:

与在线工作不同CLUSTER,VACUUM FULL在处理过程中不对待处理的表进行独占锁定.pg_repack的启动效率很高,性能可与CLUSTER直接使用相媲美.

和:

pg_repack需要在重组结束时进行独占锁定.

版本1.3.1适用于:

PostgreSQL 8.3,8.4,9.0,9.1,9.2,9.3,9.4

版本1.4.2适用于:

PostgreSQL 9.1,9.2,9.3,9.4,9.5,9.6,10

询问

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

但是,关于正确性的一个词:该BETWEEN构造包括边界.您的查询将选择12月19日的所有内容,以及 12月20日00:00时的记录.这是一个非常不可能的要求.机会是,你真的想要:

SELECT *
FROM   ticket 
WHERE  created >= '2012-12-19 0:0'
AND    created <  '2012-12-20 0:0';
Run Code Online (Sandbox Code Playgroud)

性能

首先,你问:

为什么选择顺序扫描?

您的EXPLAIN输出清楚地显示了索引扫描,而不是顺序表扫描.必定存在某种误解.

如果你为了更好的表现而努力,你可能会改进.但是必要的背景信息不在问题中.可能的选择包括:

  • 您只能查询所需的列而不是*降低传输成本(以及可能的其他性能优势).

  • 您可以查看分区并将实际时间片放入单独的表中.根据需要向分区添加索引.

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

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

    CREATE在新月开始之前的新指数.您可以使用cron作业轻松自动执行任务.可选择DROP旧月的部分索引.

  • 另外保留总索引CLUSTER(不能对部分索引进行操作).如果旧记录永远不会更改,表分区将对此任务有很大帮助,因为您只需要重新集群较新的分区.然后,如果记录永远不会改变,你可能不需要CLUSTER.

如果你结合最后两个步骤,性能应该是很棒的.

性能基础知识

您可能遗漏了其中一个基础知识.所有通常的性能建议都适用: