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

Что такое согласованность в ACID?

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

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

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

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

Ответ: Согласованность (Consistency) в ACID

Consistency (C в ACID) — это свойство, гарантирующее что база данных переходит из одного корректного состояния в другое. Все правила и ограничения целостности данных соблюдаются ДО и ПОСЛЕ транзакции.

Основное определение

ACID — это четыре свойства надёжных транзакций:

  • Atomicity (Атомарность)
  • Consistency (Согласованность)
  • Isolation (Изоляция)
  • Durability (Долговечность)

Consistency означает, что каждая транзакция должна быть валидной с точки зрения бизнес-правил.

Классический пример: Перевод денег

// Транзакция: Перевести 100 рублей со счёта A на счёт B

// НАЧАЛЬНОЕ СОСТОЯНИЕ (согласованное)
Счёт A: 500 рублей
Счёт B: 200 рублей
Общее: 700 рублей

// ПРОМЕЖУТОЧНОЕ СОСТОЯНИЕ (может быть несогласованным)
// Вычитали из A, но ещё не добавили в B
Счёт A: 400 рублей
Счёт B: 200 рублей
Общее: 600 рублей (НЕПРАВИЛЬНО! Деньги потеряны)

// ФИНАЛЬНОЕ СОСТОЯНИЕ (должно быть согласованным)
Счёт A: 400 рублей
Счёт B: 300 рублей
Общее: 700 рублей (ПРАВИЛЬНО! Согласованность соблюдена)

Инвариант (правило): Сумма всех счётов всегда = 700. Consistency гарантирует, что после транзакции этот инвариант соблюдается.

Реализация в базе данных

@Entity
public class BankAccount {
    @Id
    private Long id;
    
    @Column(nullable = false)  // Ограничение целостности: баланс не может быть NULL
    private BigDecimal balance;
    
    @Column(nullable = false)  // Ограничение целостности
    private String accountNumber;
}

@Service
@Transactional  // Гарантирует атомарность + консистентность
public class BankService {
    @Autowired
    private AccountRepository accountRepository;
    
    // Бизнес-правило: баланс не может быть отрицательным
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        // 1. Получить исходное состояние
        BankAccount from = accountRepository.findById(fromId).orElseThrow();
        BankAccount to = accountRepository.findById(toId).orElseThrow();
        
        // 2. Проверить согласованность ПЕРЕД изменениями
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException("Not enough balance");
        }
        
        // 3. Изменить состояние
        from.setBalance(from.getBalance().subtract(amount));
        to.setBalance(to.getBalance().add(amount));
        
        // 4. Сохранить (БД проверит все ограничения целостности)
        accountRepository.save(from);  // Проверка: balance >= 0?
        accountRepository.save(to);    // Проверка: balance не NULL?
        
        // 5. При успешной транзакции — состояние согласованное
        // При ошибке — ROLLBACK, и БД остаётся в старом согласованном состоянии
    }
}

Три уровня согласованности

1. Согласованность на уровне БД (Data Integrity)

Ограничения, которые проверяет сама БД:

-- Primary Key - гарантирует уникальность
CREATE TABLE users (
    id INT PRIMARY KEY,
    name VARCHAR(100) NOT NULL  -- NOT NULL ограничение
);

-- Foreign Key - гарантирует целостность ссылок
CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
        ON DELETE CASCADE  -- При удалении пользователя удалить заказы
);

-- CHECK - проверка значения
CREATE TABLE products (
    id INT PRIMARY KEY,
    price DECIMAL(10,2),
    CHECK (price > 0)  -- Цена всегда больше 0
);

-- UNIQUE - уникальность
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE  -- Только один пользователь на email
);

Эти ограничения защищают от невалидных данных:

-- Попытка вставить заказ несуществующего пользователя
INSERT INTO orders (id, user_id) VALUES (1, 999);  -- ОШИБКА!
-- FOREIGN KEY violation - user_id = 999 не существует

-- Попытка вставить отрицательную цену
INSERT INTO products (id, price) VALUES (1, -10);  -- ОШИБКА!
-- CHECK constraint violation - price должна быть > 0

2. Согласованность на уровне приложения (Business Logic Consistency)

Правила, которые проверяет Java код:

