Медленный запрос PostgreSQL с лимитом 1 и порядком по ненужному условию где - PullRequest
1 голос
/ 20 сентября 2019

У меня есть одна таблица accounts и индексы

accounts {
    id  text
    num_id  bigint
    pid text
    fid text
    created_at  timestamp with time zone
    updated_at  timestamp with time zone
}

CREATE UNIQUE INDEX accounts_pkey ON public.accounts USING btree (id)
CREATE INDEX fid_idx ON public.accounts USING btree (fid)
CREATE INDEX idx_accounts_pid_fid ON public.accounts USING btree (pid, fid)

И этот запрос медленный

explain analyse SELECT * FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY  id ASC
LIMIT 1;
Limit  (cost=0.56..3173.34 rows=1 width=123) (actual time=49389.351..49389.351 rows=0 loops=1)
  ->  Index Scan using accounts_pkey on accounts  (cost=0.56..5022497.13 rows=1583 width=123) (actual time=49389.350..49389.350 rows=0 loops=1)
        Filter: ((pid = 'hd'::text) AND (fid = '123'::text))
        Rows Removed by Filter: 56821193
Planning time: 0.094 ms
Execution time: 49389.368 ms

За этот ответ , возможно,быть решена путем добавления ненужных, где условия pid и fid

explain analyse SELECT * FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY  id ASC, pid, fid
LIMIT 1;

Однако, это не работает

Limit  (cost=0.56..3173.37 rows=1 width=123) (actual time=49495.236..49495.236 rows=0 loops=1)
  ->  Index Scan using accounts_pkey on accounts  (cost=0.56..5022556.07 rows=1583 width=123) (actual time=49495.234..49495.234 rows=0 loops=1)
        Filter: ((pid = 'hd'::text) AND (fid = '123'::text))
        Rows Removed by Filter: 56821555
Planning time: 0.096 ms
Execution time: 49495.253 ms

Есть ли я пропускаю?

Версия PostgreSQL: 9.6.8

1 Ответ

2 голосов
/ 20 сентября 2019

Из ваших комментариев следующий запрос на самом деле довольно производительный:

SELECT *
FROM accounts
ORDER BY id
LIMIT 1;

Причина, по которой он работает хорошо, заключается в том, что шаг LIMIT и ORDER BY - единственное, что нужно сделать Postgresдо SELECT, и уникальный индекс accounts_pkey можно легко отсканировать здесь.На самом деле, Postgres нужно только найти самое низкое значение id, а затем вернуться к кластерному индексу, чтобы охватить SELECT *.

Однако запрос в вашем вопросе немного другой:

SELECT *
FROM accounts
WHERE pid = 'hd' AND fid = '123'
ORDER BY id ASC
LIMIT 1;

В этом случае Postgres выбирает сканирование всего индекса accounts_pkey, начиная с шага фильтрации, соответствующего вашему предложению WHERE.Поскольку accounts_pkey охватывает только столбец id, Postgres должен выполнить поиск в кластеризованном индексе, чтобы найти значения pid и fid.В идеале Postgres должен начинать с самого низкого значения id и идти по индексу до тех пор, пока не найдет первое совпадение по значениям pid и fid.Независимо от того, что Postgres решит сделать, здесь может помочь следующий индекс покрытия:

CREATE INDEX idx_accounts_cover ON public.accounts USING btree (pid, fid, id);

Учитывая, что почти 6 миллионов записей теперь могут быть легко удалены с использованием указанного индекса, оставшиеся LIMIT / ORDER BY операция на id может быть более терпимой.И поскольку этот индекс также охватывает id, Postgres должен будет выполнить возврат к кластерному индексу только один раз, в самом конце запроса.

...