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

Приведи пример консистентности в БД

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

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

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

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

Консистентность в базах данных

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

Пример 1: Транзакция перевода денег (классический пример)

Задача

Перевести 100 рублей со счёта A на счёт B.

-- Счета до операции:
SELECT * FROM accounts;
-- A: 1000
-- B: 500

ПЛОХО: без консистентности (без транзакции)

# ❌ Два отдельных SQL запроса
# Запрос 1
update_account_a = "UPDATE accounts SET balance = balance - 100 WHERE id = 'A'"
db.execute(update_account_a)  # ✅ A: 900

# *** В этот момент происходит сбой! ***
# Программа падает, БД перезагружается

# Запрос 2 (не выполняется)
update_account_b = "UPDATE accounts SET balance = balance + 100 WHERE id = 'B'"
db.execute(update_account_b)  # ❌ Не выполнилось

# Результат:
# A: 900 (деньги уехали)
# B: 500 (деньги не пришли)
# 💸 100 рублей потеряны!

Данные в НЕСОГЛАСОВАННОМ состоянии: сумма A + B не сохранилась.

ХОРОШО: с консистентностью (транзакция)

from contextlib import contextmanager
from sqlalchemy import create_engine, text

engine = create_engine('postgresql://user:pass@localhost/bank')

# ✅ Транзакция гарантирует консистентность
with engine.begin() as conn:
    # Начало транзакции
    
    # Шаг 1: Уменьшаем A
    conn.execute(text("""
        UPDATE accounts SET balance = balance - 100 WHERE id = 'A'
    """))
    
    # *** В этот момент происходит сбой! ***
    # Программа падает
    # raise Exception("Connection lost")
    
    # Шаг 2: Увеличиваем B
    conn.execute(text("""
        UPDATE accounts SET balance = balance + 100 WHERE id = 'B'
    """))
    
    # Конец блока: автоматически COMMIT или ROLLBACK
    # Если всё прошло хорошо -> COMMIT
    # Если был exception -> ROLLBACK

# Результат:
# Если всё успешно:
#   A: 900, B: 600 ✅ (консистентно)
# Если ошибка посередине:
#   A: 1000, B: 500 ✅ (откатилось, консистентно)
#
# Никогда не будет A: 900, B: 500 (несогласованное)!

Консистентность гарантирует: либо ОБЕ операции выполнены, либо НИ ОДНА.

Пример 2: Ограничения целостности (Foreign Key)

Модель

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT NOT NULL,
    amount DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

ПЛОХО: без консистентности

-- Вставляем заказ с несуществующим user_id
INSERT INTO orders (user_id, amount) VALUES (999, 100);
-- ✅ Возможно вставится (если нет FK)

-- Результат: осиротелый заказ без пользователя
SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id;
-- | order_id | user_id | amount | user_name |
-- | 1        | 999     | 100    | NULL      | ❌ МУСОР!

-- Потом удаляем пользователя
DELETE FROM users WHERE id = 5;
-- Но его заказы остались в БД
-- Кто это мусор создал? Непонятно.

ХОРОШО: с консистентностью (Foreign Key)

-- Таблица уже создана с FOREIGN KEY
-- Теперь это НЕВОЗМОЖНО:
INSERT INTO orders (user_id, amount) VALUES (999, 100);
-- ERROR: insert or update on table "orders" violates foreign key constraint
-- ✅ БД сама защитила данные!

-- Можем вставить только с существующим user_id
INSERT INTO orders (user_id, amount) VALUES (5, 100);
-- ✅ Успешно

-- Если пытаемся удалить пользователя с заказами:
DELETE FROM users WHERE id = 5;
-- ERROR: update or delete on table "users" violates foreign key constraint
-- ✅ БД не позволит удалить!

-- Или удалить каскадно (тогда мусора не будет)
ALTER TABLE orders
    ADD CONSTRAINT fk_user_id
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE CASCADE;  -- Удалит и заказы

Консистентность гарантирует: никогда не будет осиротелых записей.

Пример 3: Проверка ограничений (Check Constraints)

ПЛОХО: без консистентности

# Приложение должно проверять
if amount <= 0:
    raise ValueError("Amount must be positive")

db.execute(f"INSERT INTO accounts (balance) VALUES ({amount})")

# Проблема: если приложение не проверит, БД примет отрицательное значение
# Другое приложение может не знать про проверку
db.execute(f"INSERT INTO accounts (balance) VALUES (-100)")
# ❌ Отрицательный баланс!

ХОРОШО: с консистентностью (Check Constraint)

CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    balance DECIMAL(10, 2) NOT NULL,
    CHECK (balance >= 0)  -- ✅ Гарантия в БД
);

-- Попытка вставить отрицательное значение
INSERT INTO accounts (balance) VALUES (-100);
-- ERROR: new row for relation "accounts" violates check constraint
-- ✅ БД сама защитила!

-- Правильно
INSERT INTO accounts (balance) VALUES (100);
-- ✅ Успешно

Консистентность гарантирует: данные соответствуют определённым правилам, даже если приложение их забудет проверить.

Пример 4: ACID свойства (Atomicity)

Сценарий: Перевод в системе с несколькими счетами

from sqlalchemy import create_engine, text
from contextlib import contextmanager