@Service
public class OrderService {
    @Transactional
    public void createOrder(Order order) {
        // Бизнес-правило 1: заказ должен иметь хотя бы один товар
        if (order.getItems().isEmpty()) {
            throw new InvalidOrderException("Order must have items");
        }
        
        // Бизнес-правило 2: сумма заказа = сумма товаров
        BigDecimal calculatedSum = order.getItems().stream()
            .map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        if (!calculatedSum.equals(order.getTotalAmount())) {
            throw new InvalidOrderException("Total amount mismatch");
        }
        
        // Бизнес-правило 3: скидка не может превышать 50%
        BigDecimal discountPercent = order.getDiscount()
            .divide(order.getTotalAmount(), RoundingMode.HALF_UP)
            .multiply(BigDecimal.valueOf(100));
        
        if (discountPercent.compareTo(BigDecimal.valueOf(50)) > 0) {
            throw new InvalidOrderException("Discount cannot exceed 50%");
        }
        
        // Если все проверки пройдены — состояние согласованное
        orderRepository.save(order);
    }
}

3. Согласованность на уровне распределённых систем (Distributed Consistency)

// Проблема: две базы данных должны быть в согласованном состоянии
// База данных 1: Наличие товара
// База данных 2: История продаж

@Service
public class DistributedService {
    @Autowired
    private InventoryService inventoryService;  // Одна БД
    
    @Autowired
    private AnalyticsService analyticsService;  // Другая БД
    
    @Transactional
    public void sellProduct(String productId, int quantity) {
        // Шаг 1: Уменьшить наличие
        inventoryService.decreaseStock(productId, quantity);
        
        // Шаг 2: Записать в историю (другая БД!)
        analyticsService.logSale(productId, quantity);
        
        // ПРОБЛЕМА: Если шаг 2 падёт, то инвентарь уменьшен,
        // но продажа не записана. Система в НЕСОГЛАСОВАННОМ состоянии!
    }
}

// РЕШЕНИЕ: использовать Saga паттерн или 2PC (Two-Phase Commit)
@Service
public class SagaOrderService {
    @Transactional
    public void sellWithSaga(String productId, int quantity) {
        try {
            // Шаг 1: Зарезервировать товар
            inventoryService.reserve(productId, quantity);
            
            // Шаг 2: Записать продажу
            analyticsService.logSale(productId, quantity);
            
            // Шаг 3: Подтвердить
            inventoryService.confirm(productId, quantity);
        } catch (Exception e) {
            // ОТКАТ: отменить резервирование
            inventoryService.release(productId, quantity);
            throw e;
        }
    }
}

Consistency vs Isolation

Часто путают эти два свойства:

СвойствоОписаниеПример
ConsistencyЧТО проверяется (правила целостности)Баланс >= 0
IsolationКАК происходит (отделение транзакций друг от друга)Две транзакции не видят друг друга промежуточные состояния
// Consistency нарушена: баланс < 0
Transaction 1:
    balance = 100
    balance -= 200  // баланс = -100 (ОШИБКА!)
    ROLLBACK

// Isolation нарушена: Transaction 2 видит промежуточное состояние
Transaction 1:       Transaction 2:
    balance = 100
    balance -= 50   // balance = 50
                    balance = ?  // видит 50 или 100? (Isolation нарушена)
    COMMIT

Как БД обеспечивает Consistency

  1. Проверка ограничений перед COMMIT

    BEGIN TRANSACTION
    UPDATE accounts SET balance = -100 WHERE id = 1;
    -- БД обнаружила нарушение CHECK (balance > 0)
    ROLLBACK  -- Откатить транзакцию
    
  2. Каскадные действия

    -- Если удалили пользователя, автоматически удалить заказы
    DELETE FROM users WHERE id = 1;
    -- ON DELETE CASCADE срабатывает автоматически
    
  3. Транзакционная логика

    @Transactional  // Вся логика как одна атомарная операция
    public void complexOperation() {
        // Операция 1
        // Операция 2
        // Операция 3
        // Если ошибка на шаге 3 — ВСЁ откатывается
    }
    

Практический чеклист

✅ Определи все бизнес-правила (инварианты) ✅ Реализуй их как:n - Ограничения БД (PRIMARY KEY, FOREIGN KEY, CHECK, NOT NULL)

  • Валидацию в коде приложения
  • Логику в @Transactional методах ✅ Используй ROLLBACK при нарушении правил ✅ Тестируй edge cases (что если данные невалидны?) ✅ Логируй нарушения согласованности

Итоговый вывод

Consistency в ACID — это гарантия того, что база данных соответствует всем бизнес-правилам ДО и ПОСЛЕ транзакции. Нарушение консистентности означает, что данные вошли в невалидное состояние. Обеспечивается комбинацией:

  1. Ограничений целостности БД
  2. Валидации в приложении
  3. Атомарности транзакций

В Java это реализуется через @Transactional аннотации, проверку бизнес-правил и правильное использование ORM фреймворков.