pgx для Go: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в pgx: мощный PostgreSQL драйвер для Go

Библиотека pgx (PostgreSQL eXtended) — это современный, высокопроизводительный драйвер и набор инструментов для работы с PostgreSQL из языка программирования Go. В отличие от стандартного пакета database/sql, pgx предоставляет прямой доступ к протоколу PostgreSQL, что даёт значительный прирост производительности и доступ к уникальным возможностям СУБД, таким как LISTEN/NOTIFY, COPY протокол, работа с массивами и составными типами, и многое другое.

Зачем использовать pgx вместо стандартного database/sql? Во-первых, pgx работает на 30-50% быстрее за счёт отсутствия лишних абстракций. Во-вторых, он поддерживает все современные возможности PostgreSQL 12+ (и более новых версий). В-третьих, pgx предоставляет богатый API для работы с соединениями, пулом соединений, подготовленными запросами и транзакциями. Наконец, pgx полностью совместим с database/sql через специальный адаптер, что позволяет использовать его в проектах, где уже используется стандартный интерфейс.

Установка pgx

Установка pgx в проект Go выполняется стандартной командой go get. Для работы с PostgreSQL вам потребуется установить основной пакет pgx и, возможно, дополнительные модули:

# Установка основного пакета pgx v5 (рекомендуемая версия)
go get github.com/jackc/pgx/v5

# Установка пакета для работы с пулом соединений
go get github.com/jackc/pgx/v5/pgxpool

# Установка адаптера для совместимости с database/sql
go get github.com/jackc/pgx/v5/stdlib

После выполнения этих команд, вы сможете импортировать pgx в свои Go-файлы. Убедитесь, что у вас установлен Go версии 1.18 или выше, так как pgx v5 использует дженерики.

Быстрый старт: первое подключение к PostgreSQL

Давайте напишем простейшую программу, которая подключается к базе данных PostgreSQL и выполняет запрос. Предположим, что у вас запущен PostgreSQL на localhost:5432, база данных называется testdb, пользователь testuser с паролем testpass.

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/jackc/pgx/v5"
)

func main() {
    // Строка подключения
    connStr := "postgres://testuser:testpass@localhost:5432/testdb"

    // Устанавливаем соединение
    conn, err := pgx.Connect(context.Background(), connStr)
    if err != nil {
        log.Fatalf("Не удалось подключиться к БД: %v", err)
    }
    defer conn.Close(context.Background())

    // Выполняем простой запрос
    var greeting string
    err = conn.QueryRow(context.Background(), "SELECT 'Hello, PostgreSQL!'").Scan(&greeting)
    if err != nil {
        log.Fatalf("Ошибка выполнения запроса: %v", err)
    }

    fmt.Println(greeting)
}

Этот код устанавливает соединение, выполняет запрос и выводит результат. Обратите внимание на использование context.Background() — это стандартный способ передачи контекста в Go, который позволяет контролировать таймауты и отмену операций.

Основные методы и функции pgx

Рассмотрим наиболее важные методы и функции библиотеки pgx. Для каждого метода приведём описание, параметры и рабочий пример кода.

1. pgx.Connect — установка соединения

Что делает: Создаёт новое подключение к PostgreSQL. Это основной способ начать работу с базой данных.

Параметры:

  • ctx context.Context — контекст для управления временем жизни соединения
  • connString string — строка подключения в формате URI или DSN

Возвращает: (*pgx.Conn, error)

Пример:

conn, err := pgx.Connect(context.Background(), "postgres://user:pass@localhost:5432/mydb")
if err != nil {
    log.Fatal(err)
}
defer conn.Close(context.Background())

2. conn.QueryRow — выполнение запроса с одной строкой результата

Что делает: Выполняет SQL-запрос, который возвращает ровно одну строку. Удобно для SELECT с условием по первичному ключу.

Параметры:

  • ctx context.Context — контекст
  • sql string — SQL-запрос с плейсхолдерами ($1, $2, ...)
  • args ...interface{} — аргументы для плейсхолдеров

Возвращает: pgx.Row (имеет метод Scan для извлечения данных)

Пример:

var name string
var age int
err := conn.QueryRow(context.Background(), 
    "SELECT name, age FROM users WHERE id = $1", 42).Scan(&name, &age)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Пользователь: %s, возраст: %d\n", name, age)

3. conn.Query — выполнение запроса с множеством строк

Что делает: Выполняет SQL-запрос и возвращает итератор по строкам результата. Используется, когда ожидается несколько строк.

Параметры: Аналогично QueryRow

Возвращает: (pgx.Rows, error)

Пример:

rows, err := conn.Query(context.Background(), "SELECT id, name FROM users WHERE age > $1", 18)
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

