Совет дня №18
Когда Postgres выполняет запрос, он строит план. Это сложная задача: сперва нужно распрасить запрос, построить дерево, проверить синтаксис. Затем строится логический план: здесь некоторые шаги разбиваются на подшаги, а другие, наоборот, группируются в один. На последнем этапе выводится физический план: в какую таблицу пойти, брать индекс или нет, в каком порядке выполнить джоины.
Парсинг запроса протекает достаточно быстро, потому что это чистая функция. Физический план, наоборот, дорогой. Вспомним: для того, чтобы определить, использовать индекс или нет, Postgres должен проверить, сколько в среднем строк в таблице; если она маленькая, проще сделать full scan. Далее нужно проверить, не является ли значение частым (с низкой селективностью). При анализе джоинов число комбинаций растет факториалом, поэтому Postgres применяет разные эвристики.
Подготовленное выражение – это запрос, который прошел этапы выше, и для него построен план. Далее его можно выполнить с разными параметрами, и (в идеале) план будет переиспользоваться. В идеале – потому что даже когда подготовленное выражение готово, оно не гарантирует применения плана. Если параметров нет, Postgres использует план. Если выражение вызывается с разными параметрами, Postgres ведет хеш-таблицу: параметры -> план -> время. Для одинаковых параметров план будет один и тот же. Накопив пять вызовов с разными параметрами, Postgres наконец-то определится с планом (с минимальным временем), и далее он используется всегда.
Некоторые клиенты к Postgres ведут свой кэш вида SQL -> preparedStatement. Каждый раз когда выполняется запрос, клиент ищет айди подготовленного выражения во внутреннем кэше. Если он есть, вызывается команда “выполни выражение foobar с такими-то параметрами” (аналог execute). Если Postgres ругнулся, что такого выражения нет, эта ошибка перехватывается, и посылается команда “тогда подготовь этот SQL и назначь ему имя foobar”. Далее все повторяется.
Подготовленные выражения живут в рамках одного соединения с БД. Это особенно важно для пула соединений: когда мы выполняем запросы, то заимствуем подключение из пула, и они могут быть разными. Клиент учитывает это: кэш подготовленных выражений ведется в разрезе подключения. По мере ротации подключений подготовленные выражения распространяются по ним.
Пожалуй, в этом совете нечего взять на заметку, разве что подумать, сколько работы делают клиентские библиотеки помимо основных обязанностей. Заодно это плавный переход к теме планов и их анализа.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter