Приведи пример консистентности в БД
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Консистентность в базах данных
Консистентность (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)
✅ Социальные сети (лайки, комментарии) ✅ Аналитика и логирование ✅ Кэши ✅ Высоконагруженные системы
Выводы
Консистентность — это гарантия, что:
- Транзакции атомарны — либо все, либо ничего
- Ограничения соблюдаются — FK, UNIQUE, CHECK
- Данные не противоречивы — сумма денег сохранена
- Мусора не будет — осиротелые записи невозможны
- Правила применяются везде — в БД, не только в приложении
Без консистентности приложение всегда в хаосе, с ней — данные надёжны и предсказуемы.