for rows.Next() {
    var id int
    var name string
    err := rows.Scan(&id, &name)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID: %d, Name: %s\n", id, name)
}

4. conn.Exec — выполнение запроса без возврата строк

Что делает: Выполняет SQL-запрос, который не возвращает строки (INSERT, UPDATE, DELETE, CREATE TABLE и т.д.). Возвращает количество затронутых строк.

Параметры: Аналогично Query

Возвращает: (pgconn.CommandTag, error)

Пример:

ct, err := conn.Exec(context.Background(), 
    "INSERT INTO users (name, age) VALUES ($1, $2)", "Alice", 30)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Затронуто строк: %d\n", ct.RowsAffected())

5. conn.Begin — начало транзакции

Что делает: Начинает новую транзакцию PostgreSQL. Все последующие запросы будут выполняться в рамках этой транзакции до вызова Commit или Rollback.

Параметры: ctx context.Context

Возвращает: (pgx.Tx, error)

Пример:

tx, err := conn.Begin(context.Background())
if err != nil {
    log.Fatal(err)
}
defer tx.Rollback(context.Background()) // откат, если не будет Commit

_, err = tx.Exec(context.Background(), "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
if err != nil {
    log.Fatal(err)
}

_, err = tx.Exec(context.Background(), "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)
if err != nil {
    log.Fatal(err)
}

err = tx.Commit(context.Background())
if err != nil {
    log.Fatal(err)
}

6. conn.Prepare — подготовка запроса

Что делает: Создаёт подготовленный запрос на стороне сервера. Это повышает производительность при многократном выполнении одного и того же запроса с разными параметрами.

Параметры:

  • ctx context.Context — контекст
  • name string — имя подготовленного запроса (можно пустую строку)
  • sql string — SQL-запрос

Возвращает: (*pgconn.StatementDescription, error)

Пример:

_, err := conn.Prepare(context.Background(), "getUser", "SELECT name, age FROM users WHERE id = $1")
if err != nil {
    log.Fatal(err)
}

// Использование подготовленного запроса
var name string
var age int
err = conn.QueryRow(context.Background(), "getUser", 42).Scan(&name, &age)
if err != nil {
    log.Fatal(err)
}

7. pgxpool.New — создание пула соединений

Что делает: Создаёт пул соединений к PostgreSQL. Пул управляет жизненным циклом соединений, автоматически переподключается при обрывах и позволяет эффективно использовать ресурсы в многопоточных приложениях.

Параметры:

  • ctx context.Context — контекст
  • connString string — строка подключения

Возвращает: (*pgxpool.Pool, error)

Пример:

pool, err := pgxpool.New(context.Background(), "postgres://user:pass@localhost:5432/mydb")
if err != nil {
    log.Fatal(err)
}
defer pool.Close()

// Получение соединения из пула
conn, err := pool.Acquire(context.Background())
if err != nil {
    log.Fatal(err)
}
defer conn.Release()

var count int
err = conn.QueryRow(context.Background(), "SELECT COUNT(*) FROM users").Scan(&count)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Всего пользователей: %d\n", count)

8. conn.CopyFrom — массовая вставка данных

Что делает: Выполняет массовую вставку строк с использованием протокола COPY PostgreSQL. Это самый быстрый способ вставить много данных (в десятки раз быстрее обычных INSERT).

Параметры:

  • ctx context.Context — контекст
  • tableName pgx.Identifier — имя таблицы (можно указать схему)
  • columnNames []string — имена столбцов
  • rowSrc pgx.CopyFromSource — источник данных (обычно pgx.CopyFromRows)

Возвращает: (int64, error) — количество вставленных строк

Пример:

// Подготовка данных для вставки
rows := [][]interface{}{
    {"Alice", 30},
    {"Bob", 25},
    {"Charlie", 35},
}

copyCount, err := conn.CopyFrom(
    context.Background(),
    pgx.Identifier{"users"},
    []string{"name", "age"},
    pgx.CopyFromRows(rows),
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Вставлено строк: %d\n", copyCount)

9. conn.Listen / conn.Unlisten — подписка на уведомления

Что делает: Позволяет подписаться на уведомления PostgreSQL (LISTEN/NOTIFY). Это механизм асинхронного уведомления между клиентами через базу данных.

Параметры:

  • ctx context.Context — контекст
  • channel string — имя канала

Возвращает: error

Пример:

// Подписка на канал
err := conn.Listen(context.Background(), "my_channel")
if err != nil {
    log.Fatal(err)
}

// Ожидание уведомления (в отдельной горутине)
notification, err := conn.WaitForNotification(context.Background())
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Пол                    

Рекомендаций