Как анализировать параметры SQL запроса
Что проверяют
Умеете ли вы увидеть, с какими значениями на самом деле уходит запрос из Java-приложения, и разобрать его план на этих значениях. Следом обычно спрашивают, почему запрос быстрый в консоли и медленный из приложения.
Как отвечать
Каркас из трёх шагов: достать запрос с параметрами, получить план, проверить, как параметры влияют на выбор плана.
- логи ORM:
spring.jpa.show-sqlпоказывает SQL с?вместо значений. Сами значения в Hibernate 6 пишет логгерorg.hibernate.orm.jdbc.bindна уровнеTRACE. Удобнее обёртки надDataSource: datasource-proxy или p6spy логируют запрос с подставленными параметрами и временем; - статистика базы: в PostgreSQL
pg_stat_statementsсобирает нормализованные запросы с$1,$2и показывает суммарное и среднее время.log_min_duration_statementпишет в лог медленные запросы,auto_explainдобавляет к ним план; - план:
EXPLAIN (ANALYZE, BUFFERS)на реальных значениях. Смотрите тип сканирования и расхождение ожидаемых и фактических строк. С PostgreSQL 16 естьEXPLAIN (GENERIC_PLAN)для запроса с$1без значений.
Главная тонкость про параметры. PgJDBC после пяти выполнений одного PreparedStatement (настройка prepareThreshold) переходит на серверные подготовленные запросы, а PostgreSQL после нескольких выполнений может закрепить общий план, не зависящий от значения. При перекошенных данных такой план хорош для редкого значения и плох для частого. Лечится статистикой, переписыванием запроса или plan_cache_mode.
Где спотыкаются
Проверяют план на случайной константе. Запрос с status = 'archived' и с status = 'new' может идти по разным планам, поэтому берите значения из реальных логов.
Пропускают несовпадение типов. Если передать BigDecimal для колонки bigint, Postgres приводит колонку к numeric, и индекс не используется. Похожая история с IN на списке переменной длины: каждая длина даёт новый текст запроса и засоряет кэши, выручают = ANY(?) с массивом или hibernate.query.in_clause_parameter_padding.
Запускают EXPLAIN ANALYZE на UPDATE или DELETE в проде, забыв, что запрос реально выполнится. Такой разбор делают внутри транзакции с ROLLBACK.