Что такое С (согласованность) в принципах ACID?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Консистентность (Consistency) в ACID — Целостность данных
Консистентность (C в ACID) — это гарантия, что транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние. Консистентность обеспечивает, что данные в БД всегда остаются в валидном состоянии, соответствуя всем бизнес-правилам и ограничениям целостности.
Определение согласованности
Согласованность означает, что:
- Все ограничения целостности соблюдаются (primary key, foreign key, unique, check constraints)
- Бизнес-инварианты сохраняются (сумма денег на счёте не может быть отрицательной)
- Данные не противоречивы (если A ссылается на B, то B должен существовать)
Пример 1: Перевод денег между счётами
# Бизнес-инвариант: сумма денег на счёте >= 0
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
engine = create_engine('postgresql://user:password@localhost/db')
# Согласованные данные ДО транзакции:
# account_1: balance = 1000
# account_2: balance = 500
# ИНВАРИАНТ: все балансы >= 0
def transfer_money(from_id: int, to_id: int, amount: float):
with Session(engine) as session:
try:
# Начало транзакции (ACID гарантия)
from_account = session.query(Account).filter_by(id=from_id).first()
to_account = session.query(Account).filter_by(id=to_id).first()
# Проверка инварианта: достаточно денег?
if from_account.balance < amount:
raise ValueError('Недостаточно средств')
# Операции
from_account.balance -= amount
to_account.balance += amount
session.commit()
# Согласованные данные ПОСЛЕ транзакции:
# account_1: balance = 900
# account_2: balance = 600
# Инвариант сохранён: все балансы >= 0
except Exception:
session.rollback()
# При ошибке откатываем ВСЕ изменения
# Данные остаются в согласованном состоянии
raise
Без Consistency:
- Если система упадёт между уменьшением счёта A и увеличением счёта B
- Деньги просто исчезнут из системы (нарушение инварианта)
С Consistency:
- ACID гарантирует, что либо обе операции выполнены, либо ни одна
- Деньги либо переведены полностью, либо остаются на исходном счёте
Пример 2: Ограничения целостности (Constraints)
from sqlalchemy import Column, Integer, String, ForeignKey, CheckConstraint
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False) # NOT NULL constraint
email = Column(String, unique=True) # UNIQUE constraint
age = Column(Integer, CheckConstraint('age >= 18')) # CHECK constraint
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False) # FK constraint
total = Column(Integer)
# БД не позволит создать:
# - User без имени (нарушение NOT NULL)
# - User с дублирующимся email (нарушение UNIQUE)
# - User с age < 18 (нарушение CHECK)
# - Order с несуществующим user_id (нарушение FOREIGN KEY)
# Все эти попытки будут откачены (rolled back)
Уровни изоляции и их влияние на консистентность
1. Read Uncommitted (самый слабый, НЕ ACID)
# Transaction A может прочитать изменения Transaction B до её коммита
# Риск: Dirty Read (чтение грязных данных)
# Session 1 Session 2
set_balance(acc1, 1000)
balance = get_balance(acc1) # Может быть 1000 (промежуточное!)
rollback() # Откат
# Session 2 прочитала "грязные" данные
# Консистентность нарушена!
2. Read Committed (стандартный уровень)
# Возможны: Non-Repeatable Read, Phantom Read
# Но НЕ Dirty Read
from sqlalchemy import create_engine
from sqlalchemy.pool import StaticPool
engine = create_engine(
'postgresql://user:password@localhost/db',
execution_options={'isolation_level': 'READ_COMMITTED'} # По умолчанию
)
3. Repeatable Read (более строгий)
# Одна транзакция не видит изменения другой после первого чтения
# PostgreSQL это использует по умолчанию
engine = create_engine(
'postgresql://user:password@localhost/db',
execution_options={'isolation_level': 'REPEATABLE_READ'}
)
4. Serializable (самый строгий, максимальная консистентность)
# Транзакции выполняются так, как будто выполняются последовательно
# Максимально медленно, но максимально безопасно
engine = create_engine(
'postgresql://user:password@localhost/db',
execution_options={'isolation_level': 'SERIALIZABLE'}
)
with Session(engine) as session:
# Эта транзакция полностью изолирована от других
session.execute(select(User))
session.commit()
Triggers и Stored Procedures для обеспечения консистентности
Бизнес-логика можно вложить в БД для гарантии консистентности:
from sqlalchemy import create_engine, text
engine = create_engine('postgresql://user:password@localhost/db')
with engine.connect() as conn:
# Trigger для аудита изменений
conn.execute(text('''
CREATE TRIGGER audit_user_changes
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
INSERT INTO audit_log (user_id, old_name, new_name, changed_at)
VALUES (NEW.id, OLD.name, NEW.name, NOW());
END;
'''))
conn.commit()
# Теперь любое обновление юзера автоматически логируется
# БД гарантирует эту консистентность на уровне хранилища
Пример: Race condition и консистентность
# Проблема: Race condition нарушает консистентность
# Сценарий: двое пытаются снять деньги со счёта одновременно
def withdraw_unsafe(account_id: int, amount: float):
# БЕЗ лока
with Session(engine) as session:
account = session.query(Account).filter_by(id=account_id).first()
if account.balance >= amount: # Проверка 1
account.balance -= amount # Изменение
session.commit()
# Потокобезопасная версия с локом
def withdraw_safe(account_id: int, amount: float):
# С SELECT FOR UPDATE LOCK
with Session(engine) as session:
account = session.query(Account).filter_by(id=account_id).with_for_update().first()
if account.balance >= amount: # Проверка защищена локом
account.balance -= amount
session.commit()
Откат транзакции (Rollback) для восстановления консистентности
from sqlalchemy.exc import IntegrityError
with Session(engine) as session:
try:
# Операция 1
user = User(name='Alice', email='alice@example.com', age=25)
session.add(user)
# Операция 2 — ошибка (дублирующийся email)
user2 = User(name='Bob', email='alice@example.com', age=30)
session.add(user2)
session.commit()
except IntegrityError:
session.rollback() # Откатываем обе операции!
# Ни Alice, ни Bob не будут добавлены
# БД остаётся в согласованном состоянии
Проверка консистентности в приложении
class ConsistencyChecker:
@staticmethod
def validate_transfer(from_acc: Account, to_acc: Account, amount: float) -> bool:
"""Проверяет инварианты перед операцией"""
# Инвариант 1: Счёт отправителя существует
if not from_acc:
raise ValueError('Счёт отправителя не найден')
# Инвариант 2: Счёт получателя существует
if not to_acc:
raise ValueError('Счёт получателя не найден')
# Инвариант 3: Достаточно денег
if from_acc.balance < amount:
raise ValueError('Недостаточно средств')
# Инвариант 4: Положительная сумма
if amount <= 0:
raise ValueError('Сумма должна быть положительной')
return True
# Использование
try:
ConsistencyChecker.validate_transfer(from_acc, to_acc, 100)
perform_transfer(from_acc, to_acc, 100)
except ValueError as e:
logger.error(f'Нарушение консистентности: {e}')
Резюме: Консистентность в ACID — это гарантия, что каждая транзакция переводит БД из одного согласованного состояния в другое, не нарушая бизнес-правила и ограничения целостности. Без консистентности данные могут быть противоречивы и ненадёжны. Она обеспечивается через:
- Constraints (PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK)
- Triggers и Stored Procedures
- Уровни изоляции транзакций
- Откаты при ошибках (Rollback)