Как оптимизировать SQL-запрос с помощью DISTINCT ON и JOIN многих значений? - PullRequest
0 голосов
/ 01 июля 2019

У меня есть такой запрос, где объединяются ~ 6000 значений

SELECT DISTINCT ON(user_id)
                user_id,
                finished_at as last_deposit_date,
                CASE When currency = 'RUB' Then amount_cents  END as last_deposit_amount_cents
            FROM    payments
            JOIN (VALUES (5),(22),(26)) --~6000 values
            AS v(user_id) USING (user_id)
            WHERE action = 'deposit' 
                AND success = 't'
                AND currency IN ('RUB')
            ORDER BY user_id, finished_at DESC

ПЛАН ЗАПРОСА для запроса со многими ЗНАЧЕНИЯМИ:

Unique  (cost=444606.97..449760.44 rows=19276 width=24) (actual time=6129.403..6418.317 rows=5991 loops=1)
  Buffers: shared hit=2386527, temp read=7807 written=7808
  ->  Sort  (cost=444606.97..447183.71 rows=1030695 width=24) (actual time=6129.401..6295.457 rows=1877039 loops=1)
        Sort Key: payments.user_id, payments.finished_at DESC
        Sort Method: external merge  Disk: 62456kB
        Buffers: shared hit=2386527, temp read=7807 written=7808
        ->  Nested Loop  (cost=0.43..341665.35 rows=1030695 width=24) (actual time=0.612..5085.376 rows=1877039 loops=1)
              Buffers: shared hit=2386521
              ->  Values Scan on "*VALUES*"  (cost=0.00..75.00 rows=6000 width=4) (actual time=0.002..4.507 rows=6000 loops=1)
              ->  Index Scan using index_payments_on_user_id on payments  (cost=0.43..54.78 rows=172 width=28) (actual time=0.010..0.793 rows=313 loops=6000)
                    Index Cond: (user_id = "*VALUES*".column1)
                    Filter: (success AND ((action)::text = 'deposit'::text) AND ((currency)::text = 'RUB'::text))
                    Rows Removed by Filter: 85
                    Buffers: shared hit=2386521
Planning time: 5.886 ms
Execution time: 6429.685 ms

Я использую PosgreSQL 10.8.0.Есть ли шанс ускорить этот запрос?

Я пытался заменить DISTINCT на рекурсию:

WITH RECURSIVE t AS (
 (SELECT min(user_id) AS user_id FROM payments)
 UNION ALL
 SELECT (SELECT min(user_id) FROM payments  
 WHERE user_id > t.user_id      
 ) AS user_id FROM
t   
  WHERE t.user_id IS NOT NULL
 )
SELECT payments.* FROM t
JOIN (VALUES (5),(22),(26)) --~6000 VALUES
AS v(user_id) USING (user_id)
, LATERAL (
 SELECT user_id,
        finished_at as last_deposit_date,
        CASE When currency = 'RUB' Then amount_cents  END as last_deposit_amount_cents FROM payments            
        WHERE payments.user_id=t.user_id
            AND action = 'deposit' 
        AND success = 't'
        AND currency IN ('RUB')     
        ORDER BY finished_at DESC LIMIT 1
) AS payments

WHERE t.user_id IS NOT NULL;

Но оказалось, что даже медленнее .

