1

为了简化案例,我们假设有以下 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' and val_c='2' and B.b_id=C.b_id) AND (val_b='3 ' and val_c='4' and 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 需要大量时间来执行查询。请注意,id、val_b 和 val_c 都有索引。

有没有办法优化查询?尝试了显式内部连接,但无助于提高性能。

提前致谢

更多信息:

  • postgres 版本 8.2.4
  • 只有一对标准在 77.621 毫秒内运行
  • 使用 2 对标准 - 151.588 毫秒
  • 有 3 对标准 - 49483.979 毫秒 <-- 性能火花疯狂

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

更新:

Vladimir Baranov 下面建议的两个单独的 INTERSECT 查询版本和 Clodoaldo Neto 使用 bool_or 聚合函数的具有子句的版本都表现得更好。谢谢 !

但是,问题仍然存在,为什么 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

4 回答 4

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优化器的查询时,它能够将其转换为等效于 using 的变体intersect,但如果有大量连接,它就不能再这样做了,只能按照查询流程在连接后进行连接。它甚至可能效率低下,最终在循环内循环内循环......,换句话说,复杂性从线性跳跃到二次或更糟。

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

顺便说一句,Postgres 的最新版本有WITH,它有效地创建了一个带有中间结果的临时表。它应该对您的情况有很大帮助,因为您的每个子查询都很简单,如果系统首先单独运行所有子查询,那么将结果组合在一起很容易。

于 2015-02-24T01:54:01.250 回答
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

于 2015-02-24T12:56:00.097 回答
0
  1. 升级到最新版本
  2. 为了清楚起见,使用JOIN语法
  3. 使用EXISTS(...)而不是IN(...)速度和舒适度
  4. PK/FK 和索引确实有帮助!

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'
        )
        ;
于 2015-02-24T12:22:41.880 回答
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

希望这能让你到达那里,

于 2015-02-23T22:43:40.730 回答