Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужны транзакции
Транзакции — это фундаментальный механизм баз данных для обеспечения надежности, консистентности и безопасности данных при одновременной работе множества приложений.
Основная проблема
Представь финансовый перевод денег:
// БЕЗ транзакции:
db.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
db.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
Что может пойти не так:
- Первая операция успешна
- Приложение падает или отключается
- Вторая операция никогда не выполнится
- Результат: деньги исчезли! Нарушена консистентность данных
ACID гарантии
Транзакции обеспечивают ACID — четыре свойства:
A — Atomicity (Атомарность):
- Транзакция либо полностью выполняется, либо полностью откатывается
- Нет промежуточных состояний
- Перевод денег либо произойдёт полностью, либо не произойдёт вообще
db.WithTx(func(tx *sql.Tx) error {
// все операции будут выполнены вместе или откачены вместе
tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
return nil // если вернём ошибку, всё откатится
})
C — Consistency (Консистентность):
- База данных переходит из одного согласованного состояния в другое
- Инварианты приложения сохраняются
- Например: сумма денег во всех счётах всегда одна и та же
I — Isolation (Изоляция):
- Одновременные транзакции не мешают друг другу
- Каждая видит консистентный снимок данных
- Уровни изоляции контролируют, насколько строго это выполняется
D — Durability (Долговечность):
- После успешного завершения транзакции данные сохранены на диск
- Если произойдёт сбой, данные не будут потеряны
Практические примеры
1. Банковские операции
func transferMoney(from, to string, amount float64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // откатится, если не вызвана Commit
// Снять со счёта
if _, err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from); err != nil {
return err
}
// Добавить на счёт
if _, err := tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to); err != nil {
return err
}
// Если всё ОК, фиксируем
return tx.Commit().Error
}
2. Создание заказа с товарами
func createOrder(userID string, items []Item) error {
tx := db.BeginTx(ctx, nil)
// Создать заказ
var orderID string
if err := tx.QueryRow("INSERT INTO orders (user_id) VALUES (?) RETURNING id", userID).Scan(&orderID); err != nil {
tx.Rollback()
return err
}
// Добавить товары
for _, item := range items {
if _, err := tx.Exec("INSERT INTO order_items (order_id, item_id, qty) VALUES (?, ?, ?)", orderID, item.ID, item.Qty); err != nil {
tx.Rollback()
return err
}
}
// Если всё успешно, фиксируем транзакцию
return tx.Commit().Error
}
Проблемы без транзакций
Грязное чтение:
- Process A пишет данные
- Process B читает незавершённые данные
- Process A откатывается
- Process B работает с неправильными данными
Lost Update:
- Process A читает значение (10)
- Process B читает значение (10)
- Process A увеличивает на 5, пишет (15)
- Process B увеличивает на 3, пишет (13)
- Результат: потеряно изменение Process A
Broken Referential Integrity:
- Удаляем заказ
- Забываем удалить товары заказа
- База в несогласованном состоянии
Применение в Go
// с database/sql
tx, _ := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
defer tx.Rollback()
// работа с транзакцией
tx.ExecContext(ctx, ...)
tx.QueryRowContext(ctx, ...)
tx.Commit() // или откатим
// с ORM (GORM)
db.WithTx(tx).Model(&Order{}).Create(&order)
db.Transaction(func(tx *gorm.DB) error {
// код
return nil
})
Когда транзакции обязательны
- Финансовые операции — переводы, платежи
- Изменение нескольких таблиц — должны быть вместе
- Проверка бизнес-инвариантов — уникальные ключи, constraints
- Операции с зависимостями — parent-child отношения
Когда транзакции опциональны
- Чтение данных (SELECT)
- Логирование — если можем потерять запись
- Кеширование — не критично по консистентности
- Статистика — небольшие неточности допустимы
Best Practices
DO:
- Используй транзакции для мульти-операционных изменений
- Выбирай правильный уровень изоляции
- Держи транзакции короткими (меньше конфликтов)
- Логируй ошибки откатов
DON'T:
- Не держи транзакцию открытой долго
- Не делай HTTP запросы внутри транзакции
- Не полагайся только на откат при ошибках
Вывод: транзакции — это критически важный инструмент для гарантирования надежности и консистентности данных. Без них современные приложения неработоспособны.