嵌套的sql子查询的postgres性能问题

时间:2015-02-23 21:57:59

标签: sql postgresql database-performance in-subquery

为了简化这种情况,我们假设有以下3个表

A(a_id),B(b_id,val_b),C(a_id,b_id,val_c)

我需要找到所有具有B和C特定值对的a_id。例子找到所有有记录的a_id(val_b ='1'和val_c ='2'和B.b_id = C.b_id)AND(val_b ='3'和val_c ='4'和B.b_id = C.b_id)AND ...

select A.a_id
from A
where (A.a_id in 
        (select C.a_id 
         from B, C 
         where B.b_id=C.b_id and B.val_b='1' and C.val_c='2') and
       A.a_id in 
        (select C.a_id 
         from B, C 
         where B.b_id=C.b_id and B.val_b='3' and C.val_c='4') and
       A.a_id in 
        (select C.a_id 
         from B, C 
         where B.b_id=C.b_id and B.val_b='5' and C.val_c='6'));

我注意到,通过添加更多(val_b,val_c)附加对postgres需要大量时间来执行查询。要注意,ids,val_b和val_c都存在索引。

有没有办法优化查询?尝试明确的内部联接,但没有帮助改善性能。

提前致谢

更多信息:

  • postgres版本8.2.4
  • 只有一对标准在77.621ms
  • 中运行
  • 有2对标准 - 151.588 ms
  • 有3对标准 - 49483.979 ms< - performance sparks crazy

  • 请注意,单独的子查询本身在~62ms内运行。

更新

下面由Vladimir Baranov建议的单独的INTERSECT查询版本和具有使用Clodoaldo Neto的bool_or聚合函数的having子句的版本执行得更好。谢谢!

然而,问题仍然是为什么postgres 8.2具有这样的性能火花,原始查询以3对标准开始?

顺便说一句,我注意到弗拉基米尔·巴拉诺夫(Vladimir Baranov)第一个用干净连接重写查询的建议也引发了同样的火花。见下文:

SELECT A.a_id
FROM
    A
    INNER JOIN (SELECT C.a_id FROM B INNER JOIN C ON B.b_id=C.b_id WHERE B.val_b='1' and C.val_c='2') Set1 ON Set1.a_id = A.a_id
    INNER JOIN (SELECT C.a_id FROM B INNER JOIN C ON B.b_id=C.b_id WHERE B.val_b='3' and C.val_c='4') Set2 ON Set2.a_id = A.a_id
    INNER JOIN (SELECT C.a_id FROM B INNER JOIN C ON B.b_id=C.b_id WHERE B.val_b='5' and C.val_c='6') Set3 ON Set3.a_id = A.a_id
;

有3组查询运行速度非常快,但只要再添加3-4组,查询性能就会降低到~30-40秒。

4 个答案:

答案 0 :(得分:1)

看看以下内容是否运行得更快会很有趣:

SELECT A.a_id
FROM A
WHERE
    A.a_id IN
    (
        SELECT C.a_id
        FROM B INNER JOIN C ON B.b_id=C.b_id
        WHERE B.val_b='1' and C.val_c='2'

        INTERSECT

        SELECT C.a_id
        FROM B INNER JOIN C ON B.b_id=C.b_id
        WHERE B.val_b='3' and C.val_c='4'

        INTERSECT

        SELECT C.a_id
        FROM B INNER JOIN C ON B.b_id=C.b_id
        WHERE B.val_b='5' and C.val_c='6'
    )
;

实际上,这里不是多个IN,而是多个子集的显式交集。

我的原始答案的查询与该问题的原始查询不同。

以下SQL Fiddle包含一些示例数据和原始查询,以检查我的变体是否与原始查询产生相同的结果。

修改

另一条调查途径。如果每个子查询运行得很快,但在一个长查询中重复多次INTERSECT变得非常慢,那么您可以尝试使用子查询的结果填充临时表,然后使用此临时表主表A。实际上,使用显式临时表一次一个地手动实现INTERSECT。根据子查询返回的行数,向临时表添加索引可能是有益的。

<强>更新

至于你的问题,为什么Postgres性能在查询变得复杂时会降低...你的Postgres版本相当陈旧,并且不太可能有人对此进行详细调查。我只能提供一些通用的想法。最新版本最有可能表现不同,自8.2以来发生了很多变化。

在每个RDBMS中,查询优化器都有有限的资源和时间来分析查询,因此他们使用了大量的启发式方法。由于查询中的连接数增加了问题的复杂性以找到最佳执行计划呈指数级增长,因此必须有一个阈值,然后优化器放弃并选择他所获得的任何计划。

你应该能够观察它。检查快速查询的执行计划,添加另一个联接以使查询变慢并比较计划。最有可能的计划将是非常不同的。您应该能够确定优化程序在每种情况下选择的路径。

当给定一个joins优化器的查询时,可能会将其转换为等同于使用intersect的变体,但是如果使用大量连接,它就无法执行此操作任何更多,只是跟随查询流程在连接后进行连接。它甚至可能效率低下,它最终在循环内部循环中进行循环...,换句话说,复杂性从线性跳到二次或更差。

所以,实际上,这些性能问题的唯一答案是:检查执行计划

BTW,Postgres的最新版本有WITH,它可以有效地创建一个具有中间结果的临时表。它应该对你的情况有很大帮助,因为你的每个子查询都很简单,如果系统最初分别运行所有子查询,那么将结果组合在一起很容易。

答案 1 :(得分:1)

select a_id
from
    a
    inner join
    c using (a_id)
    inner join
    b using (b_id)
group by a_id
having
    bool_or((val_b, val_c) = (1,2)) and
    bool_or((val_b, val_c) = (3,4)) and
    bool_or((val_b, val_c) = (5,6))

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

答案 2 :(得分:0)

每个子查询都必须再次访问索引,这会使查询的开销增加几倍。如果我理解你的要求,这是Or运营商的案例:

select a.a_id
from A
  join c on a.a_id = c.a_id
  join b on b.b_id = c.b_id
where 
(
  (b.val_b = '1' and c.val_c = '2')
  or (b.val_b = '3' and c.val_c = '4')
  or (b.val_b = '5' and c.val_c = '6')
)

这将为您提供链接到C记录的所有A记录,其中c和b值是您提到的集合之一。希望这有帮助:)

编辑似乎是多对一的:

Select a.a_id
    , sum(case when b.val_b = '1' and c.val_c = '2' then 1 else 0 end) as Condition1
    , Sum(case when b.val_b = '3' and c.val_c = '4' then 1 else 0 end) as Condition2
    , Sum(case when b.val_b = '5' and c.val_c = '6' then 1 else 0 end) as Condition3
from A
  join c on a.a_id = c.a_id
  join b on b.b_id = c.b_id
group by a.a_id
having sum(case when b.val_b = '1' and c.val_c = '2' then 1 else 0 end) > 0
    and Sum(case when b.val_b = '3' and c.val_c = '4' then 1 else 0 end) > 0
    and Sum(case when b.val_b = '5' and c.val_c = '6' then 1 else 0 end) > 0

希望能让你到那里,

答案 3 :(得分:0)

  1. 升级到最新版本
  2. 为清晰起见,请使用JOIN语法
  3. 使用EXISTS(...)而不是IN(...)来提高速度和舒适度
  4. PK / FK和索引确实有帮助!

  5. SELECT A.a_id
    FROM A
    WHERE EXISTS (
            SELECT *
            FROM B
            JOIN C ON B.b_id = C.b_id AND B.val_b = '1' 
            WHERE C.a_id = A.a_id AND C.val_c = '2'
            )
    AND EXISTS (
            SELECT *
            FROM B
            JOIN C ON B.b_id = C.b_id AND B.val_b = '3' 
            WHERE C.a_id = A.a_id AND C.val_c = '4'
            )
    AND EXISTS (
            SELECT *
            FROM B
            JOIN C ON B.b_id = C.b_id AND B.val_b = '5' 
            WHERE C.a_id = A.a_id AND C.val_c = '6'
            )
            ;