2011-05-14 194 views
9

我正在设计一些系统来存储包含开始和结束时间的记录。例如:PostgreSQL匹配时间戳的开始时间和结束时间间隔

CREATE TABLE test (
    id bigserial PRIMARY KEY, 
    ts_start timestamp NOT NULL, 
    ts_end timestamp NOT NULL, 
    foo bar NOT NULL, 
    ... 
); 

现在我想对此运行查询以查找与某个时间戳重叠的所有行。这将导致一个where子句,如:

WHERE ts_start <= '2006-4-6 12:34:56' AND ts_end > '2006-4-6 12:34:56' 

我用大量生成的测试数据对此进行了测试,性能非常糟糕。我使用ts_start上的索引和ts_end上的另一个索引以及ts_start和ts_end上的多列索引对其进行了测试。最后一次给出了最好的结果,但它仍然远未达到最佳状态。

问题是,postgresql不知道ts_end保证大于ts_start的事实,所以它使用一个能够查找ts_end小于ts_start的行的计划。

有什么建议如何解决这个问题?

编辑: 对于有这个问题的人们,如果您可以等待一段时间,那么PostgreSQL 9.2有完美的解决方案:range types。 9.2在测试版现在最终版本将最有可能在2012年

回答

8

有“时间的Postgres”(google一下),但我不知道,如果它仍然保持了......我相信有包括这类的讨论搜索postgres,但我不记得它的最终状态。总之:采用箱和依据

例子:

CREATE TABLE segments(start INTEGER NOT NULL, stop INTEGER NOT NULL, range_box BOX NOT NULL); 
INSERT INTO segments SELECT n,n+1,BOX(POINT(n,-1),POINT(n+1,1)) FROM generate_series(1, 1000000) n; 
CREATE INDEX segments_box ON segments USING gist(range_box); 
CREATE INDEX segments_start ON segments(start); 
CREATE INDEX segments_stop ON segments(stop); 

EXPLAIN ANALYZE SELECT * FROM segments WHERE 300000 BETWEEN start AND stop; 
Index Scan using segments_start on segments (cost=0.00..12959.24 rows=209597 width=72) (actual time=91.990..91.990 rows=2 loops=1) 
    Index Cond: (300000 >= start) 
    Filter: (300000 <= stop) 
Total runtime: 92.023 ms 

EXPLAIN ANALYZE SELECT * FROM segments WHERE range_box && '(300000,0,300000,0)'::BOX; 
Bitmap Heap Scan on segments (cost=283.49..9740.27 rows=5000 width=72) (actual time=0.036..0.037 rows=2 loops=1) 
    Recheck Cond: (range_box && '(300000,0),(300000,0)'::box) 
    -> Bitmap Index Scan on segments_box (cost=0.00..282.24 rows=5000 width=0) (actual time=0.032..0.032 rows=2 loops=1) 
     Index Cond: (range_box && '(300000,0),(300000,0)'::box) 
Total runtime: 0.064 ms 

正如你所看到的主旨指数是可笑的快速这里(1500倍!笑) (你可以使用许多运营商如重叠,包含,含有等

http://www.postgresql.org/docs/8.2/static/functions-geometry.html

2

末你遇到相同的问题,因为有人试图指数线段,然后查询点是否在段。你不能单独为每个维度编制索引,而需要通过构建某种BSP结构来索引某些内容。

我不确定PG是否有任何内置数据类型来支持日期范围,但我确定如果您使用PostGIS将时间范围表示为2D空间中的点,然后将PG告知地理索引那么,您将从此查询中获得最佳性能。

也许有一个特定日期的相当于我的建议,建成PG的,但正如我说的,我不熟悉它。不过,我熟悉pg的几何索引功能,我认为您应该认真考虑它作为优化。

这里有一个简单的例子(虽然我敢肯定,这将是非常快的查询):

  1. 表示每个时间范围从原点(0,0)为点的矩形(从,至)。
  2. 打开地理索引。
  3. 给定一个时间周期P如果是的时间内通过检查点(P,P)是一个像ST_Contains函数的矩形内可以查询。此查询将为O(日志(范围数))。

说明:

   | 
       | 
       | 
       | 
     to  | 
    (timestamp) | 
       | 
       | 
       |_________________ (from,to) 
       |__    | 
       | |(p,p)   | 
       |__|______________|_______________________ 

           from (timestamp) 
+0

我刚建立了一个简单的测试表,开始和结束时间戳,都是随机的,所有结束>开始的随机数,并在我的笔记本电脑表中有1M行我得到的结果为计数(*)其中范围在30至300ms范围内高于上述范围。改变random_page_cost(降低它)有利于索引,并且获得更好的运行时间。这张桌子有多大? – 2011-05-14 10:35:07

+0

@Scott:目前我正在测试1900万行,并且它需要大约6秒(和高cpu负载)以及多列索引。我有另一个类似的用例,其中有一个额外的限制,允许针对类似大小的表和结果只需要一毫秒的更具针对性的查询。 – Eelke 2011-05-14 11:21:29

+0

你的解释分析对查询计划有什么看法?降低random_page_cost直到使用索引扫描有帮助吗? – 2011-05-14 20:51:02

0

问题是PostgreSQL并不知道ts_end是保证更大然后是事实ts_start所以它使用了一个计划,是能够找到行,其中ts_end越小则ts_start的。

在这样的情况下,你需要重新表达你的查询,从而把这件事告诉Postgres的。

这与在嵌套集合中查询lft/rgt时的操作非常相似:如果您使用lft/rgt以索引方式索引子树,子树有parent_lft < lftlft < rgtparent_lft < parent_rgt,则最佳查询将依赖于parent_lft < lftlft < parent_rgt(它在lft上查找小范围的索引)而不是parent_lft < lftrgt < parent_rgt(它从一个点向上查找lft上的索引)。

您在添加索引时处于类似的情况。除非你约束ts_start和ts_end中的任何一个或两个,否则你会看一大堆行。

现在我想运行这个查询来查找与某个时间戳重叠的所有行。这将导致在where子句中,如:

WHERE ts_start <= '2006-4-6 12:34:56' AND ts_end > '2006-4-6 12:34:56'

对于特定的查询,你可能要考虑的几何类型和使用GIST指数。

具体而言,如果您将ts_start和ts_end发布到午夜,则可以获得整数表示形式(例如自时代以来的天数)。然后将后者作为可索引类型存储并使用重叠条件进行查询。

作为一个便笺,有关于最近几个月在pg-hacker列表中添加某种时间戳段/事件类型的讨论,但我很悲哀地未能通过Google搜索找到相关参考。所以......在这里提到它,以防你比我幸运。

相关问题