Транзакции в SQL
Определение и базовые свойства (ACID)
Транзакция — это логическая единица работы с данными, которая выполняется полностью или не выполняется вовсе. Основные свойства транзакций описываются аббревиатурой ACID:
- Atomicity (Атомарность) — транзакция выполняется целиком или откатывается полностью. Невозможно частичное применение изменений.
- Consistency (Согласованность) — после завершения транзакции данные остаются в согласованном состоянии, соблюдаются все ограничения целостности.
- Isolation (Изолированность) — параллельно выполняющиеся транзакции не влияют друг на друга.
- Durability (Долговечность) — после фиксации транзакции изменения сохраняются даже при сбое системы.
Уровни изоляции
Стандарт SQL определяет 4 уровня изоляции, каждый из которых решает определенные проблемы параллельного доступа:
Проблемы параллельного доступа:
- Dirty Read (Грязное чтение) — чтение данных, измененных незафиксированной транзакцией
- Non-repeatable Read (Неповторяющееся чтение) — повторное чтение тех же данных дает разные результаты из-за изменений другой транзакции
- Phantom Read (Фантомное чтение) — повторное выполнение запроса возвращает новые строки, добавленные другой транзакцией
Уровни изоляции (от слабого к сильному):
- READ UNCOMMITTED — минимальная изоляция, возможны все три проблемы
- READ COMMITTED — защита от грязного чтения (дефолт в PostgreSQL, SQL Server)
- REPEATABLE READ — защита от грязного и неповторяющегося чтения (дефолт в MySQL/InnoDB)
- SERIALIZABLE — полная изоляция, транзакции выполняются последовательно
Практическая реализация в Go
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
if err != nil {
return err
}
defer tx.Rollback() // безопасный откат при ошибке
// Выполнение операций
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - 100 WHERE id = $1", fromID)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + 100 WHERE id = $1", toID)
if err != nil {
return err
}
return tx.Commit()
Типичные ошибки и best practices
Распространенные проблемы:
- Слишком длинные транзакции — блокируют ресурсы, увеличивают вероятность deadlock'ов
- Игнорирование Rollback — при ошибке нужно явно откатывать транзакцию
- Смешивание уровней изоляции — разные СУБД по-разному реализуют уровни изоляции
- Отсутствие обработки retry — при сериализуемом уровне изоляции нужно быть готовым к повторным попыткам
Рекомендации:
- Всегда используйте
defer tx.Rollback()перед Commit - Держите транзакции короткими
- Выбирайте минимально необходимый уровень изоляции
- Для высоконагруженных систем рассмотрите оптимистичные блокировки вместо пессимистичных
- В PostgreSQL READ COMMITTED — хороший баланс между производительностью и консистентностью
Особенности разных СУБД
- PostgreSQL — по умолчанию READ COMMITTED, отличная поддержка MVCC, нет грязного чтения даже на READ UNCOMMITTED
- MySQL/InnoDB — REPEATABLE READ по умолчанию, может быть проблема с фантомным чтением
- SQLite — SERIALIZABLE по умолчанию, но блокировки на уровне всей базы
Транзакции — мощный механизм, но их неправильное использование может серьезно ухудшить производительность и привести к проблемам с масштабированием.