Ответ
Пакетное обновление (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. Для массовых миграций — временные таблицы.
Ответ 18+ 🔞
Слушай, а вот этот ваш пакетный апдейт — это ж, блядь, просто песня, а не технология! Вместо того чтобы дрочить базу тысячей мелких запросов, ты одним махом, как говорится, всё и делаешь. Серьёзно, нагрузка падает в разы, а скорость — просто овердохуищная.
Смотри, какие есть варианты, чтобы не облажаться:
1. Старый добрый pgx.Batch — просто, как три копейки
Ты просто пакуешь кучу UPDATE-ов в одну пачку и шлёшь их разом. База их принимает, обрабатывает и не морщится, как от спама.
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() // Эту хуйню закрывать надо обязательно, а то память потечёт!
// А тут проверяем, не накосячили ли где
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
}
Что хорошо: Пишешь три строчки и уже герой. Ошибки ловить удобно. Что не очень: Если ты решишь обновить, блядь, миллион строк одним батчем — может и не взлететь, будет тормозить.
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— это, блядь, реактивный снаряд по скорости. - Обновляешь основную таблицу: Делаешь один апдейт с джойном.
-- Это тот самый третий шаг
UPDATE my_table
SET column1 = u.column1
FROM updates AS u
WHERE my_table.id = u.id;
Что хорошо: Скорость — просто ёперный театр! Для гигантских объёмов — лучше не придумаешь. Что не очень: Кода больше, шагов больше. Надо думать, а это не всем дано.
И главное, запомни, как "Отче наш":
- Транзакции — твои друзья: Всё, что делаешь пачкой, оборачивай в транзакцию. Чтобы либо всё обновилось, либо нихуя. Никаких промежуточных состояний.
- Не жадничай: Не пытайся впихнуть в один батч всю свою базу. Разбей на куски по 100-1000 записей — и будет тебе счастье.
- Выбирай с умом: Для повседневки —
pgx.Batch. Для сложной логики —CASE. Если данных — пиздец сколько — временные таблицы иCOPY. Всё просто, как два пальца об асфальт.