Содержание урока · 10 разделов
- Что такое pgx: нативный драйвер PostgreSQL для Go
- Что даёт нативный pgx одним списком
- database/sql, lib/pq и pgx: кто быстрее и почему
- Три способа работать с Postgres в Go
- Когда database/sql всё ещё нужен
- Из чего состоит pgx: пакеты внутри модуля
- Как устроен курс и что запускать
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Сценарий, с которого начинается половина Go-проектов: нужен бэкенд, которому где-то хранить данные. Выбор базы обычно короткий — PostgreSQL: транзакции, надёжность, богатый SQL и хостинг на любой платформе от ноутбука до облака. Но за ним сразу встаёт второй вопрос, который новички часто пропускают: чем читать и писать в эту базу из Go. Варианта три. Первый — интерфейс database/sql из стандартной библиотеки плюс любой совместимый драйвер: так написаны статьи десятилетней давности и половина старого кода. Второй — pgx через тот же database/sql, за счёт адаптера stdlib. Третий — нативный API pgx напрямую, минуя стандартный интерфейс. Разница между тремя путями не вкусовая: скорость запросов может отличаться в разы, а часть возможностей PostgreSQL — быстрая массовая загрузка COPY, уведомления LISTEN/NOTIFY, нативные типы вроде UUID и JSONB — доступна только на нативном пути. От выбора зависит и обработка ошибок: нативный pgx отдаёт pgconn.PgError с кодами SQLSTATE, через общий интерфейс та же информация теряется в обёртках — и это не экспериментальный путь, а тот, которым идут новые сервисы. В этом уроке пройдём по всем трём вариантам и соберём критерии выбора, а весь остальной курс построен на третьем — нативном — подходе.
Что такое pgx: нативный драйвер PostgreSQL для Go#
pgx — библиотека для работы с PostgreSQL из Go, которую пишет Jack Christensen с десятками контрибьюторов; проект ведёт историю с 2013 года, а пятая мажорная версия, v5, вышла в 2022-м и живёт по пути импорта github.com/jackc/pgx/v5. Пятая версия переписана с прицелом на обобщения Go 1.18: CollectRows и RowToStructByName появились именно в ней, четвёртая поддерживается, но новые проекты стартуют с v5. Слово «нативный» здесь не маркетинг: pgx сам реализует протокол, по которому клиент общается с сервером Postgres — так называемый wire protocol. Драйвер не прячет SQL за конструкторами запросов и не переводит типы через общий знаменатель: вы пишете настоящий SQL, а типы Postgres — UUID, массивы, JSONB, временные метки с таймзоной — приходят в Go такими, какие они есть, через собственный пакет pgtype. Из нативности растёт всё остальное: пакетная отправка запросов одним рейсом по сети (Batch), сверхбыстрая загрузка данных через COPY FROM, подписка на события базы LISTEN/NOTIFY и пул соединений pgxpool, встроенный прямо в библиотеку. По количеству проектов, звёзд и ответов на Stack Overflow это самый используемый драйвер PostgreSQL в экосистеме golang: поверх него работают GORM, sqlc и значительная часть самописных бэкендов. Из практичных мелочей v5: драйвер сам кэширует prepared statements — запрос с плейсхолдерами после первого исполнения уезжает на сервер уже скомпилированным, и повторные вызовы экономят и трафик, и работу планировщика; в database/sql этим приходится управляться вручную, через Prepare и Stmt.
Что даёт нативный pgx одним списком#
- Собственная реализация протокола Postgres: без промежуточного слоя database/sql и его сканера типов
- pgtype: нативные типы UUID, JSONB, массивов, NUMERIC и timestamptz без ручных конвертаций
- pgxpool: пул соединений с настройкой лимитов и жизненного цикла коннектов
- Batch и COPY FROM: сотни и миллионы строк без построчных поездок по сети
- LISTEN/NOTIFY: подписка на события базы без опроса таблицы
- CollectRows и RowToStructByName: срез структур одной строкой кода на обобщениях Go 1.18+
Первый взгляд: pgx в шести осмысленных строках
GOpackage main
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v5/pgxpool"
)
func main() {
ctx := context.Background()
pool, err := pgxpool.New(ctx, "postgres://app:secret@localhost:5432/app")
if err != nil {
log.Fatal(err)
}
defer pool.Close()
var name string
err = pool.QueryRow(ctx, "SELECT name FROM users WHERE id = $1", 42).Scan(&name)
if err != nil {
log.Fatal(err)
}
fmt.Println(name)
}Пока не разбирайте по слогам — это телепорт вперёд. pgxpool.New создаёт пул соединений из строки подключения, pool.Ping проверяет, что база отвечает, QueryRow выполняет запрос с плейсхолдером $1, а Scan копирует результат в переменную. Каждой строке — свой урок: установка и подключение во втором, строка DSN в третьем, пул в четвёртом, запросы в пятом.
database/sql, lib/pq и pgx: кто быстрее и почему#
Вопрос «кто быстрее» раскладывается на два слоя. Первый слой — интерфейс: database/sql из стандартной библиотеки описывает, как выглядит работа с любой SQL-базой в Go. Интерфейс удобен, но цена абстракции реальна: каждое значение проходит через собственный сканер типов, а часть протокольных возможностей базы через общий знаменатель просто не торчит. Второй слой — драйвер. lib/pq — чистый Go-драйвер Postgres, который годами был стандартом де-факто, но сейчас объявлен в режиме поддержки: критические баги правят, новых возможностей не добавляют. По бенчмаркам из репозитория pgx нативный pgx обгоняет связку database/sql + lib/pq в разы на простых запросах: выигрыш идёт и от отсутствия промежуточного слоя, и от умения отправлять несколько команд одним рейсом по сети. Механика простая: сканер database/sql прогоняет каждую колонку через отражения и общий знаменатель типов, а нативный pgx кладёт байты протокола прямо в структуры Go и умеет пайплайнинг — отправлять следующий запрос, не дожидаясь ответа на предыдущий. Причём pgx быстрее даже в паре с database/sql через адаптер stdlib — драйверная часть выигрывает сама по себе. Для типового API это означает снижение p95 по базе заметнее, чем от любого тюнинга кода приложения: накладные расходы драйвера уходят почти в ноль, а узким местом честно становится сам SQL.
Три способа работать с Postgres в Go#
| Способ | Под капотом | Что получаете | Чего не хватает |
|---|---|---|---|
| database/sql + lib/pq | абстрактный интерфейс и драйвер в режиме поддержки | максимальную совместимость со старым кодом | скорости, нативных типов, Batch и COPY |
| database/sql + pgx/stdlib | интерфейс database/sql над нативным pgx | совместимый код на скорости драйвера pgx | нативного API: Batch, COPY, LISTEN/NOTIFY недоступны |
| нативный pgx v5 | собственный протокол Postgres и pgxpool | полную скорость, типы pgtype, Batch, COPY, NOTIFY | готового интерфейса database/sql — его заменяет pgx |
Postgres-драйвер GORM (gorm.io/driver/postgres) под капотом использует pgx через адаптер stdlib, а sqlc умеет генерировать код сразу под pgx/v5. Понимание драйвера объясняет поведение надстроек: почему ORM иногда тормозит на массовых вставках и что именно компилируется из сгенерированного кода. Подробный курс по ORM — на странице GORM; sqlc вернёмся в девятом уроке этого курса.
lib/pq работает и никуда не денется: серверы на нём живут годами. Но авторы проекта прямо объявили режим поддержки — исправления без развития. Старые туториалы продолжают учить lib/pq по инерции; для нового кода экосистема советует pgx, а миграция со старого обычно сводится к смене импорта и адаптеру stdlib.
Когда database/sql всё ещё нужен#
Честный ответ: реже, чем принято думать. Аргумент за абстракцию «мы потом сменим PostgreSQL на MySQL» на практике реализуется в единицах проектов из тысяч: смена СУБД тянет переписывание SQL, миграций и тестов, и последним вопросом там встаёт интерфейс драйвера, а не первым. Реальные причины бывают другие: вы пишете библиотеку, которую будут подключать к чужим сервисам, и не хотите тащить жёсткую зависимость от конкретной базы; у команды горы кода на *sql.DB и ORM, который ожидает именно этот тип; или продукт правда поставляется под разные СУБД. Замечу и обратное: SQL, который обещает абстракция, тоже не переносится бесплатно — у MySQL и Postgres разные диалекты, типы и оконные функции, поэтому «писать на общем знаменателе» означает писать на наименее мощном подмножестве обоих. Плата за абстракцию видна не в бенчмарках, а в исчезающих возможностях: LISTEN/NOTIFY и COPY через database/sql недоступны в принципе, а половина нативных типов превращается в []byte с ручной конвертацией. Моё спорное мнение, которое я проговариваю на каждом архитектурном ревью: в прикладных сервисах абстракция «на всякий случай» — чистый убыток, я пишу на нативном pgx и не выношу интерфейс хранилища ради гипотетической смены базы. Условие, при котором я прав: сервис один, СУБД выбрана осознанно, и менять её никто не собирается. Если условие не выполняется — девятый урок покажет, как жить через pgx/stdlib с минимальными потерями.
Кейс, который помогает почувствовать разницу. Команда сервисов доставки пять лет писала через lib/pq и решилась на переезд, потому что хотела COPY для ночных выгрузок и нативные UUID. Сам переезд занял вечер: импорт сменился, адаптер stdlib прикрыл старый слой, SQL не трогали вовсе. Настоящая работа началась после: NULL-колонки, которые lib/pq пропускала в sql.NullString, потребовали пересмотра моделей; NUMERIC, который раньше приходил строкой, теперь отдаётся нативным типом; а бенчмарк на типовой выборке показал ускорение вставок втрое — за счёт Batch, который раньше приходилось эмулировать конкатенацией стейтментов. Отдельная приятность: часть багов ушла сама — pgtype проверяет диапазоны при сканировании, а не молча кладёт переполненное число, и UUID перестал быть строкой, которую легко перепутать. Мораль: сменить драйвер легко, снять с него сливки — только на нативном API.
Команда слышит «pgx быстрее», ставит адаптер stdlib и не видит ни ускорения, ни новых фич — потому что скорость и Batch с COPY живут в нативном API, а через интерфейс database/sql торчит только быстрый драйвер под старым слоем. Если код уже на *sql.DB — адаптер даёт лёгкий выигрыш и совместимость. Если пишете с нуля — берите нативный API сразу.
Из чего состоит pgx: пакеты внутри модуля#
Модуль github.com/jackc/pgx/v5 распадается на несколько пакетов, и по ходу курса вы встретите их все. Пакет pgx — главный: типы Conn, Rows, Tx и весь высокоуровневый API запросов. Пакет pgconn живёт ниже: низкоуровневое соединение, протокол и ошибки — тип pgconn.PgError с кодами SQLSTATE приходит именно оттуда, и с ним вы будете разбирать конфликты уникальности. Внутри pgconn живут и CommandTag — отчёт Exec о затронутых строках, — и низкоуровневые события соединения. Пакет pgtype — типы данных Postgres: UUID, NUMERIC, массивы, JSONB, их сериализация и сканирование. Пул pgxpool вынесен в одноимённый подпакет и построен над универсальным пулом puddle — отдельной библиотекой того же автора. Знать границы полезно не для эрудиции: когда компилятор не находит тип или ошибка приходит не та, что вы ждали, первый вопрос — из какого пакета вы импортировали. В v5 граница смягчена алиасами: pgx.ErrNoRows — то же самое, что pgconn.ErrNoRows, и оба варианта встречаются в документации и чужом коде.
Меню импорта: каркас типового файла
GOimport (
"context"
"github.com/jackc/pgx/v5" // Conn, Rows, Tx: главный API
"github.com/jackc/pgx/v5/pgconn" // протокол, PgError, CommandTag
"github.com/jackc/pgx/v5/pgtype" // UUID, JSONB, массивы, NUMERIC
"github.com/jackc/pgx/v5/pgxpool" // пул соединений
)Меню модуля: в одном файле редко нужны все четыре пакета сразу, но такой импорт-каркас повторяется в половине сервисов на pgx. Пакет pgx присутствует почти всегда, pgxpool — в долгоживущих сервисах, pgtype — там, где нативные типы, pgconn — где разбор SQLSTATE-ошибок. По курсу каждый пакет получит свой урок.
Проверка: скорость pgx
За счёт чего нативный pgx обгоняет связку database/sql + lib/pq?
Проверка: статус lib/pq
Какой сейчас статус lib/pq?
Что напечатает программа?
Прочитайте код и выберите, что он выведет:
var name string
row := pool.QueryRow(ctx,
"SELECT name, email FROM users WHERE id = $1", 42)
err := row.Scan(&name)
fmt.Println(name, err)Соберите подключение к Postgres
Восстановите первые строки работы с пулом: конструктор, проверка связи и закрытие.
Соберите каркас программы
Расставьте строки в порядке исполнения: контекст, пул, обработка ошибки, закрытие, пинг.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: словарь pgx
Как устроен курс и что запускать#
Курс из двадцати пяти уроков ведёт от первого подключения до итогового сервиса заметок со сканированием структур, транзакциями, Batch, COPY и LISTEN/NOTIFY. Формат честный: песочница сайта исполняет только чистый Go без сети, поэтому pgx-код в уроках не запускается в браузере — в песочнице каждого урока откроется тематическая программа на чистом Go, а для практики нужен локальный Go и PostgreSQL, проще всего в Docker одной командой из домашнего задания. Интерактивы — тесты, «что выведет», заполните пропуски и сборка программы — работают прямо на странице и засчитывают XP, так что материал можно проходить откуда угодно, но код всё равно скопируйте в терминал: соединение с настоящей базой не заменит никакая эмуляция. Темп спокойный: урок занимает пятнадцать-двадцать пять минут, и за месяц ровного чтения курс проходится целиком. Дальше по плану: во втором уроке ставим модуль и подключаемся к Postgres, в четвёртом настраиваем пул под прод-нагрузку. Если Go пока на уровне «читал», сначала пройдите базовый курс: структуры, срезы и ошибки из него понадобятся уже на пятом уроке.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- pgx — нативный драйвер PostgreSQL для Go: собственный wire-протокол без промежуточного слоя database/sql.
- lib/pq объявлен в режиме поддержки: работает, но не развивается; для новых проектов экосистема рекомендует pgx.
- Нативный pgx быстрее связки database/sql + lib/pq и открывает pgtype, Batch, COPY и LISTEN/NOTIFY.
- database/sql оправдан для библиотек под разные СУБД и кода, уже написанного под *sql.DB, — для этого есть адаптер pgx/stdlib.
- GORM и sqlc строятся поверх pgx: понимание драйвера объясняет поведение надстроек.
- Модуль v5 состоит из пакетов pgx, pgconn, pgtype и pgxpool — знать их границы полезно при разборе ошибок.
- Код курса запускается локально: нужен Go и Postgres в Docker; песочница показывает программу на чистом Go.
Домашнее задание
Разверните Postgres одной командой: docker 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, вставьте код подключения и добейтесь строки в выводе вместо ошибки соединения. Потом остановите контейнер и запустите программу снова — зафиксируйте текст ошибки соединения: во втором уроке он станет главным героем.
Личные заметкиПусто
Вопросы и ответы по уроку
Что такое pgx простыми словами?
Библиотека, через которую Go-программа общается с PostgreSQL: открывает соединения, отправляет SQL, принимает строки и ошибки. Нативный протокол делает её самым быстрым драйвером Postgres в экосистеме Go, а pgxpool — стандартным способом держать соединения под нагрузкой.
Нужен ли database/sql, если ставлю pgx?
Нет: нативный API pgx самодостаточен, и в новых сервисах я ставлю его по умолчанию. database/sql нужен, когда вы пишете библиотеку под разные СУБД или подключаетесь к чужому коду на *sql.DB — тогда берите адаптер pgx/stdlib, о котором расскажет девятый урок.
Можно ли проходить курс без установленного PostgreSQL?
Да: весь код разбирается построчно прямо на странице, интерактивы засчитываются без базы. Для практики руками хватит Docker — команда из домашнего задания поднимает Postgres одной строкой, а запуск программы — go run main.go.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
MongoDB в Go: официальный драйвер mongo-driver v2
Курс MongoDB для Go: подключение, bson-теги, CRUD, фильтры, агрегации, индексы, транзакции и Change Streams. 25 уроков по mongo-driver v2.
- Подключаться к MongoDB и настраивать клиента
- Моделировать документы структурами и bson-тегами
- Делать CRUD, сложные фильтры и проекции
- Строить агрегации $match/$group/$lookup