← Назад к вопросам
Что такое транзакции?
1.6 Junior🔥 181 комментариев
#Архитектура и паттерны#Базы данных (SQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Транзакции в базах данных
Транзакция — это группа SQL операций, которые либо все выполняются успешно, либо все откатываются (rollback). Это гарантирует консистентность и целостность данных, даже если что-то пошло не так.
Основной принцип: ACID
Транзакции гарантируют свойства ACID:
A — Atomicity (Атомарность)
- Транзакция либо полностью выполнена, либо не выполнена вообще
- Нет половинчатых состояний
C — Consistency (Консистентность)
- БД переходит из одного консистентного состояния в другое
- Все правила целостности соблюдены
I — Isolation (Изоляция)
- Параллельные транзакции не видят друг друга промежуточные состояния
- Предотвращает race conditions
D — Durability (Долговечность)
- Если транзакция успешна, данные постоянны (даже при сбое)
Простой пример: перевод денег
# БЕЗ транзакции (опасно!)
user1.balance -= 100 # Вычли деньги
# ... система упала, данные потеряны!
user2.balance += 100 # Так и не добавили
# С ТРАНЗАКЦИЕЙ (безопасно!)
try:
user1.balance -= 100
user2.balance += 100
commit() # Успех!
except Exception:
rollback() # Откатили оба изменения
SQL синтаксис
-- Явное управление транзакциями
BEGIN; -- или START TRANSACTION
UPDATE users SET balance = balance - 100 WHERE id = 1;
UPDATE users SET balance = balance + 100 WHERE id = 2;
COMMIT; -- Сохранить все изменения
-- Откат при ошибке
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
-- Ошибка!
ROLLBACK; -- Вернулись к начальному состоянию
Django ORM
from django.db import transaction
# Простая транзакция
with transaction.atomic():
user1 = User.objects.get(id=1)
user2 = User.objects.get(id=2)
user1.balance -= 100
user2.balance += 100
user1.save()
user2.save()
# При выходе из контекста — автоматический commit
# При исключении — автоматический rollback
# Явное управление
from django.db import connection
cursor = connection.cursor()
try:
cursor.execute("BEGIN")
cursor.execute("UPDATE users SET balance = balance - 100 WHERE id = 1")
cursor.execute("UPDATE users SET balance = balance + 100 WHERE id = 2")
cursor.execute("COMMIT")
except Exception:
cursor.execute("ROLLBACK")
raise
SQLAlchemy
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('postgresql://...')
Session = sessionmaker(bind=engine)
session = Session()
try:
user1 = session.query(User).get(1)
user2 = session.query(User).get(2)
user1.balance -= 100
user2.balance += 100
session.commit() # Успешная транзакция
except Exception:
session.rollback() # Откат при ошибке
raise
finally:
session.close()
# Или более элегантно:
from contextlib import contextmanager
@contextmanager
def get_session():
session = Session()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
with get_session() as session:
user = session.query(User).get(1)
user.balance -= 100
Уровни изоляции (Isolation Levels)
По умолчанию разные БД используют разные уровни:
READ UNCOMMITTED (самый слабый)
- Транзакция видит "грязные" данные (uncommitted changes)
- Самый быстрый, но опасный
READ COMMITTED (по умолчанию в PostgreSQL)
- Видит только подтверждённые данные
- Может быть phantom read
REPEATABLE READ (по умолчанию в MySQL)
- Одинаковые SELECT возвращают одинаковые данные в транзакции
- Защита от non-repeatable read
SERIALIZABLE (самый строгий)
- Полная изоляция, транзакции как будто идут друг за другом
- Самый медленный
from django.db import transaction
# Установить уровень изоляции
with transaction.atomic():
with transaction.set_autocommit(True):
# Код здесь
pass
# SQLAlchemy
from sqlalchemy.pool import StaticPool
engine.execute(
"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"
)
Проблемы при параллельных транзакциях
Dirty Read
# T1: читает uncommitted изменение от T2
T1: SELECT balance FROM users WHERE id = 1 # 1000
T2: UPDATE users SET balance = 500 WHERE id = 1
T1: SELECT balance FROM users WHERE id = 1 # 500 (грязное чтение!)
T2: ROLLBACK # T2 откатилась, но T1 видит 500
Non-Repeatable Read
# T1: одно и то же SELECT возвращает разные результаты
T1: SELECT balance FROM users WHERE id = 1 # 1000
T2: UPDATE users SET balance = 500 WHERE id = 1
T2: COMMIT
T1: SELECT balance FROM users WHERE id = 1 # 500 (изменилось!)
Phantom Read
# T1: количество строк изменилось в одной транзакции
T1: SELECT COUNT(*) FROM orders WHERE status = 'pending' # 5
T2: INSERT INTO orders (status) VALUES ('pending')
T2: COMMIT
T1: SELECT COUNT(*) FROM orders WHERE status = 'pending' # 6 (phantom!)
Best Practices
# 1. Транзакции должны быть короткими
with transaction.atomic():
# Быстрые БД операции
user.balance -= 100
user.save()
# НЕ делай длительные операции здесь!
# 2. Не блокируй внешние ресурсы
with transaction.atomic():
# Плохо
response = requests.get('https://api.example.com') # I/O блокирует!
user.balance -= 100
user.save()
# 3. Используй SELECT FOR UPDATE для критичных операций
with transaction.atomic():
user = User.objects.select_for_update().get(id=1)
# Теперь БД заблокирована для других транзакций
user.balance -= 100
user.save()
# 4. Обработай специфичные исключения
from django.db import IntegrityError
try:
with transaction.atomic():
User.objects.create(email=email)
except IntegrityError:
# Email уже существует
pass
Важные моменты
- Транзакции необходимы для консистентности при множественных операциях
- ACID гарантирует надёжность
- Разные БД, разные реализации и уровни изоляции
- Всегда используй контексты (with) для автоматического commit/rollback
- Для критичных операций используй SELECT FOR UPDATE
- Транзакции замедляют систему — держи их короткими
Транзакции — фундамент надёжной системы работы с БД.