Знакомлюсь с 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 "корректно" отрабатывал такие запросы?