Как диагностировать причины замедления SQL запроса
Что проверяют
Умеете ли вы искать причину по шагам, а не сразу добавлять индекс. Для Go-разработчика важно ещё отделить медленную базу от медленного приложения: запрос может ждать свободного соединения в пуле, а не выполняться. Следом обычно дают план запроса и просят найти в нём проблему.
Как отвечать
Сначала локализуйте, где теряется время:
- пул соединений:
db.Stats()уdatabase/sqlпоказываетWaitCountиWaitDuration. Рост ожидания значит, что соединений не хватает или их держат незакрытыеrows; - трассировка: спаны OpenTelemetry вокруг запросов или
QueryTracerв pgx отделяют время в базе от сериализации и сети; - сама база:
pg_stat_statementsпоtotal_exec_timeиmean_exec_timeпокажет, какой запрос реально дорогой, аlog_min_duration_statementиauto_explainпоймают медленные в логах.
Дальше EXPLAIN (ANALYZE, BUFFERS) на конкретном запросе. Смотрят на расхождение ожидаемых и фактических строк (устаревшая статистика, помогает ANALYZE), Seq Scan по большой таблице, Nested Loop с тысячами повторов, сортировку с external merge на диске и число прочитанных буферов. Если запрос сам по себе быстрый, но иногда висит, проверьте блокировки: pg_stat_activity с wait_event_type = 'Lock' и pg_blocking_pids().
Отдельная ловушка на Go: pgx по умолчанию кэширует подготовленные выражения, и PostgreSQL после пяти выполнений может перейти на общий план. На неравномерных данных он бывает сильно хуже. Лечится plan_cache_mode = force_custom_plan на уровне сессии или другим режимом выполнения запросов в pgx.
Где спотыкаются
Запускают EXPLAIN ANALYZE на UPDATE или DELETE в проде. Он выполняет запрос по-настоящему, такие проверки делают внутри транзакции с ROLLBACK.
Проверяют на локальной базе с тысячей строк. Планировщик выбирает другой план, и проблема не воспроизводится.
Забывают defer rows.Close() и ищут тормоза в SQL, хотя запросы стоят в очереди за соединениями из пула.