2013-09-04 5 views
0

Часть запроса 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.id и tests.parent_id.

Тесты содержат 28876 строк (из них 1282 WHERE parent_id IS NOT NULL).

Часть запроса запрашивает 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 

EXPLAIN ANALYZE

(Примечание: это использует реальное имя таблицы, 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 
+1

Вы пытаетесь рекурсивно запросить иерархию в этой таблице? Если да, то для этого вам нужен CTE. –

+0

Я хотел бы попробовать попробовать Союз с условиями, разделенными на два запроса. Могу ли я спросить, в чем цель проверки соответствия родительских идентификаторов, просто для того, чтобы убедиться, что вы каждый раз проверяете каждую запись в тестах? – ChrisProsser

+1

@ChrisProsser Это только часть более крупного запроса, который выполняет дальнейшую фильтрацию на основе атрибутов объединенной таблицы 'c'. В отдельности я вижу, что результаты «DISTINCT» этого запроса ничем не отличаются от «SELECT id from tests». – doctororange

ответ

2

обновление: Похоже, ваш запрос в основном что-то вроде этого

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) 

старый: попытаться превратить это в этот запрос и посмотреть, если это будет быстрее:

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 

Как долго требуется выполнение одного из этих запросов?

+0

Это значительно быстрее (160 мс), но не поддается фильтрации, которую я хочу делать по результатам. См. Обновленный OP. Благодарю. – doctororange

+0

@doctororange трудно сказать, но попробуйте обновить –

+0

Мне нужно узнать о 'CTE' (новой концепции для меня), чтобы полностью понять, что здесь происходит, но ваш запрос дает желаемый результат ** и менее чем за 40 мс **. Спасибо, и правда! – doctororange

1

Я думаю запрос может быть уменьшен до:

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. Это только сообщает о различных значениях.

UPDATE: Я только что протестировал его (по синтетическим данным), и приведенный выше запрос возвращает точно такие же результаты, что и исходный минус дубликаты.

UPDATE2: добавлен брачный вес.

+0

Спасибо, но это не дает желаемого результата. Он производит то же, что и мой запрос, если LEFT JOIN, где был изменен на 'c.parent_id = t.id'. (т. е. он пропускает объединения, где 'c.parent_id = t.parent_id') – doctororange

+0

ИМО« недостающие »строки будут дублировать идентификаторы, так как запрос t уже дал конкретный идентификатор. (попробуйте добавить отдельный исходный запрос, чтобы получить тот же счет) – wildplasser

+0

Недостающие строки не дублируются. Я представил изменение вашего ответа, которое улавливает дополнительный случай, когда 'c.parent_id = t.parent_id', и эта версия вашего запроса дает правильный результат в ~ 350 мс. Заметное улучшение! Спасибо, искренне за ваш вклад. На этот раз большой зеленый галочка отправился на @RomanPekar для быстрого запроса. – doctororange

Смежные вопросы