Содержание урока · 9 разделов
Прежде чем что-то подключать, соберём площадку. Нужен свежий Go — проверьте go version, pgx v5 живёт на актуальных релизах тулчейна, — и работающий PostgreSQL. Ставить базу в систему не обязательно: контейнер Docker поднимает Postgres одной командой, вместе с пользователем, паролем и готовой базой внутри. Дальше — стандартный ритуал любого модуля Go: инициализация модуля и скачивание зависимости. Модуль называется github.com/jackc/pgx/v5, и суффикс v5 здесь обязателен: без него go get скачает четвёртую мажорную версию, у которой другой API, и код этого курса не соберётся. Вместе с основным пакетом подтянутся внутренние зависимости — pgconn и pgtype из того же модуля и пул puddle: они приезжают автоматически, отдельно ставить их не нужно, а в go.mod появится одна строка с версией. Строку require вместе с контрольными суммами go.sum коммитят в репозиторий: сборка на CI воспроизведёт ровно ту же зависимость, что и на ноутбуке. Обновления внутри пятой версии — обычный go get -u, который почти никогда не ломает код: мажорная граница v5 обещает совместимость внутри себя. Строку require вместе с контрольными суммами go.sum коммитят в репозиторий: сборка на CI воспроизведёт ровно ту же зависимость, что и на ноутбуке. Обновления внутри пятой версии — обычный go get -u, который почти никогда не ломает код: мажорная граница v5 обещает совместимость внутри себя. Ещё один плюс: pgx написан на чистом Go без CGO, поэтому кросс-компиляция под любую платформу работает без сюрпризов — приятный бонус для сборки в контейнерах. На этом подготовка закончена: база крутится в контейнере, модуль скачан, остался код.
Поднимаем базу и ставим модуль
GOdocker run --name pgx-course \
-e POSTGRES_USER=app \
-e POSTGRES_PASSWORD=secret \
-e POSTGRES_DB=app \
-p 5432:5432 \
-d postgres:16
go mod init pgx-demo
go get github.com/jackc/pgx/v5Первая команда поднимает контейнер Postgres 16 с пользователем app, паролем secret и базой app на стандартном порту 5432; флаг -d отправляет его в фон. Затем — инициализация модуля и установка pgx v5. Проверить живость базы можно через docker ps, а остановить и удалить — docker stop pgx-course и docker rm pgx-course; данные контейнера при этом исчезнут, что для учебной базы нормально.
pgx.Connect: первое подключение к Postgres из Go#
Главный вызов урока — pgx.Connect(ctx, connString). Первый аргумент — контекст: в v5 он обязателен почти в каждом методе, и именно по контексту потом отменяются зависшие подключения и запросы. Второй — строка подключения, DSN: пользователь, пароль, хост, порт и имя базы в одном значении; в следующем уроке разберём её по косточкам. Импорт нужен один — github.com/jackc/pgx/v5: имя пакета после него остаётся pgx, и никакие псевдонимы в коде не требуются. Connect одновременно открывает TCP-соединение и проводит хендшейк протокола — аутентификацию и согласование параметров, — поэтому возвращает он уже готовое к работе соединение pgx.Conn, а не заготовку для будущих вызовов. Ошибка возвращается вторым значением, как принято в Go: паник здесь нет и не должно быть, даже если база недоступна. Важно понимать масштаб: pgx.Conn — это ровно одно соединение с базой. Для CLI-утилит, миграций и разовых скриптов этого достаточно; сервису, который обслуживает HTTP-запросы из разных горутин, понадобится пул — но это тема четвёртого урока, а первые запросы прекрасно работают и на одиночном соединении. Единственное ограничение Conn — время жизни: соединение закрывается вместе с программой или при вызове Close, и держать его открытым «на всякий случай» — плохая привычка: сервер считает каждую сессию.
Полная программа: подключение, Ping, Close
GOpackage main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v5"
)
func main() {
ctx := context.Background()
conn, err := pgx.Connect(ctx, "postgres://app:secret@localhost:5432/app")
if err != nil {
log.Fatal(err)
}
defer conn.Close(ctx)
if err = conn.Ping(ctx); err != nil {
log.Fatal(err)
}
fmt.Println("Postgres отвечает")
}Полный каркас: контекст, подключение, отложенное закрытие, пинг. pgx.Connect вернёт ошибку, если база недоступна или не приняла логин и пароль; conn.Close(ctx) освобождает соединение при выходе из main — в v5 Close принимает контекст; conn.Ping отправляет серверу тривиальный запрос и убеждается, что тот живой. Ошибку парсинга DSN и отказ сети видно в одном месте — в err от Connect. Запускайте go run main.go — на экране должна появиться строка «Postgres отвечает».
Что происходит на проводе за эти три вызова, полезно видеть по шагам. Сначала Go открывает TCP-соединение на порт 5432 — здесь рождаются ошибки network-слоя вроде connection refused, когда на порту никто не слушает. Затем клиент отправляет StartupMessage: имя пользователя, имя базы и желаемые параметры протокола. Сервер выбирает метод аутентификации и либо принимает логин, либо отвечает отказом, из которого растут ошибки password authentication failed. Устойчивый SCRAM-SHA-256 не передаёт пароль по сети в открытом виде: клиент доказывает знание секрета обменом вызов-ответ, а сервер уже решает, принимать ли его. После успешного хендшейка сервер объявляет параметры сессии и переходит в состояние готовности — только теперь pgx.Connect возвращает управление вашему коду. Поэтому в тексте ошибки pgx всегда есть контекст: к какому хосту, каким пользователем и на каком шаге всё сломалось — читать её стоит слева направо, а не пугаться длины. Сам хендшейк на локальной машине стоит миллисекунды, но через сеть и вместе с TLS-рукопожатием — уже десятки: это вторая причина, по которой сервис живёт на пуле из четвёртого урока, а не открывает соединения на каждый запрос. Для любопытных: хендшейк заканчивается сообщением ReadyForQuery — с этого момента сервер ждёт SQL, и дальнейший разговор идёт либо расширенным протоколом с плейсхолдерами и типизированными аргументами, либо простым, где стейтмент уходит одной строкой текста. pgx выбирает режим сам: расширенный — для запросов с аргументами, простой — для пачки команд без параметров; это различие ещё выстрелит в уроке про Exec с несколькими стейтментами. Для любопытных: хендшейк заканчивается сообщением ReadyForQuery — с этого момента сервер ждёт SQL, и дальнейший разговор идёт либо расширенным протоколом с плейсхолдерами и типизированными аргументами, либо простым, где стейтмент уходит одной строкой текста. pgx выбирает режим сам: расширенный — для запросов с аргументами, простой — для пачки команд без параметров; это различие ещё выстрелит в уроке про Exec с несколькими стейтментами.
Ping: зачем проверять соединение отдельно#
После хендшейка соединение уже живое — зачем тогда Ping? Причины две. Первая — диагностика: Ping отправляет серверу простейший запрос, так что если между приложением и базой встал прокси, залип DNS или база ушла в режим восстановления, вы увидите это отдельной ошибкой с понятным текстом, а не сюрпризом в первом же пользовательском запросе. Вторая — health-check: в Kubernetes readiness-проба вызывает ровно такую проверку, и под без живой базы не получает трафик от балансировщика. Пинг дешёвый, но не бесплатный: он занимает соединение и делает рейс до сервера, поэтому в readiness-пробе его вызывают с разумным интервалом, а не в каждом обработчике. И про слово «живость»: Ping не проверяет готовность схемы или наличие данных — только то, что сервер отвечает на протокольном уровне. Проверки «миграции накатаны и таблица на месте» пишутся отдельными запросами и к пингу отношения не имеют. И про слово «живость»: Ping не проверяет готовность схемы или наличие данных — только то, что сервер отвечает на протокольном уровне. Проверки «миграции накатаны и таблица на месте» пишутся отдельными запросами и к пингу отношения не имеют. Для разовых проверок у pgx есть шорткат pgx.Ping(ctx, connString): он открывает соединение, пингует и закрывает его одним вызовом — удобно для CLI-скриптов и проверок окружения в CI. Таймаут, в пределах которого всё это обязано уложиться, задаётся не параметром, а контекстом: context.WithTimeout обрежет зависший вызов — подробности в пятнадцатом уроке про таймауты.
Если Postgres не поднят, Connect вернёт ошибку вида failed to connect to host=127.0.0.1 user=app database=app: dial tcp 127.0.0.1:5432: connect: connection refused. Читается она слева направо: к кому подключались и на каком шаге сломались. Причина почти всегда прозаична: контейнер не запущен (docker ps пуст), порт не проброшен флагом -p, или программа выполняется в другом контейнере, где localhost — это не хост-машина. Порядок диагностики: docker ps, затем docker logs pgx-course, и только потом правка кода.
Четыре типовые ошибки первого подключения#
- connection refused: база не запущена, порт не проброшен или указан неверный хост
- password authentication failed for user: пароль в DSN не совпал с POSTGRES_PASSWORD контейнера
- database app does not exist: имя базы в DSN отличается от POSTGRES_DB
- TLS-ошибки: сервер требует шифрование, а DSN просит sslmode=disable — вся тема третьего урока
- sorry, too many clients already: лимит соединений сервера исчерпан — вернёмся к нему в уроке про пул
Одна строка вместо программы: pgx.Ping
GOerr := pgx.Ping(ctx, "postgres://app:secret@localhost:5432/app")
if err != nil {
log.Fatalf("Postgres недоступен: %v", err)
}
fmt.Println("с базой всё в порядке")pgx.Ping — шорткат для проверки окружения: открыть соединение, отправить пинг, закрыть. Состояния он не возвращает — только ошибку. Такой вызов часто стоит в скриптах CI перед миграциями: сначала убеждаемся, что база вообще доступна, и лишь потом делаем работу. В сервисах с пулом эта функция не нужна — там пинг живёт в Ping от пула.
Вызовы урока одной таблицей#
| Вызов | Что делает | Где уместен |
|---|---|---|
| pgx.Connect | открывает соединение и проводит хендшейк протокола | старте CLI-утилиты, разового скрипта |
| conn.Ping | проверяет живость уже открытого соединения | старте сервиса и readiness-пробе |
| pgx.Ping | открывает, пингует и закрывает соединение одной строкой | проверке окружения в CI и скриптах |
| conn.Close | корректно завершает сессию на сервере | defer сразу после успешного Connect |
Одно соединение против пула: что выбрать сейчас#
pgx.Connect и pgxpool.New принимают одну и ту же строку подключения и возвращают совместимые API: у обоих есть Exec, Query, QueryRow и Begin. Разница — в модели владения. Одиночный Conn — один ресурс, который нельзя дёргать из разных горутин одновременно: конкурентный запрос из второго обработчика упрётся в уже занятое соединение и дождётся своей очереди или упадёт. Пул держит несколько соединений и выдаёт их горутинам по мере необходимости, а при нехватке выстраивает в очередь внутри себя. Правило простое: скрипты и утилиты — Connect, долгоживущие сервисы — pool. Учиться при этом лучше на одиночном соединении: первые уроки курса работают с Conn именно потому, что из него видно каждую ошибку без шума пула, а переезд на пул позже — замена одной строки и одного импорта, вы сделаете его в четвёртом уроке за минуту. Пулам не чужды и тесты: в интеграционных кейсах проще поднять один локальный пул и делить его между тестами, чем открывать соединение в каждом — об этом двадцать третий урок.
Каждое соединение — ресурс на обеих сторонах: в приложении сокет, в Postgres — процесс с памятью. Программа, которая в цикле открывает соединения без закрытия, упрётся в лимит базы и начнёт ловить too many clients. Дисциплина одна: сразу после успешного Connect — defer conn.Close(ctx), сразу после создания пула — defer pool.Close().
Песочница сайта и локальный запуск#
Оговорка о формате курса, честная на весь двадцать пять уроков: код здесь настоящий, с сетью и базой, поэтому в браузерной песочнице он не исполняется — сайт умеет запускать только программы на чистом Go без внешних соединений, и в песочнице каждого урока откроется тематическая программа, иллюстрирующая идею урока. Для рукотворной практики нужны локальный Go и Postgres в Docker — обе команды уже есть в этом уроке. Зато ошибки подключения стоит коллекционировать специально: они приходят в проде в первые же сутки, и умение за десять секунд отличить connection refused от аутентификации экономит часы дежурств. Ритм прохождения простой: сначала разбор и интерактивы прямо на странице — они работают без базы и засчитывают XP, затем копирование кода в терминал и эксперименты с ошибками из списка перед глазами.
Проверка: поведение Connect
Что вернёт pgx.Connect, если Postgres не запущен на указанном хосте?
Проверка: зачем Ping
Зачем после успешного Connect вызывать Ping?
Что напечатает программа?
Прочитайте код и выберите, что он выведет:
conn, err := pgx.Connect(ctx,
"postgres://app:wrongpass@localhost:5432/app")
if err != nil {
fmt.Println("ошибка:", err)
return
}
defer conn.Close(ctx)
fmt.Println("подключено")Соберите подключение
Восстановите каркас: контекст, подключение, закрытие и проверка связи.
Соберите main.go из строк
Расставьте строки в порядке исполнения: подключение, закрытие, пинг, вывод.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: первое подключение
Соединение открыто и проверено — дальше по курсу: в третьем уроке разберём строку подключения по косточкам — параметры sslmode, переменные окружения и сборка DSN без секретов в коде. В четвёртом пересаживаемся с одиночного Conn на пул соединений и настраиваем лимиты под нагрузку. В пятом наконец отправим настоящие INSERT и SELECT. Если контексты, ошибки и defer пока в новинку — подтяните их в базовом курсе Go, там этим темам посвящены отдельные уроки: без них половина магии pgx останется непонятой. Пятнадцать-восемнадцать минут на урок — и к концу недели у вас работает связка Go плюс Postgres.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Модуль ставится командой go get github.com/jackc/pgx/v5 — суффикс версии обязателен.
- pgx.Connect(ctx, dsn) открывает соединение и проводит хендшейк; возвращает *pgx.Conn и error.
- conn.Ping(ctx) проверяет, что сервер отвечает на запросы; тот же вызов — основа readiness-проб.
- defer conn.Close(ctx) ставится сразу после успешного подключения — соединения не должны утекать.
- connection refused — база не запущена или порт не проброшен; password authentication failed — неверные креденшалы.
- Скриптам — одиночное соединение, долгоживущим сервисам — пул pgxpool из четвёртого урока.
- Код курса запускается локально с Go и Postgres в Docker; песочница показывает программу на чистом Go.
Домашнее задание
Поднимите контейнер командой из урока, соберите main.go и добейтесь строки «Postgres отвечает». Затем поэкспериментируйте с ошибками: поменяйте пароль в DSN на неверный и сравните текст ошибки с тем, что напечатает программа при остановленном контейнере. Найдите в каждом сообщении pgx хост, пользователя и базу — научиться читать эти строки за минуту стоит любой дальнейшей отладки.
Личные заметкиПусто
Вопросы и ответы по уроку
Почему pgx.Connect, а не sql.Open?
sql.Open из database/sql ленив: он не подключается, пока не понадобится первый запрос, и часть ошибок соединения откладывается на потом. pgx.Connect делает всю работу сразу и возвращает готовое соединение или честную ошибку — для CLI и первых шагов это удобнее. Для сервисов с нагрузкой выбор всё равно за pgxpool — одиночное соединение здесь только учебная ступень, а тот же fail-fast для пула даёт пинг при старте.
Нужно ли закрывать соединение в конце main?
На практике процесс всё равно умрёт вместе с сокетами, но закрывать — правильная дисциплина: defer conn.Close(ctx) гарантирует корректное завершение сессии на сервере, а в длинных программах без Close соединения утекут и упрутся в лимит базы. Сомневаетесь — проверьте: pg_stat_activity покажет зависшие сессии от программ, забывших Close.
Можно ли подключаться без пароля?
Можно, если сервер настроен на trust- или peer-аутентификацию — так бывает в локальной разработке и CI. В DSN тогда достаточно user и dbname. Но для прод-контура аутентификация по паролю или сертификату обязательна, как и sslmode — об этом следующий урок. Сомневаетесь, какой метод настроен на сервере, — ответ в pg_hba.conf: там для каждого хоста прописан свой способ. И даже в доверенной сети disable стоит ставить осознанно, а не по умолчанию.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
MongoDB в Go: официальный драйвер mongo-driver v2
Курс MongoDB для Go: подключение, bson-теги, CRUD, фильтры, агрегации, индексы, транзакции и Change Streams. 25 уроков по mongo-driver v2.
- Подключаться к MongoDB и настраивать клиента
- Моделировать документы структурами и bson-тегами
- Делать CRUD, сложные фильтры и проекции
- Строить агрегации $match/$group/$lookup