Ответ
Пакетное обновление (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. Использование временной таблицы
Это самый мощный подход для очень больших объемов данных.
- Создать временную таблицу:
CREATE TEMP TABLE updates (...)
- Загрузить данные: Использовать
COPY FROM
(самый быстрый способ в PostgreSQL) для массовой вставки данных во временную таблицу. - Обновить основную таблицу: Выполнить один
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
. Для массовых миграций — временные таблицы.