2013-05-29 167 views
0

我在PostgreSQL 8.2的分叉版本上已经适应了MPP。我试图从两个较大的表格中计算出一系列最佳下界的最大下界。这里是前述方式表的例子:优化涉及一组时间最大下界的查询PostgreSQL

Table A 
|source_ip (inet type) |s_time (date type) | 
------------------------------------------------ 
|10.50.43.200   | 2013-02-21 01:47:08 | 
|10.50.43.200   | 2013-02-21 01:47:38 | 
|10.50.43.200   | 2013-02-21 01:47:41 | 
|10.50.43.200   | 2013-02-25 17:05:00 | 
|10.50.43.200   | 2013-02-25 17:05:03 | 
|10.50.43.200   | 2013-02-25 17:05:04 | 
|10.50.43.200   | 2013-02-25 17:05:34 | 
|10.50.43.200   | 2013-02-25 17:10:01 | 
|10.50.43.200   | 2013-02-25 17:12:52 | 

Table B 
|source_ip (inet type) |mac (macaddr type) |l_time (date type) | 
---------------------------------------------------------------------- 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 22:33:47 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 23:07:32 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-20 23:13:04 | 
|10.50.43.200   | 00:24:d7:99:e9:0c | 2013-02-21 00:02:56 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:04:56 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:04:59 | 
|10.50.43.200   | 00:24:d7:99:68:14 | 2013-02-25 17:26:15 | 

对于表A我想加入一个附加列,那就是“最大下界”为表B每个时间戳的每一行。也就是说,我想要一个包含表B中所有值中的最大时间的列,并且也小于或等于表A中的相应时间。我期待的输出将类似于以下内容:

OUTPUT 
------------------------------------------------------------ 
|10.50.43.200 |2013-02-21 01:47:38 |2013-02-21 00:02:56 | 
|10.50.43.200 |2013-02-21 01:47:41 |2013-02-21 00:02:56 | 
|10.50.43.200 |2013-02-25 17:05:00 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:03 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:04 |2013-02-25 17:04:59 | 
|10.50.43.200 |2013-02-25 17:05:34 |2013-02-25 17:04:59 | 

下面的查询是什么,我想出了,但我不知道,如果使用max()聚合函数是实现这一目标的最佳方式。

所以,我的问题是 - 我们可以在不使用max()的情况下重写下面的查询,以便在大型数据集(在100多万个范围内)更快?

SELECT a.source_ip, 
a.s_Time, max(b.l_Time) AS max_time 
    FROM table_a AS a 
    INNER JOIN 
    table_b AS b 
    ON (a.source_ip = b.source_ip AND a.s_time > b.l_time) 
    GROUP BY a.source_ip, b.sourcemac, a.s_time 
    ORDER BY a.s_time asc; 

这里是解释计划:

            QUERY PLAN 
------------------------------------------------------------------------------------------------------------------- 
Gather Motion 72:1 (slice1; segments: 72) (cost=1519175930.51..1519305453.91 rows=143915 width=48) 
    -> HashAggregate (cost=1519175930.51..1519305453.91 rows=143915 width=48) 
     Group By: a.source_ip, a.s_time 
     -> Hash Join (cost=991681.79..1169135585.55 rows=648222862 width=23) 
       Hash Cond: a.source_ip = b.source_ip 
       Join Filter: a.s_time > b.l_time 
       -> Append-only Columnar Scan on a (cost=0.00..1083707.12 rows=1439149 width=15) 
       -> Hash (cost=487360.24..487360.24 rows=560358 width=15) 
        -> Seq Scan on b (cost=0.00..487360.24 rows=560358 width=15) 
(9 rows) 

我知道我可以哈希source_ip -s来bigints更快的连接。我也认为可能值得尝试索引连接中使用的列,但我不确定最佳优化策略是什么,并且会喜欢来自StackOverflow社区优秀专家组的任何输入。我们也尝试了rank()窗口函数,但是它在我们正在使用的实现上存在问题,并且是我们测试过的这种类型的查询性能最差的函数,所以理想的策略将有望避免任何窗口函数。

编辑:添加一个索引上source_ipstart_yime为表A和重写使用LIMIT 1建议在后查询:

              QUERY PLAN 
------------------------------------------------------------------------------------------------------------------------------------- 
Gather Motion 72:1 (slice2; segments: 72) (cost=1624120.24..7442384075819.75 rows=145921 width=48) 
    -> HashAggregate (cost=1624120.24..7442384075819.75 rows=145921 width=48) 
     Group By: a.src, a.start_time 
     -> Append-only Columnar Scan on a (cost=0.00..1098806.16 rows=1459206 width=15) 
     SubPlan 1 
      -> Limit (cost=708374.49..708374.51 rows=1 width=15) 
       -> Limit (cost=708374.49..708374.49 rows=1 width=15) 
         -> Sort (cost=708374.49..708376.35 rows=11 width=15) 
          Sort Key (Limit): b.source_ip, b.start_time 
          -> Result (cost=708339.65..708347.10 rows=11 width=15) 
            Filter: $0 = b.source_ip AND $1 > b.start_time 
            -> Materialize for deadlock safety (cost=708339.65..708347.10 rows=11 width=15) 
             -> Broadcast Motion 72:72 (slice1; segments: 72) (cost=0.00..708338.90 rows=11 width=15) 
               -> Seq Scan on b (cost=0.00..708338.90 rows=11 width=15) 
+0

什么指数的定义? –

+0

请将包含索引和相关部分配置参数的表结构添加到问题中。 (看起来你已经将work_mem设置为一个非常大的值)BTW:pg-8.2是非常古老的。 – wildplasser

回答

1
SELECT a.src, 
    a.s_Time, 
    (SELECT b.l_time AS max_time 
     FROM table_b AS b WHERE a.source_ip = b.source_ip 
     AND a.s_time > b.l_time 
     ORDER BY b.source_ip DESC, b.l_time DESC /* index on (source_ip, l_time) */ 
     LIMIT 1) 
FROM table_a AS a 
ORDER BY a.start_time; 

我离开了GROUP BY,因为我没有看到a.src和我不确定a.s_timea.start_time是不同的列。

无论如何,这个想法是,PG是非常聪明的索引LIMIT 1查询(至少,最近的版本是;没有担保8.2)。非常新的版本可能足够聪明,可以将MAX转换为等效的LIMIT 1查询,如果需要的话,但我几乎可以肯定是在8.2之后。

+0

感谢您的建议,我尝试了你的重写并发布了解释计划输出,它仍然看起来像使用限制而不是max的查询计划器中的成本更糟糕。由于某种原因,查询计划程序看起来不像是使用索引,应该在两个表上还是仅在一个表上创建它? – user7980

+1

对于我的想法来说,table_b(l_time)必须有一个索引。这个想法是,而不是一种排序,PG会从索引中获取最大值。 PG的后续版本能够在索引字段上采用MAX时推断出这一点 - 但我相信,一直回到8.2。 –

1

发现的最大不使用MAX()LIMIT的标准方法是使用NOT EXISTS (a record with a larger value),如:

SELECT a.src, a.s_Time 
     , b.l_Time AS max_time 
    FROM table_a AS a 
    JOIN table_b AS b ON b.source_ip = a.source_ip 
        AND b.l_time < a.s_time 
    WHERE NOT EXISTS (
     SELECT * 
     FROM table_b nx 
     WHERE nx.b.source_ip = b.source_ip 
     AND nx.l_time < a.s_time 
     AND nx.l_time > b.l_time 
    );