Skip to main content
pgx — самый распространённый драйвер PostgreSQL для Go, а pgxpool — его пул соединений. В этом руководстве вы подключите сервис на Go к ClickHouse Managed Postgres по TLS с проверкой сертификата, создадите таблицу todos с помощью миграции golang-migrate и научите сервер на базе net/http обрабатывать запросы на создание, чтение, обновление и удаление.

Предварительные требования

  • Go версии 1.25 или новее. Это руководство протестировано с Go 1.27.1, pgx 5.11.0 и golang-migrate 4.20.1.
  • Аккаунт ClickHouse Cloud
  • psql для создания базы данных. Вместо этого можно выполнить оператор CREATE DATABASE в консоли SQL.

Создание сервиса ClickHouse Managed Postgres

В консоли ClickHouse Cloud нажмите New service и выберите Postgres. Инстанс будет готов через несколько минут. Пошаговая инструкция приведена в кратком руководстве по началу работы.

Получение сведений о подключении

Нажмите Подключение на левой боковой панели вашего сервиса. Оставьте выбранным вариант Directly и включите Use SSL. Теперь URL подключения на вкладке url использует порт 5432 и заканчивается на sslmode=verify-full&sslrootcert=<service-name>-ca-certificate.pem. Нажмите Download CA certificate. Скачать его можно и позже в разделе Settings → CA Certificate. Сертификат уникален для вашего инстанса, поэтому pgx может с его помощью убедиться, что подключается именно к вашему серверу. В этом руководстве и миграции, и приложение подключаются к Postgres напрямую через порт 5432. Сервис на Go — это долгоживущий процесс, а pgxpool уже поддерживает в нём пул соединений, поэтому второй пулер не нужен. Если вы запускаете много инстансов сервиса и вам требуется больше клиентских соединений, чем допускает Postgres, используйте для приложения встроенный PgBouncer (via PgBouncer, порт 6432). Подробнее см. в разделе Использование PgBouncer: в этом случае потребуется одна дополнительная настройка соединения.

Настройка проекта

Создайте модуль Go, добавьте pgx и установите CLI-утилиту migrate с драйвером pgx:
Флаг -tags 'pgx5' собирает CLI migrate с драйвером pgx v5, который использует схему URL pgx5://. go install помещает бинарный файл в $GOBIN, а если GOBIN не задан — в $(go env GOPATH)/bin, поэтому убедитесь, что этот каталог указан в вашем PATH.
Добавляете в существующее приложение?Пропустите mkdir и go mod init. В своём модуле выполните go get github.com/jackc/pgx/v5 и приведённую выше команду go install, затем перейдите к разделу Настройка соединения. Если ваш код использует database/sql, stdlib.OpenDBFromPool оборачивает тот же пул в *sql.DB, поэтому существующие запросы можно переносить постепенно; замените плейсхолдеры ? на $1, $2 и т. д.

Настройка соединения

Переместите загруженный CA‑сертификат в каталог проекта и переименуйте его в ca-certificate.pem. Создайте базу данных для приложения. Замените <PASSWORD> и хост на значения из модального окна Подключение:
Задайте в командной оболочке две переменные окружения. Обе указывают на одну и ту же базу данных guide_go и различаются только схемой URL: приложение считывает DATABASE_URL, а CLI-утилита migrate — MIGRATE_URL:
pgx разбирает URL так же, как libpq. При sslmode=verify-full он загружает файл CA из sslrootcert и проверяет как цепочку сертификатов, так и имя хоста, поэтому писать собственный код для TLS не нужно. Путь sslrootcert вычисляется относительно каталога, из которого вы запускаете команды. Если пароль содержит специальные символы, например @, / или #, закодируйте их с помощью URL-кодирования.
Всегда задавайте sslmode=verify-fullЕсли sslmode не указан, pgx использует режим prefer, при котором сертификат сервера не проверяется, даже если задан sslrootcert. Если при sslmode=verify-full указан неверный файл CA, pgx завершается с ошибкой x509: certificate signed by unknown authority, а если файл не существует — с ошибкой unable to read CA file. При подключении по IP-адресу вместо имени хоста возникает ошибка x509: cannot validate certificate for <IP> because it doesn't contain any IP SANs.

Создание схемы с помощью миграции

Создайте файлы миграции:
Заполните миграцию up:
migrations/000001_create_todos.up.sql
А также миграция down, которая её откатывает:
migrations/000001_create_todos.down.sql
Примените миграцию:
migrate записывает текущую версию в таблицу schema_migrations и при последующих запусках применяет только более новые миграции. Закоммитьте каталог migrations вместе с кодом.
Всегда запускайте migrate через прямое соединение (порт 5432). На время работы он захватывает рекомендательную блокировку (advisory lock) уровня сеанса, чтобы два развертывания не могли выполнять миграции одновременно. При работе через PgBouncer захват и освобождение блокировки могут произойти в разных соединениях Postgres. В этом случае блокировка так и остаётся захваченной, а последующие запуски migrate зависают или завершаются ошибкой timeout: can't acquire database lock.

