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

Ответ

Пакетное обновление (batch update) — это эффективный способ обновить множество строк в базе данных за один раз, что значительно снижает сетевые задержки и нагрузку на БД по сравнению с отправкой отдельных UPDATE запросов для каждой строки.

Рассмотрим несколько популярных подходов:

1. Множественные UPDATE в одной транзакции с pgx.Batch

Это самый простой и идиоматичный способ в Go при работе с pgx. Все запросы отправляются на сервер за один раунд-трип.

func batchUpdateWithPgx(ctx context.Context, conn *pgx.Conn, items []Item) error {
    batch := &pgx.Batch{}
    for _, item := range items {
        batch.Queue("UPDATE my_table SET value = $1 WHERE id = $2", item.Value, item.ID)
    }

    br := conn.SendBatch(ctx, batch)
    defer br.Close() // Важно закрывать BatchResults

    // Проверяем ошибки для каждой выполненной команды
    for i := 0; i < len(items); i++ {
        if _, err := br.Exec(); err != nil {
            return fmt.Errorf("error in batch on item %d: %w", i, err)
        }
    }

    return nil
}

Плюсы: Простота, хороший контроль над ошибками.
Минусы: Может быть не так производительно, как один SQL-запрос для очень больших батчей.

2. UPDATE с выражением CASE

Этот подход формирует один большой SQL-запрос, который обновляет разные строки разными значениями.

UPDATE my_table
SET
    column1 = CASE id
        WHEN 1 THEN 'new_value_1'
        WHEN 2 THEN 'new_value_2'
        -- ... и так далее
    END,
    column2 = CASE id
        WHEN 1 THEN 100
        WHEN 2 THEN 200
        -- ... и так далее
    END
WHERE id IN (1, 2, ...);

Плюсы: Выполняется как один атомарный запрос, очень эффективно для средних батчей.
Минусы: Требует динамической генерации SQL, что может быть сложно и небезопасно (риск SQL-инъекций, если не использовать плейсхолдеры).

3. Использование временной таблицы

Это самый мощный подход для очень больших объемов данных.

  1. Создать временную таблицу: CREATE TEMP TABLE updates (...)
  2. Загрузить данные: Использовать COPY FROM (самый быстрый способ в PostgreSQL) для массовой вставки данных во временную таблицу.
  3. Обновить основную таблицу: Выполнить один UPDATE с JOIN (или FROM в PostgreSQL) с временной таблицей.
-- Шаг 3
UPDATE my_table
SET column1 = u.column1
FROM updates AS u
WHERE my_table.id = u.id;

Плюсы: Наилучшая производительность для десятков тысяч и более обновлений.
Минусы: Более сложная реализация, требует нескольких шагов.

Общие рекомендации:

  • Используйте транзакции: Всегда оборачивайте пакетные операции в транзакцию для обеспечения атомарности.
  • Ограничивайте размер батча: Не делайте батчи слишком большими (обычно 100-1000 записей — хороший старт), чтобы не исчерпать память или не вызвать длительные блокировки в БД.
  • Выбирайте метод по задаче: Для большинства случаев pgx.Batch — лучший выбор. Для сложных обновлений — CASE. Для массовых миграций — временные таблицы.