Анализ плана выполнения PostgreSQL - PullRequest
0 голосов
/ 15 октября 2019

Я пытался понять, как PostgreSQL использует индексы и каковы их планы выполнения.

Я выполнил приведенный ниже запрос,

EXPLAIN(VERBOSE,ANALYZE)
SELECT SUM("Orders"."order_subtotal_net_after_discount") AS "Total sum of Order Subtotal Net After Discount"
FROM "ol"."orders_test" AS "Orders"
LEFT OUTER JOIN "ol"."ga_transactions" AS "Ga Transactions" ON "Ga Transactions"."transaction_id" = "Orders"."order_number"
WHERE ( to_char(created_at_order,'YYYY-MM-DD')=to_char(now(),'YYYY-MM-DD')
       AND "Orders"."order_state_2"<>'canceled'
      AND "Orders"."order_state_2" <> 'pending_payment'
      AND "Orders"."order_state_1" <> 'canceled'
      AND "Orders"."order_state_1" <> 'pending_payment'    
      AND "Orders"."order_state_1" <> 'closed'
      AND "Orders"."order_state_2" <> 'closed'
       )

И получил приведенный ниже план выполнения, обнаружил, что происходит последовательное сканирование с использованием фильтра ниже

Filter: ((("Orders".order_state_2)::text <> 'canceled'::text) AND (("Orders".order_state_2)::text <> 'pending_payment'::text) AND (("Orders".order_state_1)::text <> 'canceled'::text) AND (("Orders".order_state_1)::text <> 'pending_payment'::text) AND (("Orders".order_state_1)::text <> 'closed'::text) AND (("Orders".order_state_2)::text <> 'closed'::text) AND (to_char("Orders".created_at_order, 'YYYY-MM-DD'::text) = to_char(now(), 'YYYY-MM-DD'::text)))"

ЦелоеПлан запроса

Aggregate  (cost=157385.36..157385.37 rows=1 width=32) (actual time=840.221..840.221 rows=1 loops=1)
  Output: sum("Orders".order_subtotal_net_after_discount)
  ->  Nested Loop Left Join  (cost=0.42..157378.56 rows=2717 width=6) (actual time=0.017..840.095 rows=470 loops=1)
        Output: "Orders".order_subtotal_net_after_discount
        ->  Seq Scan on ol.orders_test "Orders"  (cost=0.00..148623.91 rows=2717 width=16) (actual time=0.009..838.144 rows=470 loops=1)
              Output: "Orders".rno, "Orders".order_id, "Orders".order_number, "Orders".invoice_id, "Orders".invoice_number, "Orders".store_id, "Orders".customer_id, "Orders".real_customer_id, "Orders".shipping_address_id, "Orders".order_state_1, "Orders".order_state_2, "Orders".invoice_state, "Orders".shipping_street, "Orders".shipping_city, "Orders".shipping_postcode, "Orders".shipping_country_id, "Orders".shipping_description, "Orders".coupon_code, "Orders".first_order_of_customer, "Orders".payment_method, "Orders".order_subtotal_net, "Orders".order_subtotal_net_after_discount, "Orders".order_subtotal, "Orders".order_shipment, "Orders".order_shipping_tax, "Orders".order_discount, "Orders".order_tax_total, "Orders".order_grand_total, "Orders".order_total_paid, "Orders".order_refunded_total, "Orders".order_total_open, "Orders".invoice_subtotal_net, "Orders".invoice_subtotal_net_after_discount, "Orders".invoice_subtotal, "Orders".invoice_shipment, "Orders".invoice_shipping_tax, "Orders".invoice_discount, "Orders".invoice_tax_total, "Orders".invoice_grand_total, "Orders".invoice_refunded_total, "Orders".created_at_order, "Orders".created_at_invoice, "Orders".updated_at_invoice, "Orders".customer_email, "Orders".row_number, "Orders".nthorder, "Orders".time_since_last_order, "Orders".time_since_first_order
              Filter: ((("Orders".order_state_2)::text <> 'canceled'::text) AND (("Orders".order_state_2)::text <> 'pending_payment'::text) AND (("Orders".order_state_1)::text <> 'canceled'::text) AND (("Orders".order_state_1)::text <> 'pending_payment'::text) AND (("Orders".order_state_1)::text <> 'closed'::text) AND (("Orders".order_state_2)::text <> 'closed'::text) AND (to_char("Orders".created_at_order, 'YYYY-MM-DD'::text) = to_char(now(), 'YYYY-MM-DD'::text)))
              Rows Removed by Filter: 654356
        ->  Index Only Scan using ga_transactions_transaction_id_idx on ol.ga_transactions "Ga Transactions"  (cost=0.42..3.21 rows=1 width=10) (actual time=0.004..0.004 rows=0 loops=470)
              Output: "Ga Transactions".transaction_id
              Index Cond: ("Ga Transactions".transaction_id = ("Orders".order_number)::text)
              Heap Fetches: 0
Planning Time: 0.540 ms
Execution Time: 840.255 ms

Я создал указанный ниже индекс, и, похоже, запрос не использует этот индекс,

  1. Нужно ли включать все выходные столбцы вВКЛЮЧИТЬ список?

  2. Если я добавлю больше индексов, это увеличит время обновления. Так есть ли лучший способ определить эффективные индексы для таблицы?

CREATE INDEX "IX_states"
    ON ol.orders_test USING btree
    (order_state_2   ASC NULLS LAST, order_state_1   ASC NULLS LAST, created_at_order   ASC NULLS LAST)
    INCLUDE(rno, order_id, order_number, invoice_id, invoice_number, store_id, customer_id, real_customer_id, shipping_address_id, invoice_state, shipping_street, shipping_city, shipping_postcode, shipping_country_id, shipping_description, coupon_code, first_order_of_customer, payment_method, order_subtotal_net_after_discount, created_at_invoice, customer_email, nthorder, time_since_last_order, time_since_first_order)
    WITH (FILLFACTOR=90)
    TABLESPACE pg_default;

Ответы [ 2 ]

2 голосов
/ 15 октября 2019

Нет, нет необходимости включать все столбцы в индекс. Индекс используется для быстрого поиска нескольких строк. Это не волшебная пуля, которая делает все быстрее, внезапно. Если вы включите в индекс все столбцы таблицы, сканирование индекса займет столько же времени, сколько и сканирование Seq

Вы определенно хотите индексировать created_at_order.

create index on orders_test (created_at_order);

Однако ваше выражение to_char(created_at_order,'YYYY-MM-DD') не будет использовать этот индекс. Лучше всего изменить ваше состояние:

where created_at_order >= current_date and created_at_order < current_date + 1

Это будет использовать вышеуказанный индекс. Если вы не хотите использовать такое выражение, вам нужен специальный индекс для выражения. Вместо преобразования его в текстовое значение я бы индексировал значение даты:

create index on orders_test ( (created_at_order::date) );

Индексирование значения даты также имеет то преимущество, что индекс меньше. date занимает всего 4 байта, тогда как строка '2019-10-15' занимает десять байтов памяти.

Тогда этот индекс будет использовать следующий код:

where created_at_order::date = current_date

Если дополнительные ограничения наСтолбцы состояний еще больше сокращают строки, для этого можно использовать отфильтрованный индекс:

create index on orders_test ( (created_at_order::date) )
WHERE order_state_2 <>'canceled'
  AND order_state_2  <> 'pending_payment'
  AND order_state_1  <> 'canceled'
  AND order_state_1  <> 'pending_payment'    
  AND order_state_1  <> 'closed'
  AND order_state_2  <> 'closed';

Это уменьшит индекс и ускорит поиск строк в индексе. Однако этот индекс больше не будет использоваться для запроса, который не включает эти условия.

Если вы не используете все эти условия все время, уменьшите их до тех, которые вы используете всегда.

Если эти дополнительные условия действительно не уменьшают количество строк, потому что условие «создано сегодня» уже очень ограничивающее, вы можете их пропустить.

0 голосов
/ 15 октября 2019

Вы создали индекс с created_at_order в качестве метки времени, но в WHERE вы используете преобразование функций и сравнение строк. Я бы посоветовал вам отредактировать WHERE clausule примерно так:

WHERE created_at_order BETWEEN now() AND TIMESTAMP 'today'
...