Почему PostgreSQL использует неоптимальный план выполнения для простого запроса?

Знакомлюсь с PostgreSQL 9.5 и меня уже много "простых" моментов начинают смущать. Вот один из них.
Создаю в базе таблицу params:
CREATE TABLE params(
counter_id integer NOT NULL, 
"time" bigint NOT NULL, 
value integer NOT NULL, 
param integer NOT NULL, 
param_id bigint NOT NULL, 
CONSTRAINT param_id 
PRIMARY KEY (param_id));

Для таблицы params создаю индекс:
CREATE INDEX counter_id_time ON params (counter_id, time);

Добавляю в таблицу params для экспериментов 320 млн записей.
Затем выполняю следующие запросы:
SELECT MAX(time) FROM params WHERE counter_id = 6001; -- 0,031 с

SELECT MAX(time) FROM params WHERE counter_id = 15998; -- 0,016 с

Затем выполняю такой запрос:
SELECT MAX(time) FROM params WHERE counter_id = 6001 OR counter_id = 15998; -- 139 с !??

Почему PostgreSQL выбирает такой тернистый вариант для такого "простого" запроса?
Вот какой план для выполнения запроса выдаёт БД:
"Aggregate  (cost=140975.88..140975.89 rows=1 width=8)"
"  ->  Bitmap Heap Scan on params  (cost=919.98..140878.74 rows=38853 width=8)"
"        Recheck Cond: ((counter_id = 6001) OR (counter_id = 15998))"
"        ->  BitmapOr  (cost=919.98..919.98 rows=38854 width=0)"
"              ->  Bitmap Index Scan on counter_id_time  (cost=0.00..450.27 rows=19427 width=0)"
"                    Index Cond: (counter_id = 6001)"
"              ->  Bitmap Index Scan on counter_id_time  (cost=0.00..450.27 rows=19427 width=0)"
"                    Index Cond: (counter_id = 15998)"

Мне пришлось "разжевать" для PostgreSQL запрос до такого:
SELECT MAX(m) FROM (SELECT MAX(time) m FROM params WHERE counter_id = 6001 UNION SELECT MAX(time) m FROM params WHERE counter_id = 15998) p; -- 0,016 c

Вот какой план для выполнения этого запроса выдаёт БД:
"Unique  (cost=9.23..9.24 rows=2 width=0)"
"  ->  Sort  (cost=9.23..9.24 rows=2 width=0)"
"        Sort Key: ($1)"
"        ->  Append  (cost=4.59..9.22 rows=2 width=0)"
"              ->  Result  (cost=4.59..4.60 rows=1 width=0)"
"                    InitPlan 2 (returns $1)"
"                      ->  Limit  (cost=0.57..4.59 rows=1 width=8)"
"                            ->  Index Only Scan Backward using counter_id_time on params params_1  (cost=0.57..78084.90 rows=19427 width=8)"
"                                  Index Cond: ((counter_id = 6001) AND ("time" IS NOT NULL))"
"              ->  Result  (cost=4.59..4.60 rows=1 width=0)"
"                    InitPlan 1 (returns $0)"
"                      ->  Limit  (cost=0.57..4.59 rows=1 width=8)"
"                            ->  Index Only Scan Backward using counter_id_time on params  (cost=0.57..78084.90 rows=19427 width=8)"
"                                  Index Cond: ((counter_id = 15998) AND ("time" IS NOT NULL))"

Например, в MySQL 5.7.10 и MS SQL 2014 SP1, проблемный для PostgreSQL запрос не является проблемным.
К чему я это всё: когда мне необходимо будет сделать выборку по нескольким counter_id, по условию количество которых будет ограничено, PostgreSQL всё равно будет пытаться сделать выборку по всем counter_id, а только в конце отсеет ненужные значения. Использование UNIOIN конечно же вариант, но я читаю его неоптимальным в большинстве случаев.
Что можно сделать, чтобы PostgreSQL "корректно" отрабатывал такие запросы?
  • Вопрос задан
  • 844 просмотра
Пригласить эксперта
Ответы на вопрос 1
Melkij
@Melkij
PostgreSQL DBA
Обычно делают counter_id in (6001, 15998) а не толпу or.
explain analyze select max(time) from params where counter_id in (6001, 15998);

Aggregate (cost=18.52..18.53 rows=1 width=8) (actual time=1.586..1.586 rows=1 loops=1)
-> Index Only Scan using params_counter_id_time_idx on params (cost=0.56..18.51 rows=4 width=8) (actual time=0.769..1.566 rows=12 loops=1)
Index Cond: (counter_id = ANY ('{6001,15998}'::bigint[]))
Heap Fetches: 2
Planning time: 50.191 ms
Execution time: 1.933 ms

Тут на порядок поменьше данных, это просто виртуалка и pg ещё 9.4

or две минуты исполнения тоже не даёт:
Aggregate (cost=25.20..25.21 rows=1 width=8) (actual time=38.797..38.797 rows=1 loops=1)
-> Bitmap Heap Scan on params (cost=9.16..25.19 rows=4 width=8) (actual time=0.362..38.749 rows=12 loops=1)
Recheck Cond: ((counter_id = 6001) OR (counter_id = 15998))
Heap Blocks: exact=12
-> BitmapOr (cost=9.16..9.16 rows=4 width=0) (actual time=0.042..0.042 rows=0 loops=1)
-> Bitmap Index Scan on params_counter_id_time_idx (cost=0.00..4.58 rows=2 width=0) (actual time=0.031..0.031 rows=6 loops=1)
Index Cond: (counter_id = 6001)
-> Bitmap Index Scan on params_counter_id_time_idx (cost=0.00..4.58 rows=2 width=0) (actual time=0.009..0.009 rows=6 loops=1)
Index Cond: (counter_id = 15998)
Planning time: 0.247 ms
Execution time: 39.408 ms


Попробуйте сделать analyze params;
Может, у планировщика статистика старая.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы