Зачем нужны транзакции
Транзакции — это механизм, обеспечивающий надёжное выполнение групп операций над базой данных (или другой системой хранения), как **атомарного** и **целостного** блока, который либо выполняется полностью, либо не выполняется вовсе. Их основное назначение — **гарантировать согласованность данных, устойчивость к сбоям и корректную работу при одновременном доступе**. Особенно важны транзакции в многооперационных изменениях и в многопользовательских средах.
---
## 1. **Определение транзакции**
Транзакция — это логическая единица работы, которая состоит из одной или более операций (обычно чтение, вставка, обновление или удаление), выполняемых как одно целое. Она должна обладать четырьмя свойствами — **ACID**.
---
## 2. **Свойства ACID**
Свойства ACID описывают требования к любой полноценной транзакционной системе:
### A — **Atomicity** (Атомарность)
* Все операции внутри транзакции либо выполняются полностью, либо не выполняются вовсе.
* Если одна из операций в транзакции завершится ошибкой, система откатит все уже выполненные операции.
* Например, при переводе денег с одного счёта на другой: либо списание и зачисление произойдут оба, либо ни одно не произойдёт.
### C — **Consistency** (Согласованность)
* После завершения транзакции данные должны оставаться в согласованном состоянии.
* Например, если в базе данных есть ограничение уникальности или внешние ключи — они не должны нарушаться в результате выполнения транзакции.
### I — **Isolation** (Изолированность)
* Одновременные транзакции не должны влиять друг на друга. Результат параллельного выполнения должен быть эквивалентен какому-то последовательному порядку выполнения.
* Это предотвращает "грязные чтения", "неповторяющиеся чтения" и "фантомные чтения".
### D — **Durability** (Надёжность/Устойчивость)
* После фиксации (commit) изменений они должны сохраняться даже при сбоях, например, при отключении питания.
* Это достигается через журналы (write-ahead log), репликацию и устойчивые хранилища.
---
## 3. **Когда нужны транзакции**
### 3.1. **Финансовые операции**
Например, при переводе средств между двумя счетами:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
Если произойдёт сбой после первого запроса, но до второго — данные останутся неконсистентными без транзакции.
### 3.2. **Обновление связанных таблиц**
При добавлении заказа и связанных с ним записей:
BEGIN;
INSERT INTO orders ...;
INSERT INTO order_items ...;
COMMIT;
Если одна операция не выполнится, обе должны быть отменены.
### 3.3. **Конкурентный доступ**
В системах с множеством пользователей или потоков, транзакции защищают от ошибок, вызванных параллельным доступом к одним и тем же данным.
---
## 4. **Структура транзакции**
Типичный жизненный цикл транзакции:
1. **Begin** — начало транзакции.
2. **Операции** — чтение/запись/изменение данных.
3. **Commit** — фиксирование изменений (успешное завершение).
4. **Rollback** — откат всех изменений (в случае ошибки или по инициативе приложения).
---
## 5. **Типы изоляции транзакций**
Разные уровни изоляции балансируют между производительностью и безопасностью от ошибок параллельного доступа:
| Уровень | Грязные чтения | Неповторяющиеся чтения | Фантомные чтения |
| ---------------- | -------------- | ---------------------- | ---------------- |
| Read Uncommitted | ✅ | ✅ | ✅ |
| Read Committed | ❌ | ✅ | ✅ |
| Repeatable Read | ❌ | ❌ | ✅ |
| Serializable | ❌ | ❌ | ❌ |
**Примеры:**
* **Грязное чтение (Dirty Read)**: одна транзакция читает данные, которые другая ещё не зафиксировала.
* **Неповторяющееся чтение**: повторное чтение тех же данных возвращает разные значения.
* **Фантомное чтение**: повторное выполнение запроса возвращает другие строки (новые или удалённые).
---
## 6. **Транзакции в SQL**
BEGIN TRANSACTION;
UPDATE users SET age = 30 WHERE id = 5;
DELETE FROM logs WHERE user_id = 5;
COMMIT;
Если ошибка:
ROLLBACK;
---
## 7. **Автоматические и ручные транзакции**
Многие базы данных по умолчанию работают в **автоматическом режиме**, фиксируя каждую команду отдельно. Для группировки операций вручную нужно отключать автокоммит.
// пример на Go
tx, err := db.Begin()
...
tx.Commit() // или tx.Rollback()
---
## 8. **Транзакции вне баз данных**
Транзакции используются не только в СУБД:
* **Файловые системы**: при копировании или записи нескольких файлов.
* **Системы сообщений (например, Kafka)**: поддерживают транзакционные отправки и подтверждения.
* **Распределённые системы**: используют распределённые транзакции и протоколы типа 2PC, Saga и т.п.
---
## 9. **Распределённые транзакции**
Когда изменения происходят в нескольких системах/сервисах, используются специальные протоколы:
### 9.1. **Two-Phase Commit (2PC)**
* **Prepare**: все участники подтверждают готовность.
* **Commit**: координатор отправляет команду зафиксировать.
Минус: уязвимость к блокировке в случае сбоя координатора.
### 9.2. **Saga Pattern**
* Многошаговый бизнес-процесс, каждая операция имеет **компенсационную** (отменяющую) операцию.
* Используется в микросервисах.
---
## 10. **Журналирование и WAL**
Механизм WAL (Write-Ahead Log) используется для сохранения устойчивости:
1. Все операции записываются в журнал.
2. Только после этого данные записываются в таблицы.
3. В случае сбоя — данные можно восстановить, «переиграв» журнал.
---
## 11. **Почему не всегда нужны транзакции**
* Повышают накладные расходы (особенно `Serializable` изоляция).
* В высоконагруженных системах иногда используются **eventual consistency** или **идемпотентные операции** без строгих транзакций.
---
## 12. **Безопасность и логика приложения**
Транзакции позволяют разрабатывать безопасные приложения без сложного контроля последовательностей операций. Они гарантируют, что никакая ошибка, сбой или пользовательская активность не приведёт к повреждению данных.
---
## 13. **Примеры проблем без транзакций**
### 13.1. **Потеря денег**
* Списали деньги с одного счёта, а на второй зачислить не успели — пользователь потерял средства.
### 13.2. **Дублирование заказов**
* Один заказ может быть создан дважды, если два потока одновременно видят "свободный" слот.
### 13.3. **Гонки данных**
* Два запроса обновляют одну и ту же строку, и один затирает изменения другого.
---
## 14. **Контроль ошибок в транзакциях**
Пример на Go:
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
Такой шаблон гарантирует корректный rollback при ошибке и commit при успехе.
---
## 15. **Проверка и логгирование**
Транзакции позволяют лучше отслеживать и анализировать сбои, так как всегда можно узнать:
* Что выполнилось.
* Что откатилось.
* Почему произошёл rollback.