Боремся со сверхинтеллектом Postgresql средствами Postgresql
PostgreSQL — отличнейшая БД, планировщик которой достаточно интеллектуален.
Однако в ряде случаев мощь интеллекта планировщика вырастает настолько, что он превращается в сверх-интеллект, ну и как всякий сверх-интеллект — объявляет войну своему создателю, а прежде всего начинает с войны с проектом в котором живет.
Образумливать взбунтовавшийся интеллект иногда очень сложно. Поделюсь недавней «находкой» в этой области.
Предположим что у Вас есть вебсайт, на котором есть несколько страничек, которые выводят выборки из БД (в общем-то типовой случай).
Рассмотрим простейший пример. Допустим Вы выбираете список из одной таблицы по пересечению условий AND
:
/* Запрос № 1*/
SELECT
*
FROM
"table1"
WHERE
"a" = 1
AND "b" = 2
AND "c" = 3
При разработке приложения, разработчику очевидно что для данного запроса
неплохо иметь индекс:
CREATE INDEX CONCURRENTLY "table_index1" ON "table1"("a","b","c");
Или даже частичный, если какое-то из полей — константа:
CREATE INDEX CONCURRENTLY "table_index1" ON "table1"("a","b")
WHERE "c" = 3;
На другой странице у Вас может быть выборка по «c» и «e»:
/* Запрос №2 */
SELECT
*
FROM
"table1"
WHERE
"c" = 456
ORDER BY
"e"
LIMIT
10
соответственно для другой страницы у Вас будет индекс:
CREATE INDEX CONCURRENTLY "table_index2" ON "table1"("c","e");
Ну и если проект у Вас большой, то вполне вероятно наличие третьего индекса:
CREATE INDEX CONCURRENTLY "table_index3" ON "table1"("a","b","e");
Теперь Ваш сайт работает нормально, таблицы (в данном примере — одна штука) понемногу наполняются.
Сайт развивается. Разработчики иногда добавляют индексы для важных выборок.
В какой-то момент случается факап. Вы заходите pg_dump
и видите сотни запросов №1. Вы начинаете исследовать EXPLAIN этого запроса и обнаруживаете нечто вроде следующего:
Bitmap Heap Scan on table1 (cost=43482.22..54652.36 rows=2806 width=2494) (actual time=2544.520..2602.755 rows=337 loops=1)
Recheck Cond: ((a = 1) AND (b = 2) AND (c = 3))
Heap Blocks: exact=86917
-> BitmapAnd (cost=43482.22..43482.22 rows=2806 width=0) (actual time=2516.333..2516.333 rows=0 loops=1)
-> Bitmap Index Scan on table_index2 (cost=0.00..7643.88 rows=150331 width=0) (actual time=1791.934..1791.934 rows=3982643 loops=1)
Index Cond: (c = 3)
-> Bitmap Index Scan on table_index3 (cost=0.00..35836.69 rows=1258378 width=0) (actual time=91.829..91.829 rows=377222 loops=1)
Index Cond: ((a = 1) AND (b = 2))
Planning time: 2.706 ms
Execution time: 2612.418 ms
(10 строк)
То есть начиная с какого-то размера таблицы постгрис решил что вместо того чтобы выбрать 337 записей напрямую из предназначенного для этого запроса (возможно частичного!) индекса, он лучше сделает выборку на 4+ млн записей, потом перемножит ее с выборкой на 0.3+ млн записей и вернет Вам результат из 337 записей.
Как бороться с этим?
Гугл выдает много ссылок с аналогичными вопросами, но мало ответов.
Есть ссылки на разные решения, в том числе и тут на хабре. Однако большинство решений требуют при апгрейдах Pg заниматься вопросом и их апгрейда, требуют нестандартного запуска Pg итп.
В общем нам, смигрировавшим за последние пару лет между 9.0 → 9.1 → 9.3 → 9.5, планирующим апгрейд на 10.x, такой способ не очень подходит.
Заставить PostgreSQL использовать Ваш индекс можно и штатным способом. Для этого нам потребуется фейковая функция:
CREATE FUNCTION "selindex" ("name" TEXT)
RETURNS BOOLEAN
AS $$
BEGIN
RETURN TRUE;
END;
$$
LANGUAGE plpgsql
IMMUTABLE
;
Функция ничего не делает, но будет использоваться для того чтобы сбить сверхинтеллект PostgreSQL с толку в нужное нам русло.
Далее перестраиваем индексы примерно таким образом:
CREATE INDEX CONCURRENTLY "table_index1" ON "table1"("a","b","c");
WHERE "selindex"('table_index1')
Если секция WHERE уже имеется — добавляем в нее соответствующее условие AND:
CREATE INDEX CONCURRENTLY "table_index1" ON "table1"("a","b")
WHERE "c" = 3 AND "selindex"('table_index1');
Ну, а в запросы дописываем AND "selindex"('table_index1')
:
SELECT
*
FROM
"table1"
WHERE
"a" = 1
AND "b" = 2
AND "c" = 3
AND "selindex"('table_index1')
PS: Честно говоря я подустал разгребать факапы связанные со «сверхинтеллектом». Отладка, интеграционные тесты, нагрузочные тесты Вам не гарантируют что в перспективе PostgreSQL не откажется от использования того или иного индекса в пользу двух-пяти других. Но пока изящного решения как бороться с этим я не нашел.