Почему оптимизация не применяется, когда я делаю LEFT JOIN?

Я уже спрашивал, почему столбец не смещается вниз и оптимизация не применяется.
И получите подробное объяснение в список рассылки

Были проблемы, когда я использовал whole-row vars. Но здесь я их не использую.
Запрос работает медленно, когда я делаю LEFT JOIN или помещаю этот LEFT JOIN внутрь CTE.

Когда я запускаю запрос напрямую (или помещаю условие внутри), для запуска требуется всего 6 мс:

WITH ready AS (
SELECT
  agreement_id, sys_period, app_period,
  acc_i.ready             as acc_i_ready,
  acc_i.acc_period        as acc_i_period,
  acc_i.consumed_period   as acc_i_consumed_period,
  acc_u.ready             as acc_u_ready,
  acc_u.acc_period        as acc_u_period,
  acc_u.consumed_period   as acc_u_consumed_period
FROM "order_bt" o
LEFT JOIN acc_ready( 'Invoice', app_period(), o ) acc_i ON acc_i.ready
LEFT JOIN acc_ready( 'Usage',   app_period(), o ) acc_u ON acc_u.ready

WHERE o.sys_period @> sys_time()  AND  o.app_period && app_period()
 AND agreement_id = 1736
)
SELECT * 
FROM ready

Когда я просто повторяю условие снаружи, это занимает 45 мс

WITH ready AS (
SELECT
  agreement_id, sys_period, app_period,
  acc_i.ready             as acc_i_ready,
  acc_i.acc_period        as acc_i_period,
  acc_i.consumed_period   as acc_i_consumed_period,
  acc_u.ready             as acc_u_ready,
  acc_u.acc_period        as acc_u_period,
  acc_u.consumed_period   as acc_u_consumed_period
FROM "order_bt" o
LEFT JOIN acc_ready( 'Invoice', app_period(), o ) acc_i ON acc_i.ready
LEFT JOIN acc_ready( 'Usage',   app_period(), o ) acc_u ON acc_u.ready

WHERE o.sys_period @> sys_time()  AND  o.app_period && app_period()
 AND agreement_id = 1736
)
SELECT * 
FROM ready
-- NOTICE: I only repeat this!
WHERE sys_period @> sys_time()  AND  app_period && app_period()
 AND agreement_id = 1736

Когда я разделяю условие, это занимает 671 мс:

WITH ready AS (
SELECT
  agreement_id, sys_period, app_period,
  acc_i.ready             as acc_i_ready,
  acc_i.acc_period        as acc_i_period,
  acc_i.consumed_period   as acc_i_consumed_period,
  acc_u.ready             as acc_u_ready,
  acc_u.acc_period        as acc_u_period,
  acc_u.consumed_period   as acc_u_consumed_period
FROM "order_bt" o
LEFT JOIN acc_ready( 'Invoice', app_period(), o ) acc_i ON acc_i.ready
LEFT JOIN acc_ready( 'Usage',   app_period(), o ) acc_u ON acc_u.ready

WHERE o.sys_period @> sys_time()  AND  o.app_period && app_period()
)
SELECT * 
FROM ready
WHERE 
 agreement_id = 1736  -- Unfortunately this is not used for index scan =( Why???

Когда я перемещаю все условия наружу, это занимает уже 11084 мс:
(хммм, но я не нарушать встроенное условие, не так ли?)

WITH ready AS (
SELECT
  agreement_id, sys_period, app_period,
  acc_i.ready             as acc_i_ready,
  acc_i.acc_period        as acc_i_period,
  acc_i.consumed_period   as acc_i_consumed_period,
  acc_u.ready             as acc_u_ready,
  acc_u.acc_period        as acc_u_period,
  acc_u.consumed_period   as acc_u_consumed_period
FROM "order_bt" o
LEFT JOIN acc_ready( 'Invoice', app_period(), o ) acc_i ON acc_i.ready
LEFT JOIN acc_ready( 'Usage',   app_period(), o ) acc_u ON acc_u.ready
)
SELECT * 
FROM ready
WHERE sys_period @> sys_time()  AND  app_period && app_period()
 AND agreement_id = 1736

А когда я удаляю LEFT JOIN, это занимает всего 5 мс!

WITH ready AS (
SELECT
  agreement_id, sys_period, app_period
FROM "order_bt" o
)
SELECT * 
FROM ready
WHERE sys_period @> sys_time()  AND  app_period && app_period()
 AND agreement_id = 1736

Сравните: 6 мс VS 45 мс VS 671 мс VS 11084 мс VS 5 мс
То же самое на depesz: 6 мс VS 45 мс VS 671 мс VS 11084 мс VS 5 мс

Почему второй, третий и четвертый запросы не оптимизированы как первый/последний запросы?
Есть ли какие-то обходные пути для четвертого случая, чтобы он работал быстрее?
(например, выбрать дополнительные поля, которые описаны в списке рассылки ( смотри ссылки выше))

PS. Второй случай кажется мне нелогичным и не является ожидаемым поведением

Версия сервера 13.1.


*первый — когда условие находится только внутри CTE
*второй — когда это условие копируется за пределы CTE

UPD Дальнейшие расследования:

попробуйте I1

Здесь, когда функция volatile для первого и второго случаев, BitmapHeapScan применяется по двум индексам:
order_idx_agreement_id, order_id_sys_period_app_period_excl

9 мс VS 45 мс


попробуйте I2

После замены функции acc_ready на STABLE,
для первого запроса используется BitmapHeapScan.
для второго запроса используется IndexScan (по ошибке?).
Сравните 7 мс VS 11 мс

Здесь мы видим, что встраивание экономит нам 2 мс
и 34 мс (из-за отсутствия JIT-генерации).
Но все равно требуется дополнительно 4 мс (по ошибке?), когда я повторяю условие снаружи.

(Здесь я не ожидаю дополнительных 4 мс времени, потому что внутри CTE условие не изменилось, поэтому изменений во времени тоже быть не должно, не так ли?)


попробуйте I3

Наконец, когда я удаляю индекс order_id_sys_period_app_period_excl, снова используется BitmapHeapScan (вместо более медленного IndexScan).
Это самый быстрый запрос, который занимает всего
3 мс


Остаются открытыми вопросы:
Почему IndexScan использовалось в разделе I2, а BitmapHeapScan — в разделе I1?
Почему BitmapIndexScan не применяются друг за другом, как это было сделано в разделе I3? (самый быстрый случай)


person Eugen Konkov    schedule 22.05.2021    source источник


Ответы (1)


Благодаря RhodiumToad в IRC:

<сильный>1. Оптимизация не применяется, т.к. CTE содержит изменчивую функцию.
(было acc_ready)

<сильный>2. Для второго запроса требуется дополнительное время JIT-генерации.
Сравните 9 мс VS 45 мс

person Eugen Konkov    schedule 22.05.2021