← Назад к вопросам

Что такое консистентность транзакции?

2.0 Middle🔥 111 комментариев
#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Консистентность транзакции: как БД сохраняет корректность данных

Консистентность (Consistency) — это одно из четырёх ключевых ACID свойств транзакций. Разберусь подробно, что это значит и почему это критично.

ACID и место Consistency

ACID — это четыре свойства, гарантирующие надёжность БД:

  1. Atomicity — либо всё, либо ничего
  2. Consistency — данные всегда в корректном состоянии
  3. Isolation — транзакции не мешают друг другу
  4. 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

Практический чеклист для консистентности

  1. Определи бизнес-правила — какие должны быть инварианты
  2. Реализуй constraints в БД — PRIMARY KEY, FOREIGN KEY, CHECK
  3. Обработай ошибки в коде — валидация данных
  4. Используй транзакции — для атомарности
  5. Выбери правильный isolation level — зависит от требований
  6. Логируй критические операции — для аудита
  7. Тестируй параллелизм — race conditions

Вывод

Консистентность — это гарантия, что данные в БД всегда в корректном состоянии. Она достигается через:

  • Constraints (PRIMARY KEY, FOREIGN KEY, CHECK)
  • Atomicity (либо всё, либо ничего)
  • Isolation (транзакции не мешают друг другу)
  • Приложение-логика (валидация и бизнес-правила)

Без консистентности приложение может потерять деньги, создать невозможные состояния или нарушить бизнес-логику. Это одна из причин, почему БД нужна в 99% приложений, а не просто файлы JSON.

Что такое консистентность транзакции? | PrepBro