Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 1 из 25 ~16 мин +20 XP

Знакомство: зачем Go-проекту pgx

Зачем нужен pgx, когда в стандартной библиотеке Go уже есть database/sql, а на драйвере lib/pq выросли тысячи туториалов? Потому что pgx разговаривает с PostgreSQL по нативному протоколу: без промежуточного слоя абстракции, с собственными типами и встроенным пулом соединений — именно поэтому он быстрее и богаче возможностями. В этом уроке разберём, как pgx устроен, чем отличается от database/sql и lib/pq и когда абстрактный интерфейс всё ещё оправдан.

Содержание урока · 10 разделов

Сценарий, с которого начинается половина 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 в шести осмысленных строках

GO
package 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

Когда 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: пакеты внутри модуля#

Модуль 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, и оба варианта встречаются в документации и чужом коде.

Меню импорта: каркас типового файла

GO
import (
        "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

Восстановите первые строки работы с пулом: конструктор, проверка связи и закрытие.

pool, err := pgxpool.(ctx, "postgres://app:secret@localhost:5432/app") if err != nil { log.Fatal(err) } if err = pool.(ctx); err != nil { log.Fatal(err) } defer pool.()

Соберите каркас программы

Расставьте строки в порядке исполнения: контекст, пул, обработка ошибки, закрытие, пинг.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: словарь pgx

Термин

pgx

нажмите, чтобы перевернуть
Определение

Нативный драйвер PostgreSQL для Go (github.com/jackc/pgx/v5): собственная реализация протокола, pgxpool, Batch, COPY, LISTEN/NOTIFY, типы pgtype.

1 / 6

Как устроен курс и что запускать#

Курс из двадцати пяти уроков ведёт от первого подключения до итогового сервиса заметок со сканированием структур, транзакциями, 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-проекту pgx)

Вопросы и ответы по уроку

Что такое 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.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

MongoDB25 уроков~12 чпрактика25 уроков уже доступны

MongoDB в Go: официальный драйвер mongo-driver v2

Курс MongoDB для Go: подключение, bson-теги, CRUD, фильтры, агрегации, индексы, транзакции и Change Streams. 25 уроков по mongo-driver v2.

  • Подключаться к MongoDB и настраивать клиента
  • Моделировать документы структурами и bson-тегами
  • Делать CRUD, сложные фильтры и проекции
  • Строить агрегации $match/$group/$lookup