engine = create_engine('postgresql://localhost/bank')

def transfer_with_commission(from_account, to_account, amount, commission_percent=2):
    """Перевод денег с комиссией"""
    
    with engine.begin() as conn:  # Начало транзакции
        # Шаг 1: Списываем основную сумму + комиссию
        commission = amount * commission_percent / 100
        total_debit = amount + commission
        
        conn.execute(text("""
            UPDATE accounts
            SET balance = balance - :total_debit
            WHERE id = :account_id
        """), {"total_debit": total_debit, "account_id": from_account})
        
        # Шаг 2: Зачисляем сумму без комиссии
        conn.execute(text("""
            UPDATE accounts
            SET balance = balance + :amount
            WHERE id = :account_id
        """), {"amount": amount, "account_id": to_account})
        
        # Шаг 3: Отправляем комиссию в наш счёт
        conn.execute(text("""
            UPDATE accounts
            SET balance = balance + :commission
            WHERE id = 'BANK_COMMISSION'
        """), {"commission": commission})
    
    # После выхода из блока: либо ВСЕ 3 операции выполнены
    # либо ВСЕ откатаны. Никаких "полуоткатанных" состояний!

# Использование
transfer_with_commission('account_A', 'account_B', 100)

# Результат (если всё успешно):
# A: -102 (выплачена сумма + комиссия)
# B: +100 (получена сумма без комиссии)
# BANK_COMMISSION: +2 (получена комиссия)
#
# Сумма денег в системе сохранена! (Atomicity)

Пример 5: Уникальные ограничения (Unique Constraint)

ПЛОХО: без консистентности

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT
);

-- Можно создать двух пользователей с одним email
INSERT INTO users (email) VALUES ('john@example.com');
INSERT INTO users (email) VALUES ('john@example.com');

SELECT * FROM users WHERE email = 'john@example.com';
-- ДВА строки! ❌ МУСОР!

ХОРОШО: с консистентностью (Unique Index)

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT UNIQUE NOT NULL  -- ✅ Уникальное значение
);

INSERT INTO users (email) VALUES ('john@example.com');
-- ✅ Успешно

INSERT INTO users (email) VALUES ('john@example.com');
-- ERROR: duplicate key value violates unique constraint
-- ✅ БД защитила!

Пример 6: Денормализованные данные (без консистентности)

ПЛОХО: денормализованная таблица

-- Храним сумму в двух местах (BAD PRACTICE)
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    items_total DECIMAL(10, 2),
    tax DECIMAL(10, 2),
    total DECIMAL(10, 2)  -- ❌ Должен быть items_total + tax
);

-- Обновляем items_total
UPDATE orders SET items_total = 100 WHERE id = 1;

-- Забыли обновить total!
SELECT * FROM orders WHERE id = 1;
-- | id | items_total | tax | total |
-- | 1  | 100         | 10  | 90    | ❌ НЕСОГЛАСОВАННО!

ХОРОШО: вычисляемое значение

-- Используем представление (VIEW) для вычисления
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    items_total DECIMAL(10, 2),
    tax DECIMAL(10, 2)
);

CREATE VIEW orders_with_total AS
SELECT
    id,
    items_total,
    tax,
    (items_total + tax) AS total  -- Всегда правильно!
FROM orders;

SELECT * FROM orders_with_total WHERE id = 1;
-- | id | items_total | tax | total |
-- | 1  | 100         | 10  | 110   | ✅ СОГЛАСОВАННО!

-- Или в Python ORM
from sqlalchemy import Column, Integer, Float, computed

class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    items_total = Column(Float)
    tax = Column(Float)
    
    @property
    def total(self):
        return self.items_total + self.tax  # Вычисляется, не хранится

Уровни консистентности

┌──────────────────────────────────────────────┐
│ ACID - Самый строгий (для критичных данных) │
│ - Atomicity: ВСЕ или НИЧЕГО                 │
│ - Consistency: только валидные данные       │
│ - Isolation: независимые транзакции         │
│ - Durability: сохранено на диск             │
└──────────────────────────────────────────────┘
       ↓
┌──────────────────────────────────────────────┐
│ BASE - Более мягкий (для распределённых систем)
│ - Basically Available: система доступна     │
│ - Soft state: состояние может меняться      │
│ - Eventually consistent: консистентность со временем
└──────────────────────────────────────────────┘

Когда использовать какую консистентность

ACID (PostgreSQL, MySQL)

✅ Финансовые системы ✅ Медицинские данные ✅ Критичные бизнес-операции

BASE (MongoDB, Cassandra)

✅ Социальные сети (лайки, комментарии) ✅ Аналитика и логирование ✅ Кэши ✅ Высоконагруженные системы

Выводы

Консистентность — это гарантия, что:

  1. Транзакции атомарны — либо все, либо ничего
  2. Ограничения соблюдаются — FK, UNIQUE, CHECK
  3. Данные не противоречивы — сумма денег сохранена
  4. Мусора не будет — осиротелые записи невозможны
  5. Правила применяются везде — в БД, не только в приложении

Без консистентности приложение всегда в хаосе, с ней — данные надёжны и предсказуемы.

Приведи пример консистентности в БД | PrepBro