Hash Join (стоимость = 418.67..21807.22 строк = 3000 width = 24) (фактическое время = 16.804..10843.174 строк = 5991 циклов = 1) Hash Cond: (t.user_id = " VALUES". column1) Буферы: общее попадание = 6396763 CTE t -> Рекурсивный союз (стоимость = 0.46..53.73 строк = 101 ширина = 8) (фактическое время = 0.142..1942.351 строк = 237029 циклов = 1) Буферы: общее попадание= 864281 -> Результат (стоимость = 0.46..0.47 строк = 1 ширина = 8) (фактическое время = 0.141..0.142 строк = 1 петля = 1) Буферы: общий доступ = 4 InitPlan 3 (возвращает $ 1) -> Лимит (стоимость = 0.43..0.46 строк = 1 ширина = 8) (фактическое время = 0.138..0.139 строк = 1 петля = 1) Буферы: общее попадание = 4 -> Сканирование только по индексу с использованием index_payments_on_user_id для платежей платежей_2 (стоимость = 0,43 ..155102,74 строк = 4858092 ширины = 8) (фактическое время = 0,137..0.138 строк = 1 циклов = 1) Индекс Cond: (user_id IS NOT NULL) Выборки из кучи: 0 Буферы: общий доступ = 4 -> Сканирование WorkTable для t t_1 (стоимость = 0,00..5.12 строк = 10 по ширине = 8) (фактическое время = 0,008..0.008 строк = 1 циклов = 237029) Фильтр: (user_id IS NOT NULL) Строк, удаленных фильтром: 0 Буферы: общий доступ = 864277 Подплан 2-> Результат (стоимость = 0,48..0.49 строк = 1 ширина = 8) (фактическое время = 0.007..0.007 строк = 1 цикл = 237028) Буферы: общий доступ = 864277 InitPlan 1 (возвращает $ 3) -> Лимит (стоимость =0.43..0.48 строк = 1 ширина = 8) (фактическое время = 0,007..0.007 строк = 1 цикл = 237028) Буферы: общее попадание = 864277 -> Сканирование только по индексу с использованием index_payments_on_user_id для платежей платежей_1 (стоимость = 0,43..80786,25 строк= 1619364 ширина = 8) (фактическое время = 0,007..0.007 строк = 1 цикл = 237028) Индекс Cond: ((user_id IS NULL) И (user_id> t_1.user_id)) Выборки кучи: 46749 Буферы: общий доступ = 864277-> Вложенный цикл (стоимость = 214.94..21498.23 строки = 100 ширина = 32) (фактическое время = 0.475..10794.535 строк = 167333 цикла = 1) Буферы: общее попадание = 6396757 -> Сканирование CTE на t (стоимость = 0.00..2.02 строки = 100 ширина = 8) (фактическое время = 0.145..1998.788 строк = 237028 циклов = 1) Фильтр: (user_id IS NOT NULL) Строки, удаленные фильтром: 1 Buffers: общее попадание = 864281 -> предел (стоимость = 214.94..214.94 строк = 1 ширина = 24) (фактическое время = 0.037..0.037 строк = 1 цикл = 237028) Буферы: общее попадание = 5532476 -> сортировка (стоимость =214.94..215.37 строк = 172 ширины = 24) (фактическое время = 0.036..0.036 строк = 1 петля = 237028) Ключ сортировки: payment.finished_at DESC Метод сортировки: быстрая сортировка Память: 25 КБ Буферы: общий доступ = 5532476 -> Сканирование индексаиспользование index_payments_on_user_id для платежей (стоимость = 0.43..214.08 строк = 172 ширины = 24) (фактическое время = 0.003..0.034 строк = 15 циклов = 237028) Индекс Cond: (user_id = t.user_id) Фильтр: (success AND ((action) :: text = 'deposit' :: text) AND ((валюта) :: text = 'RUB' :: text)) Строк, удаленных фильтром: 6 буферов: общее попадание = 5532473 -> Хеш (стоимость = 75,00..75.00 строк = 6000 по ширине = 4) (фактическое время = 2.255..2.255 строк = 6000 циклов = 1) Контейнеры: 8192 Пакеты: 1 Использование памяти: 275 КБ-> Сканирование значений в « VALUES » (стоимость = 0,00..75,00 строк = 6000 ширины = 4) (фактическое время = 0,004..1,206 строк = 6000 циклов = 1) Время планирования: 7,029 мс Время выполнения: 10846,774 мс

1 Ответ

1 голос
/ 01 июля 2019

Для этого запроса:

SELECT DISTINCT ON (user_id)
       p.user_id,
       p.finished_at as last_deposit_date,
       (CASE WHEN p.currency = 'RUB' THEN p.amount_cents  END) as last_deposit_amount_cents
FROM payments p JOIN
     (VALUES (5),( 22), (26) --~6000 values
     ) v(user_id)
     USING (user_id)
WHERE p.action = 'deposit' AND
      p.success = 't' ND
      p.currency = 'RUB'
ORDER BY p.user_id, p.finished_at DESC;

Я не совсем понимаю выражение CASE, потому что WHERE отфильтровывает все остальные значения.

Тем не менее, я ожидаю, что индекс на (action, success, currency, user_id, finished_at desc) будет полезным.

...