Postgres оптимизатор запросов генерирует плохой план после добавления еще одного заказа по критерию - PullRequest
1 голос
/ 10 июля 2020

Я использую django orm со связанным выбором, и он генерирует запрос формы:

SELECT *
  FROM "coupons_coupon"
  LEFT OUTER JOIN "coupons_merchant"
    ON ("coupons_coupon"."merchant_id" = "coupons_merchant"."slug")
 WHERE ("coupons_coupon"."end_date" > '2020-07-10T09:10:28.101980+00:00'::timestamptz AND "coupons_coupon"."published" = true)
 ORDER BY "coupons_coupon"."end_date" ASC, "coupons_coupon"."id"
 LIMIT 5;

, который затем выполняется по следующему плану:

Limit  (cost=4363.28..4363.30 rows=5 width=604) (actual time=21.864..21.865 rows=5 loops=1)
  ->  Sort  (cost=4363.28..4373.34 rows=4022 width=604) (actual time=21.863..21.863 rows=5 loops=1)
        Sort Key: coupons_coupon.end_date, coupons_coupon.id"
        Sort Method: top-N heapsort  Memory: 32kB
        ->  Hash Left Join  (cost=2613.51..4296.48 rows=4022 width=604) (actual time=13.918..20.209 rows=4022 loops=1)
              Hash Cond: ((coupons_coupon.merchant_id)::text = (coupons_merchant.slug)::text)
              ->  Seq Scan on coupons_coupon  (cost=0.00..291.41 rows=4022 width=261) (actual time=0.007..1.110 rows=4022 loops=1)
                    Filter: (published AND (end_date > '2020-07-10 09:10:28.10198+00'::timestamp with time zone))
                    Rows Removed by Filter: 1691
              ->  Hash  (cost=1204.56..1204.56 rows=24956 width=331) (actual time=13.894..13.894 rows=23911 loops=1)
                    Buckets: 16384  Batches: 4  Memory Usage: 1948kB
                    ->  Seq Scan on coupons_merchant  (cost=0.00..1204.56 rows=24956 width=331) (actual time=0.003..4.681 rows=23911 loops=1)

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

Limit  (cost=0.57..8.84 rows=5 width=600) (actual time=0.013..0.029 rows=5 loops=1)
  ->  Nested Loop Left Join  (cost=0.57..6650.48 rows=4022 width=600) (actual time=0.012..0.028 rows=5 loops=1)
        ->  Index Scan using coupons_cou_end_dat_a8d5b7_btree on coupons_coupon  (cost=0.28..1015.77 rows=4022 width=261) (actual time=0.007..0.010 rows=5 loops=1)
              Index Cond: (end_date > '2020-07-10 09:10:28.10198+00'::timestamp with time zone)
              Filter: published
        ->  Index Scan using coupons_merchant_pkey on coupons_merchant  (cost=0.29..1.40 rows=1 width=331) (actual time=0.003..0.003 rows=1 loops=5)
              Index Cond: ((slug)::text = (coupons_coupon.merchant_id)::text)

Почему это происходит? Можно ли подтолкнуть оптимизатор к использованию аналогичного плана для предыдущего запроса?

Я использую postgres 12.

1 Ответ

3 голосов
/ 10 июля 2020

v13 из PostgreSQL, который должен быть выпущен в ближайшие несколько месяцев, реализует инкрементную сортировку, при которой он может читать строки в предварительно отсортированном порядке на основе столбцов префиксов, а затем сортирует только связи в этих столбцах префиксов ( s) по оставшимся столбцам, чтобы получить полную сортировку на основе большего количества столбцов, чем предоставляет индекс. Я думаю, что это сделает более или менее то, что вы хотите.

Limit  (cost=2.46..2.99 rows=5 width=21)
   ->  Incremental Sort  (cost=2.46..405.58 rows=3850 width=21)
         Sort Key: coupons_coupon.end_date, coupons_coupon.id
         Presorted Key: coupons_coupon.end_date
         ->  Nested Loop Left Join  (cost=0.31..253.48 rows=3850 width=21)
               ->  Index Scan using coupons_coupon_end_date_idx on coupons_coupon  (cost=0.15..54.71 rows=302 width=17)
                     Index Cond: (end_date > '2020-07-10 05:10:28.10198-04'::timestamp with time zone)
                     Filter: published
               ->  Index Only Scan using coupons_merchant_slug_idx on coupons_merchant  (cost=0.15..0.53 rows=13 width=4)
                     Index Cond: (slug = coupons_coupon.merchant_id)

Конечно, простое добавление id в текущий индекс будет работать с текущими выпущенными версиями, и даже под версией 13 должно быть более эффективно иметь индекс полностью упорядочить строки так, как вам нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...