Как в Go выполнить несколько запросов к базе данных в одной пачке (batch) и обработать результаты?

Ответ

Пакетная обработка запросов (batching) — это эффективный способ отправить несколько команд к базе данных за один сетевой вызов, что снижает задержки и повышает производительность.

В Go это можно реализовать с помощью драйверов, поддерживающих такую функциональность. Наиболее популярным и современным решением является pgx.

Пример с использованием pgx

pgx предоставляет удобный интерфейс pgx.Batch для работы с пакетами запросов.

package main

import (
    "context"
    "fmt"
    "os"

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

func main() {
    ctx := context.Background()
    // Убедитесь, что у вас есть доступ к БД
    dbpool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
    if err != nil {
        fmt.Fprintf(os.Stderr, "Unable to create connection pool: %vn", err)
        os.Exit(1)
    }
    defer dbpool.Close()

    batch := &pgx.Batch{}
    // Добавляем запросы в очередь
    batch.Queue("SELECT id, name FROM users WHERE id = $1", 1)
    batch.Queue("SELECT id, price FROM products WHERE id = $1", 42)

    // Отправляем пакет
    results := dbpool.SendBatch(ctx, batch)
    // Важно закрыть results, чтобы освободить соединение
    defer results.Close()

    // Читаем результаты в том же порядке, в котором отправляли запросы
    var userID int
    var userName string
    if err := results.QueryRow().Scan(&userID, &userName); err != nil {
        fmt.Fprintf(os.Stderr, "Failed to read user: %vn", err)
    }
    fmt.Printf("User: ID=%d, Name=%sn", userID, userName)

    var productID int
    var productPrice float64
    if err := results.QueryRow().Scan(&productID, &productPrice); err != nil {
        fmt.Fprintf(os.Stderr, "Failed to read product: %vn", err)
    }
    fmt.Printf("Product: ID=%d, Price=%.2fn", productID, productPrice)
}

Ключевые моменты:

  1. Порядок важен: Результаты нужно читать строго в том же порядке, в котором запросы были добавлены в batch.
  2. Освобождение ресурсов: Всегда используйте defer results.Close(), чтобы гарантировать, что соединение с базой данных вернется в пул, даже если при обработке результатов произойдет паника.
  3. Обработка ошибок: Проверяйте ошибку после каждого вызова .Scan(). Ошибка в одном из запросов не прерывает выполнение всего пакета, но результат для этого запроса будет содержать ошибку.