Подтвердить что ты не робот

Как я могу "думать лучше" при чтении плана запросов PostgreSQL? (Пример прилагается)

Я потратил более часа сегодня, озадачивая себя планом запросов, который я не мог понять. Запрос был UDPATE, и он просто не запускался AT ALL. Полностью зашел в тупик: pg_locks показал, что ничего не ждет. Теперь я не считаю себя лучшим или худшим человеком для чтения плана запросов, но я считаю, что это исключительно сложно. Мне интересно, как это читать? Существует ли методология, согласно которой Pg-асы следуют, чтобы точно определить ошибку?

Я планирую задать еще один вопрос о том, как обойти эту проблему, но прямо сейчас я говорю конкретно о о том, как читать эти типы планов. Пожалуйста, не указывайте на какой-либо общий учебник, если он специально не устраняет эту проблему, выделенную ниже плана запроса.

                                         QUERY PLAN                                         
--------------------------------------------------------------------------------------------
 Nested Loop Anti Join  (cost=47680.88..169413.12 rows=1 width=77)
   Join Filter: ((co.fkey_style = v.chrome_styleid) AND (co.name = o.name))
   ->  Nested Loop  (cost=5301.58..31738.10 rows=1 width=81)
         ->  Hash Join  (cost=5301.58..29722.32 rows=229 width=40)
               Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text))
               ->  Seq Scan on options io  (cost=0.00..20223.32 rows=23004 width=36)
                     Filter: (name IS NULL)
               ->  Hash  (cost=4547.33..4547.33 rows=36150 width=24)
                     ->  Seq Scan on vehicles iv  (cost=0.00..4547.33 rows=36150 width=24)
                           Filter: (date_sold IS NULL)
         ->  Index Scan using options_pkey on options co  (cost=0.00..8.79 rows=1 width=49)
               Index Cond: ((co.fkey_style = iv.chrome_styleid) AND (co.code = io.code))
   ->  Hash Join  (cost=42379.30..137424.09 rows=16729 width=26)
         Hash Cond: ((v.lot_id = o.lot_id) AND ((v.vin)::text = (o.vin)::text))
         ->  Seq Scan on vehicles v  (cost=0.00..4547.33 rows=65233 width=24)
         ->  Hash  (cost=20223.32..20223.32 rows=931332 width=44)
               ->  Seq Scan on options o  (cost=0.00..20223.32 rows=931332 width=44)
(17 rows)

Проблема с этим планом запроса - я считаю, что я понимаю - лучше всего сказать RhodiumToad (он определенно лучше в этом, поэтому я ставлю на его объяснение лучше) irc://irc.freenode.net/#postgresql:

ой, этот план потенциально опасен проблема с этим планом заключается в том, что он запускает чрезвычайно дорогое hashjoin для каждой строки проблема заключается в оценке строк = 1 от другого объединения и планировщик думает, что это нормально, чтобы поставить очень дорогостоящий запрос во внутреннем пути nestloop, где, по оценкам, внешний путь возвращает только одну строку. так как, очевидно, по оценке планировщика дорогостоящая часть будет выполняться только один раз но у этого есть очевидная тенденция действительно испортиться на практике проблема заключается в том, что планировщик считает свои собственные оценки в идеале планировщик должен знать разницу между "оценкой, чтобы возвратить 1 строку" и "невозможно вернуть более 1 строки", но это совсем не ясно, как включить это в существующий код

Далее он говорит:

это может повлиять на любое соединение, но обычно объединение с подзапросами является наиболее вероятным

Теперь, когда я прочитал этот план, первое, что я заметил, это Nested Loop Anti Join, это стоило 169,413 (я буду придерживаться верхних границ). Эта Anti-Join распадается на результат Nested Loop по цене 31,738 и результат Hash Join по цене 137,424. Теперь 137,424 намного больше, чем 31,738, поэтому я знал, что проблема связана с Hash Join. Затем переходим к EXPLAIN ANALYZE сегменту Hash Join вне запроса. Это выполнено за 7 секунд. Я убедился, что есть индексы (lot_id, vin) и (co.code, и v.code) - было. Я отключил seq_scan и hashjoin индивидуально и заметьте увеличение скорости менее 2 секунд. Не достаточно близко, чтобы объяснить, почему он не прогрессирует через час.

Но, после всего этого, я совершенно неправ! Да, это была медленная часть запроса, но из-за бит rows="1" (я предполагаю, что он был на Nested Loop Anti Join). Есть ли урок, который поможет мне идентифицировать эти типы проблем. Здесь ошибка (отсутствие способности) в планировщике неверно оценивает количество строк? Как я должен читать это, чтобы прийти к такому же выводу RhodiumToad did?

Это просто rows="1", который должен заставить меня понять это?

Я запускал VACUUM FULL ANALYZE для всех задействованных таблиц, и это Postgresql 8.4.

4b9b3361

Ответ 1

Прослеживание таких вопросов требует некоторого опыта в том, где все может пойти не так. Но чтобы найти проблемы в планах запросов, попробуйте выполнить проверку произведенного плана изнутри, проверьте, соответствует ли количество оценок строк здравым и стоимостным оценкам затраченное время. Btw. две оценки затрат не являются более низкими и верхними границами, во-первых, оценочная стоимость для создания первой строки выпуска, второе число - расчетная общая стоимость, см. пояснить документацию, имеется также документация планера. Это также помогает узнать, как работают различные методы доступа. В качестве отправной точки Wikipedia содержит информацию о вложенном цикле, hash и объединяет объединения.

В вашем примере вы должны начать с:

           ->  Seq Scan on options io  (cost=0.00..20223.32 rows=23004 width=36)
                 Filter: (name IS NULL)

Запустите EXPLAIN ANALYZE SELECT * FROM options WHERE name IS NULL; и посмотрите, соответствуют ли возвращенные строки оценке. Фактор 2 выключен, как правило, не проблема, вы пытаетесь определить разницу по порядку величины.

Затем см. EXPLAIN ANALYZE SELECT * FROM vehicles WHERE date_sold IS NULL; возвращает ожидаемое количество строк.

Затем перейдите на один уровень к хеш-соединению:

     ->  Hash Join  (cost=5301.58..29722.32 rows=229 width=40)
           Hash Cond: ((io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text))

Смотрите, если EXPLAIN ANALYZE SELECT * FROM vehicles AS iv INNER JOIN options io ON (io.lot_id = iv.lot_id) AND ((io.vin)::text = (iv.vin)::text) WHERE iv.date_sold IS NULL AND io.name IS NULL; приводит к 229 строкам.

На еще один уровень добавляется INNER JOIN options co ON (co.fkey_style = iv.chrome_styleid) AND (co.code = io.code), и ожидается, что он вернет только одну строку. Вероятно, проблема заключается в том, что если фактическое число строк составляет от 1 до 100, общая оценка затрат на перемещение внутреннего цикла содержащего вложенный цикл отключается в 100 раз.

Основная ошибка, которую делает планировщик, вероятно, заключается в том, что она ожидает, что два предиката для соединения в co не зависят друг от друга и умножают их селективность. Хотя на самом деле они могут быть сильно коррелированы, а селективность ближе к MIN (s1, s2), а не s1 * s2.

Ответ 2

Вы АНАЛИЗИЛИ таблицы? И что говорит pg_stats об этих таблицах? План запроса основан на статистике, они должны быть в порядке. И какую версию вы используете? 8,4?

Расходы можно рассчитать, используя статистику, количество переходов, количество строк и настройки в postgresql.conf для констант затрат Планировщика.

work_mem также задействован, он может быть слишком низким и заставить планировщика выполнить seqscan, чтобы убить производительность...