логотип PurpleSchool
логотип PurpleSchool

Работа с PostgreSQL в Go

Автор

Александр Гольцман

PostgreSQL — мощная реляционная база данных, широко используемая в веб-разработке, аналитике и корпоративных системах. В языке Go (или Golang) для работы с PostgreSQL можно использовать как низкоуровневые драйверы, так и ORM.

В этой статье я покажу, как интегрировать PostgreSQL в Go-проект, разберем особенности работы с соединениями, индексы и транзакции, а также рассмотрим, в каких случаях стоит использовать ORM, а когда лучше обходиться чистым SQL.

Драйверы PostgreSQL для Go

В экосистеме Go есть несколько способов взаимодействия с PostgreSQL:

  • database/sql
    • lib/pq
    — стандартный вариант с низкоуровневым доступом.
  • pgx — высокопроизводительный драйвер с расширенными возможностями.
  • ORM (например, gorm) — удобный способ работы с базой через структуры Go.

Я рекомендую pgx, если нужна высокая производительность, так как он быстрее lib/pq и поддерживает расширенные возможности PostgreSQL, такие как копирование данных (COPY), прослушивание (LISTEN/NOTIFY) и подготовленные запросы (Prepared Statements).

Устанавливаем pgx:

go get github.com/jackc/pgx/v5

Работа с соединениями

PostgreSQL поддерживает пул соединений, что важно для высоконагруженных приложений. В pgx можно использовать pgxpool для управления соединениями:

import (
    "context"
    "log"
    "github.com/jackc/pgx/v5/pgxpool"
)

func main() {
    dsn := "postgres://user:password@localhost:5432/mydb"
    pool, err := pgxpool.New(context.Background(), dsn)
    if err != nil {
        log.Fatal("Ошибка подключения:", err)
    }
    defer pool.Close()
}

Почему это важно?

  • Использование пула соединений снижает нагрузку на базу.
  • Открытие и закрытие соединений — дорогостоящая операция.
  • PostgreSQL имеет ограничение на количество активных соединений, поэтому управление ими критично.

Оптимизация запросов

Индексы в PostgreSQL

PostgreSQL поддерживает несколько видов индексов, которые ускоряют доступ к данным.

  • B-Tree — стандартный индекс для равенства и сравнений (=, <, >).
  • GIN — ускоряет поиск по JSONB и tsvector.
  • BRIN — полезен для больших таблиц с упорядоченными данными.

Пример создания индекса:

CREATE INDEX idx_users_email ON users(email);

В Go можно явно указывать использование индексов в запросах, анализируя их с помощью EXPLAIN ANALYZE.

row := pool.QueryRow(context.Background(), "EXPLAIN ANALYZE SELECT * FROM users WHERE email = $1", "user@example.com")
var analysis string
row.Scan(&analysis)
log.Println(analysis)

Работа с JSONB

Одно из преимуществ PostgreSQL — поддержка JSONB, что позволяет хранить и запрашивать данные в JSON-формате.

Пример хранения JSONB в Go:

type Product struct {
    ID     int            `json:"id"`
    Name   string         `json:"name"`
    Params map[string]any `json:"params"`
}

query := `INSERT INTO products (name, params) VALUES ($1, $2) RETURNING id`
jsonData := map[string]any{"color": "red", "size": "M"}
id := 0
err := pool.QueryRow(context.Background(), query, "Shirt", jsonData).Scan(&id)

Запрос данных по ключу JSONB:

SELECT * FROM products WHERE params->>'color' = 'red';

Работа с транзакциями

В pgx транзакции обрабатываются через Begin(). Пример корректного использования:

tx, err := pool.Begin(context.Background())
if err != nil {
    log.Fatal("Ошибка при создании транзакции:", err)
}
defer tx.Rollback(context.Background())

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

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

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

Транзакции позволяют гарантировать целостность данных, например, при переводах между счетами.

Использование Listen/Notify

PostgreSQL поддерживает механизм LISTEN/NOTIFY, позволяющий подписываться на события в базе данных. Это полезно для реактивных систем.

Пример подписки:

conn, err := pool.Acquire(context.Background())
if err != nil {
    log.Fatal(err)
}
defer conn.Release()

_, err = conn.Exec(context.Background(), "LISTEN new_event")
if err != nil {
    log.Fatal(err)
}

for {
    notification, err := conn.Conn().WaitForNotification(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Получено уведомление:", notification.Payload)
}

Отправка уведомления из базы:

NOTIFY new_event, 'data_updated';

Выбор между pgx и ORM

Когда использовать pgx:

  • Высоконагруженные системы.
  • Сложные запросы и работа с COPY.
  • Полный контроль над соединением и транзакциями.

Когда использовать ORM (gorm):

  • Простые CRUD-операции.
  • Быстрая разработка MVP.
  • Удобная миграция схем.

Заключение

PostgreSQL предоставляет мощные инструменты для работы с данными, а Go предлагает несколько способов взаимодействия с базой.

Смотрите, что важно учитывать:

  • Драйвер pgx — оптимальный выбор для работы с PostgreSQL благодаря высокой производительности.
  • Индексы помогают ускорить запросы, но их нужно применять осознанно.
  • JSONB удобен для хранения полуструктурированных данных.
  • Listen/Notify позволяет строить реактивные системы.

Если ваша задача — высокая производительность и полный контроль, используйте pgx. Если важнее скорость разработки, ORM вроде gorm может значительно упростить работу с базой.

Карта развития разработчика

Получите полную карту развития разработчика по всем направлениям: frontend, backend, devops, mobile