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

Что такое С (согласованность) в принципах ACID?

1.6 Junior🔥 171 комментариев
#DevOps и инфраструктура#Django

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

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

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

Консистентность (Consistency) в ACID — Целостность данных

Консистентность (C в ACID) — это гарантия, что транзакция переводит базу данных из одного согласованного состояния в другое согласованное состояние. Консистентность обеспечивает, что данные в БД всегда остаются в валидном состоянии, соответствуя всем бизнес-правилам и ограничениям целостности.

Определение согласованности

Согласованность означает, что:

  1. Все ограничения целостности соблюдаются (primary key, foreign key, unique, check constraints)
  2. Бизнес-инварианты сохраняются (сумма денег на счёте не может быть отрицательной)
  3. Данные не противоречивы (если 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)
Что такое С (согласованность) в принципах ACID? | PrepBro