【问题标题】:Why is this LEFT JOIN running slowly?为什么这个 LEFT JOIN 运行缓慢?
【发布时间】:2013-09-04 05:54:41
【问题描述】:

PostgreSQL 查询的LEFT JOIN 部分运行非常缓慢,我不知道为什么。

完整的查询:

SELECT t.id FROM tests t
LEFT JOIN tests c ON c.parent_id IN (t.id, t.parent_id)
INNER JOIN responses r ON (
    r.test_id IN (t.id, t.parent_id, c.id)
) WHERE r.user_id = 333

tests.idtests.parent_id 上有索引。

测试包含 28876 行(其中有 1282 行 WHERE parent_id IS NOT NULL)。

查询的 LEFT JOIN 部分生成 32098 行,大约需要 700 毫秒

SELECT t.id FROM tests t
LEFT JOIN tests c ON c.parent_id IN (t.id, t.parent_id)

查询的其余部分花费的时间可以忽略不计。

关于为什么它可能很慢的任何想法,或者实现相同目标的更好方法?

谢谢!


选择版本()

PostgreSQL 9.1.9 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit

解释分析

(注意:这里使用的是真实的表名usability_tests,我在前面的示例中将其简化为tests。)

Nested Loop  (cost=5.18..158692.45 rows=80 width=4) (actual time=107.873..5718.295 rows=103 loops=1)
  Join Filter: ((r.usability_test_id = t.id) OR (r.usability_test_id = t.parent_id) OR (r.usability_test_id = c.id))
  ->  Nested Loop Left Join  (cost=0.56..136015.63 rows=28876 width=12) (actual time=0.091..486.496 rows=32098 loops=1)
        Join Filter: ((c.parent_id = t.id) OR (c.parent_id = t.parent_id))
        ->  Seq Scan on usability_tests t  (cost=0.00..1455.76 rows=28876 width=8) (actual time=0.042..39.558 rows=28876 loops=1)
        ->  Bitmap Heap Scan on usability_tests c  (cost=0.56..4.60 rows=4 width=8) (actual time=0.010..0.011 rows=0 loops=28876)
              Recheck Cond: ((parent_id = t.id) OR (parent_id = t.parent_id))
              ->  BitmapOr  (cost=0.56..0.56 rows=4 width=0) (actual time=0.008..0.008 rows=0 loops=28876)
                    ->  Bitmap Index Scan on index_usability_tests_on_parent_id  (cost=0.00..0.28 rows=2 width=0) (actual time=0.003..0.003 rows=0 loops=28876)
                          Index Cond: (parent_id = t.id)
                    ->  Bitmap Index Scan on index_usability_tests_on_parent_id  (cost=0.00..0.28 rows=2 width=0) (actual time=0.001..0.001 rows=0 loops=28876)
                          Index Cond: (parent_id = t.parent_id)
  ->  Materialize  (cost=4.62..153.63 rows=39 width=4) (actual time=0.001..0.076 rows=70 loops=32098)
        ->  Bitmap Heap Scan on responses r  (cost=4.62..153.44 rows=39 width=4) (actual time=0.053..0.187 rows=70 loops=1)
              Recheck Cond: (user_id = 3649)
              ->  Bitmap Index Scan on index_responses_on_user_id  (cost=0.00..4.61 rows=39 width=0) (actual time=0.040..0.040 rows=70 loops=1)
                    Index Cond: (user_id = 3649)
Total runtime: 5718.592 ms

【问题讨论】:

  • 您是否尝试递归查询该表中的层次结构?如果是,那么您需要 CTE。
  • 我会考虑尝试将条件拆分为两个查询的联合。我能问一下检查父 ID 是否匹配的目的是什么,这是否只是为了确保您在测试中获得每条记录至少一次?
  • @ChrisProsser 这只是更大查询的一部分,它根据连接表c 的属性进行进一步过滤。单独来看,我可以看到此查询的DISTINCT 结果与SELECT id from tests 没有什么不同。
  • 就像旁注一样,当您过滤以选择表的一小部分时,索引通常有助于提高速度,但当您必须返回表的大部分时不要加快速度.
  • 如果询问查询性能,总是显示您的确切 PostgreSQL 版本 (SELECT version()) 和 explain analyze 结果。 总是.

标签: sql performance postgresql left-join


【解决方案1】:

更新:看起来您的查询基本上是这样的

with cte as (
    select r.test_id
    from responses as r
    where r.user_id = 333
    union all
    select c.parent_id
    from tests as c
        inner join responses as r on r.test_id = c.id
    where r.user_id = 333
)
select
    t.id
from tests as t
where
    t.id in (select c.test_id from cte as c) or
    t.parent_id in (select c.test_id from cte as c)

old:试着把它变成这个查询,看看会不会更快:

select t.id 
from tests t
    inner join tests c on c.parent_id = t.id

union all

select t.id 
from tests t
    inner join tests c oN c.parent_id = t.parent_id

执行其中一项查询需要多长时间?

【讨论】:

  • 这明显更快(160ms),但不适合我想要对结果进行的过滤。请参阅更新的 OP。谢谢。
  • 我必须了解CTE(对我来说是一个新概念)才能完全理解这里发生了什么,但是您的查询会产生所需的结果,并且在 40 毫秒内乙>。谢谢,赞!
【解决方案2】:

认为查询可以简化为:

SELECT t.id FROM tests t
WHERE EXISTS ( 
        SELECT * FROM responses r
        WHERE (r.test_id = t.id OR r.test_id = t.parent_id )
        AND r.user_id = 333
        )
OR EXISTS (
        SELECT * FROM responses r 
        JOIN tests c ON r.test_id = c.id
            -- Note: the ... OR sibling makes no sense to me
        WHERE (c.parent_id = t.id OR c.parent_id = t.parent_id)
        AND r.user_id = 333
        );

注意:问题中的查询可能t.id 生成重复值。这个只报告不同的值。

更新:我刚刚测试了它(在合成数据上),上面的查询返回的结果与原始的完全相同减去重复项

UPDATE2:添加了兄弟匹配。

【讨论】:

  • 谢谢,但这并不能完全产生预期的结果。如果 LEFT JOIN 更改为c.parent_id = t.id,它会产生与我的查询相同的结果。 (即它错过了c.parent_id = t.parent_id 的连接)
  • IMO “缺失”的行都将是重复的 id,因为 t 查询已经产生了特定的 id。 (尝试在原始查询中添加不同的值,以获得相同的计数)
  • 缺失的行不是重复的。我已经提交了对您的答案的编辑,该编辑捕获了c.parent_id = t.parent_id 的其他情况,并且您的此版本的查询在大约 350 毫秒内产生了正确的结果。明显改善!真诚感谢您的意见。这次绿色的大勾号去了@RomanPekar,以获得最快的查询。
猜你喜欢
  • 1970-01-01
  • 2012-12-23
  • 2015-03-03
  • 2019-10-09
  • 2021-05-03
  • 1970-01-01
  • 2010-12-18
  • 1970-01-01
  • 2012-10-25
相关资源
最近更新 更多