Выполнение запросов к базе данных

Создайте файл db.go. Он создаёт пул из DATABASE_URL и при запуске проверяет соединение, запрашивая у Postgres версию TLS, используемую в текущем сеансе:
db.go
Создайте файл todos.go с HTTP-обработчиками. Каждый обработчик выполняет один параметризованный запрос через пул, а pgx.CollectRows и pgx.CollectExactlyOneRow считывают строки в структуру Todo, сопоставляя поля по именам столбцов:
todos.go
Передача значений в виде аргументов ($1) вместо их подстановки в строку SQL защищает от SQL-инъекций: pgx отправляет их отдельно от текста запроса. Отдельно проверять ошибку, возвращаемую pool.Query, не нужно: её вернут pgx.CollectRows и pgx.CollectExactlyOneRow. Создайте файл main.go. Он создаёт один пул на весь процесс, обслуживает маршруты на порту 3114 и закрывает пул по нажатию Ctrl+C. В существующем приложении вместо этого вызовите newPool один раз при запуске, а registerTodoRoutes — для вашего http.ServeMux:
main.go

Запуск и проверка

Добавьте оставшиеся зависимости pgxpool в go.mod и go.sum, после чего запустите сервер:
Значение TLSv1.3 берётся из pg_stat_ssl на сервере, а значит, сеанс действительно зашифрован. Если указать в sslrootcert любой другой сертификат, сервер не запустится и запишет в журнал x509: certificate signed by unknown authority — это доказывает, что сертификат действительно проверяется. Во втором терминале создайте две задачи, отметьте первую как выполненную и удалите вторую:
Запрос PATCH возвращает обновлённую строку:
Откройте http://localhost:3114/todos в браузере, чтобы просмотреть список оставшихся задач. pgx возвращает значения timestamptz в вашем локальном часовом поясе. Ответ будет выглядеть так:
Остановите сервер сочетанием клавиш Ctrl+C. В журнал будет выведено сообщение Shutting down, после чего сервер закроет пул. Чтобы просмотреть данные в консоли, откройте консоль SQL на левой боковой панели вашего сервиса, разверните guide_go, затем public и выберите таблицу todos. В соседней таблице schema_migrations хранится версия миграции. В таблице todos осталась одна строка:

Использование PgBouncer

Чтобы направлять запросы приложения через встроенный PgBouncer, выберите via PgBouncer в модальном окне Подключение, измените порт в DATABASE_URL на 6432 и добавьте default_query_exec_mode=cache_describe. Для MIGRATE_URL оставьте порт 5432:
pgx считывает default_query_exec_mode из URL и не передаёт его на сервер. Изменять код не требуется, подходит и тот же CA‑сертификат. Теперь в журнале запуска отображается TLS: none, поскольку pg_stat_ssl сообщает о собственном соединении PgBouncer с Postgres. Соединение вашего приложения с PgBouncer по-прежнему проверяется: с неверным CA‑сертификатом подключение точно так же завершится ошибкой.
Не используйте режим запросов pgx по умолчанию при работе через PgBouncerПо умолчанию pgx подготавливает каждый запрос как именованный оператор (один раз на соединение) и повторно использует полученные типы столбцов. PgBouncer подготавливает такие операторы на том соединении с Postgres, через которое выполняется запрос, и использует их совместно для разных клиентов. После миграции, которая меняет тип столбца или добавляет столбец в таблицу, запрашиваемую через SELECT *, запросы через PgBouncer могут по-прежнему завершаться ошибкой cached plan must not change result type даже после перезапуска приложения, а в pgx 5.9 и новее — возвращать неверные значения без всякой ошибки. Например, если тип столбца изменён с bigint на double precision, вместо 42.5 возвращаются сырые биты значения float64, то есть 4631178160564600832.cache_describe кэширует только типы параметров и столбцов, выполняет каждый запрос как неименованный оператор за один круговой обмен и декодирует строки по типам столбцов, которые сервер возвращает с каждым результатом, поэтому эта проблема его не затрагивает. Режим exec тоже подходит. Не используйте describe_exec через PgBouncer: он разбивает каждый запрос на два круговых обмена, которые PgBouncer может направить в разные соединения с Postgres. Указывать statement_cache_capacity=0 не нужно.
PgBouncer работает в режиме пулинга транзакций, поэтому каждая транзакция может выполняться в отдельном соединении с Postgres:
  • Транзакции через pool.Begin или pgx.BeginFunc работают как обычно.
  • Настройки сеанса, заданные с помощью SET, не сохраняются за вашим соединением и могут повлиять на других клиентов. Вместо этого используйте SET LOCAL внутри транзакции. Кроме того, PgBouncer отклоняет search_path в URL подключения с ошибкой unsupported startup parameter: search_path, поэтому указывайте имена таблиц вместе со схемой.
  • PREPARE и EXECUTE на уровне SQL, LISTEN и сеансовые рекомендательные блокировки не работают.

Дальнейшие шаги

Последнее изменение 14 октября 2026 г.