优化Postgres时间戳查询范围

时间:2012-12-21 22:43:40

标签: 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),
  ...
)

我在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;

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

1 个答案:

答案 0 :(得分:19)

CLUSTER

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

<击> create CLUSTER ticket USING ticket_1_idx;

运行一次:

CLUSTER ticket USING ticket_1_idx;

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

CLUSTER ticket;

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

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

pg_repack

CLUSTER对表进行独占锁定,这可能是多用户环境中的问题。 Quoting the manual:

  

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

大胆强调我的。考虑alternative pg_repack

  

CLUSTERVACUUM 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月20日00:00开始选择所有12月19日的 plus 记录。这是极不可能的要求。机会是,你真的想要:

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(不能对部分索引进行操作)的总索引。如果旧记录永远不会更改,表分区将对此任务有很大帮助,因为您只需要重新集群较新的分区。 然后,如果记录永远不会改变,您可能不需要CLUSTER

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

性能基础

您可能遗漏了其中一个基础知识。所有通常的表现建议均适用: