Странное использование индекса PostgreSQL при использовании LIMIT..OFFSET - PullRequest
1 голос
/ 06 июня 2019

PostgreSQL 9.6.3 на x86_64-pc-linux-gnu, скомпилированный gcc (Debian 4.9.2-10) 4.9.2, 64-битный

Таблица и индексы:

create table if not exists orders
(
    id bigserial not null constraint orders_pkey primary key,
    partner_id integer,
    order_id varchar,
    date_created date,
    state_code integer,
    state_date timestamp,
    recipient varchar,
    phone varchar,
);

create index if not exists orders_partner_id_index on orders (partner_id);
create index if not exists orders_order_id_index on orders (order_id);
create index if not exists orders_partner_id_date_created_index on orders (partner_id, date_created);

Задача состоит в создании подкачки / сортировки / фильтрации данных.

Запрос на первую страницу:

select order_id, date_created, recipient, phone, state_code, state_date
from orders
where partner_id=1 and date_created between '2019-04-01' and '2019-04-30'
order by order_id asc limit 10 offset 0;

План запроса:

QUERY PLAN
"Limit  (cost=19495.48..38990.41 rows=10 width=91)"
"  ->  Index Scan using orders_order_id_index on orders  (cost=0.56..**41186925.66** rows=21127 width=91)"
"        Filter: ((date_created >= '2019-04-01'::date) AND (date_created <= '2019-04-30'::date) AND (partner_id = 1))"

Индекс orders_partner_id_date_created_index не используется, поэтому стоимость очень высока!

Но, начиная с некоторых значений смещения (точное значение время от времени отличается, похоже, что оно зависит от общего количества строк), индекс начинает использоваться:

select order_id, date_created, recipient, phone, state_code, state_date
from orders
where partner_id=1 and date_created between '2019-04-01' and '2019-04-30'
order by order_id asc limit 10 offset 40;

План:

QUERY PLAN
"Limit  (cost=81449.76..81449.79 rows=10 width=91)"
"  ->  Sort  (cost=81449.66..81502.48 rows=21127 width=91)"
"        Sort Key: order_id"
"        ->  Bitmap Heap Scan on orders  (cost=4241.93..80747.84 rows=21127 width=91)"
"              Recheck Cond: ((partner_id = 1) AND (date_created >= '2019-04-01'::date) AND (date_created <= '2019-04-30'::date))"
"              ->  Bitmap Index Scan on orders_partner_id_date_created_index  (cost=0.00..4236.65 rows=21127 width=0)"
"                    Index Cond: ((partner_id = 1) AND (date_created >= '2019-04-01'::date) AND (date_created <= '2019-04-30'::date))"

Что происходит?Это способ заставить сервер использовать индекс?

1 Ответ

3 голосов
/ 06 июня 2019

Общий ответ:

  • Postgres хранит некоторую информацию о ваших таблицах
  • Перед выполнением запроса планировщик подготавливает план выполнения на основе этой информации
  • В вашем случае, планировщик считает, что для определенного значения смещения этот неоптимальный план будет лучше. Обратите внимание, что желаемый план требует сортировки всех выбранных строк по order_id, тогда как этот «худший» план этого не делает. Я предполагаю, что Postgres делает ставку, что таких строк будет достаточно для разных заказов, и просто проверяет один заказ за другим, начиная с самого низкого.

Я могу придумать два решения:

A) предоставить больше данных на станок, запустив

ANALYZE orders;

(https://www.postgresql.org/docs/9.6/sql-analyze.html)

или бо меняя собранную статистику

ALTER TABLE orders SET STATISTCS (...);

(https://www.postgresql.org/docs/9.6/planner-stats.html)

B) Перепишите запрос так, чтобы он указывал на желаемое использование индекса, например:

WITH
partner_date (partner_id, date_created) AS (
    SELECT  1,
            generate_series('2019-04-01'::date, '2019-04-30'::date, '1 day'::interval)::date
)
SELECT o.order_id, o.date_created, o.recipient, o.phone, o.state_code, o.state_date
FROM   orders o
JOIN   partner_date pd
    ON (o.partner_id, o.date_created) = (pd.partner_id, pd.date_created)
ORDER BY order_id ASC LIMIT 10 OFFSET 0;

Или, может быть, даже лучше:

WITH
partner_date (partner_id, date_created) AS (
    SELECT  1,
            generate_series('2019-04-01'::date, '2019-04-30'::date, '1 day'::interval)::date
), 
all_data AS (
    SELECT o.order_id, o.date_created, o.recipient, o.phone, o.state_code, o.state_date
    FROM   orders o
    JOIN   partner_date pd
        ON (o.partner_id, o.date_created) = (pd.partner_id, pd.date_created)
)
SELECT *
FROM   all_data
ORDER BY order_id ASC LIMIT 10 OFFSET 0;

Отказ от ответственности - я не могу объяснить, почему первый запрос должен интерпретироваться планировщиком Postgres иначе, просто думаю, что это возможно. С другой стороны, второй запрос отделяет смещения / лимиты от объединений, и я был бы очень удивлен, если бы Postgres все-таки сделал это «плохо» (по вашим оценкам).

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