Что такое консистентность транзакции?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Консистентность транзакции: как БД сохраняет корректность данных
Консистентность (Consistency) — это одно из четырёх ключевых ACID свойств транзакций. Разберусь подробно, что это значит и почему это критично.
ACID и место Consistency
ACID — это четыре свойства, гарантирующие надёжность БД:
- Atomicity — либо всё, либо ничего
- Consistency — данные всегда в корректном состоянии
- Isolation — транзакции не мешают друг другу
- Durability — данные не теряются после коммита
Что такое консистентность
Консистентность означает, что БД переходит из одного корректного состояния в другое корректное состояние. Никогда не будет промежуточного "сломанного" состояния, видимого другим транзакциям.
Примеры корректного состояния (бизнес-правила):
- В банке: сумма денег на счётах всегда сохраняется (перевод не может потерять деньги)
- В интернет-магазине: если товар продан, количество уменьшилось и зачислены деньги
- В социальной сети: если пользователь подписан на другого, связь всегда обоюдная
Классический пример: переводы между счётами
# Бизнес-правило: сумма денег сохраняется
beginning_balance_account_a = 1000 # У Алисы
beginning_balance_account_b = 500 # У Боба
total_before = beginning_balance_account_a + beginning_balance_account_b # 1500
# Транзакция: Алиса отправляет Бобу 200
def transfer(from_account, to_account, amount):
db.execute("""
UPDATE accounts
SET balance = balance - $1
WHERE id = $2
""", (amount, from_account))
db.execute("""
UPDATE accounts
SET balance = balance + $1
WHERE id = $2
""", (amount, to_account))
transfer('alice', 'bob', 200)
# После транзакции
end_balance_account_a = 800 # 1000 - 200
end_balance_account_b = 700 # 500 + 200
total_after = end_balance_account_a + end_balance_account_b # 1500
# ✅ Консистентно: total_before == total_after
Почему консистентность важна
Сценарий без консистентности:
1. Отнять 200 у Алисы: Alice = 800
2. СБОЙ! (например, отключение электричества)
3. Боб не получил 200: Bob = 500
Результат: потеряно 200! Деньги испарились.
С консистентностью (Atomicity + Consistency):
1. Начало транзакции
2. Отнять 200 у Алисы: Alice = 800
3. Добавить 200 к Бобу: Bob = 700
4. СБОЙ! Но база ещё не закоммитила...
5. Откат! Alice = 1000, Bob = 500
Результат: либо обе операции успешны, либо откатились. Деньги не потеряны.
Консистентность на уровне БД
БД проверяет constraints — правила, которые должны быть всегда истины:
CREATE TABLE accounts (
id INT PRIMARY KEY,
balance DECIMAL(10, 2),
CONSTRAINT non_negative_balance CHECK (balance >= 0)
);
Если какая-то транзакция нарушает constraint, БД откатывает её:
# ❌ Эта операция нарушает constraint
db.execute("""
UPDATE accounts
SET balance = -100
WHERE id = 1
""")
# CONSTRAINT VIOLATION! Откат.
Примеры constraints, обеспечивающих консистентность
-- Primary Key: каждый ID уникален
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100)
);
-- Foreign Key: связь между таблицами корректна
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT REFERENCES users(id),
amount DECIMAL(10, 2)
);
-- Нельзя создать заказ для несуществующего пользователя
-- CHECK constraint: бизнес-правило
CREATE TABLE products (
id INT PRIMARY KEY,
price DECIMAL(10, 2),
CONSTRAINT positive_price CHECK (price > 0)
);
-- UNIQUE constraint: не может быть дубликатов
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(100) UNIQUE
);
-- Нельзя создать двух пользователей с одинаковым email
-- NOT NULL: поле обязательно
CREATE TABLE comments (
id INT PRIMARY KEY,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
Консистентность vs Isolation
Это часто путают. Разница:
| Консистентность | Isolation |
|---|---|
| Данные в корректном состоянии | Транзакции не видят чужие незакоммиченные изменения |
| Проверяются constraints | Контролирует видимость данных |
| Может проверяться приложением | Зависит от уровня изоляции |
Пример:
# Транзакция 1
db.transaction().start()
db.execute("UPDATE accounts SET balance = 800 WHERE id = 1")
# Консистентность: проверяет, что balance >= 0
# Isolation: другие транзакции НЕ видят 800 до коммита
db.execute("UPDATE accounts SET balance = 700 WHERE id = 2")
db.transaction().commit() # Теперь видят оба изменения
Консистентность в приложении
Не все правила можно проверить на уровне БД. Некоторые проверяются в коде:
def transfer(from_account, to_account, amount):
"""Гарантировать консистентность: деньги не потеряются"""
# 1. Бизнес-правило: нельзя отправить отрицательную сумму
if amount <= 0:
raise ValueError("Amount must be positive")
# 2. Бизнес-правило: есть ли деньги на счёте
current_balance = db.get_balance(from_account)
if current_balance < amount:
raise InsufficientFundsError()
# 3. Атомарная операция: либо обе, либо ни одна
with db.transaction():
db.execute("""
UPDATE accounts
SET balance = balance - $1
WHERE id = $2
""", (amount, from_account))
db.execute("""
UPDATE accounts
SET balance = balance + $1
WHERE id = $2
""", (amount, to_account))
# Логирование для аудита
db.log_transaction({
'from': from_account,
'to': to_account,
'amount': amount,
'timestamp': datetime.now()
})
Консистентность и параллелизм
Когда множество транзакций работают параллельно:
# Thread 1: Алиса отправляет Бобу 200
with db.transaction():
alice_balance = db.get_balance('alice') # Читает 1000
bob_balance = db.get_balance('bob') # Читает 500
db.update_balance('alice', alice_balance - 200)
db.update_balance('bob', bob_balance + 200)
# Thread 2: Чарли отправляет Алисе 100 (параллельно!)
with db.transaction():
charlie_balance = db.get_balance('charlie') # Читает 300
alice_balance = db.get_balance('alice') # ❌ Читает 1000 (ещё не обновлено!)
db.update_balance('charlie', charlie_balance - 100)
db.update_balance('alice', alice_balance + 100)
# Результат:
# Alice должна быть: 1000 - 200 + 100 = 900
# Но может быть: 1100 (если не было синхронизации)
Для решения используют уровни изоляции:
# Самый безопасный: SERIALIZABLE
with db.transaction(isolation_level='SERIALIZABLE'):
pass
# Компромисс: REPEATABLE READ
with db.transaction(isolation_level='REPEATABLE READ'):
pass
# Менее безопасный: READ COMMITTED (default в PostgreSQL)
with db.transaction(isolation_level='READ COMMITTED'):
pass
Практический чеклист для консистентности
- Определи бизнес-правила — какие должны быть инварианты
- Реализуй constraints в БД — PRIMARY KEY, FOREIGN KEY, CHECK
- Обработай ошибки в коде — валидация данных
- Используй транзакции — для атомарности
- Выбери правильный isolation level — зависит от требований
- Логируй критические операции — для аудита
- Тестируй параллелизм — race conditions
Вывод
Консистентность — это гарантия, что данные в БД всегда в корректном состоянии. Она достигается через:
- Constraints (PRIMARY KEY, FOREIGN KEY, CHECK)
- Atomicity (либо всё, либо ничего)
- Isolation (транзакции не мешают друг другу)
- Приложение-логика (валидация и бизнес-правила)
Без консистентности приложение может потерять деньги, создать невозможные состояния или нарушить бизнес-логику. Это одна из причин, почему БД нужна в 99% приложений, а не просто файлы JSON.