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

Какие знаешь принципы ACID?

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

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

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

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

# ACID принципы в базах данных

ACID - это набор из четырёх важнейших принципов, гарантирующих надежность транзакций в базах данных. Расскажу про каждый подробно.

A — Atomicity (Атомарность)

Атомарность означает, что транзакция либо выполняется ПОЛНОСТЬЮ, либо вообще НЕ выполняется. Нет промежуточных состояний.

-- Классический пример: перевод денег
BEGIN TRANSACTION;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;  -- Если успешно, оба UPDATE'а применяются
-- ROLLBACK; -- Если ошибка, оба UPDATE'а откатываются
// PHP пример
DB::transaction(function () {
    $fromAccount = Account::find(1);
    $toAccount = Account::find(2);
    
    // Если произойдет ошибка ДО коммита - оба изменения откатятся
    $fromAccount->decrement('balance', 100);  // Шаг 1
    $toAccount->increment('balance', 100);    // Шаг 2
    
    // Только если оба успешны - коммитим
});

Почему это критично:

  • Если снять со счета, но не положить на другой - деньги потеряются
  • Атомарность гарантирует, что либо ОБА действия выполнены, либо ни одного

C — Consistency (Консистентность)

Консистентность означает, что данные остаются в корректном состоянии ДО и ПОСЛЕ транзакции. Все правила и constraints соблюдаются.

-- Constraints обеспечивают consistency
CREATE TABLE accounts (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2) NOT NULL CHECK (balance >= 0)
);

CREATE TABLE orders (
    id INT PRIMARY KEY,
    user_id INT NOT NULL,
    total DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);
// БД не позволит нарушить consistency
DB::transaction(function () {
    // Попытка создать отрицательный баланс
    Account::find(1)->update(['balance' => -100]);
    // Будет ошибка благодаря CHECK constraint
    
    // Попытка создать заказ несуществующего пользователя
    Order::create(['user_id' => 99999, 'total' => 100]);
    // Будет ошибка благодаря FOREIGN KEY constraint
});

Правила consistency:

  • Primary Key - нет дублей
  • Foreign Key - ссылки на существующие записи
  • Unique constraints - уникальные значения
  • Check constraints - значения соответствуют условиям
  • Not Null - поле не может быть пусто

I — Isolation (Изолированность)

Изолированность означает, что одновременные транзакции не видят незавершенные изменения друг друга. Это предотвращает грязные чтения и other anomalies.

-- Пример без изолированности (грязное чтение)
Transakcija 1:                  Transakcija 2:
BEGIN;                          
UPDATE balance = 500;           
                                BEGIN;
                                SELECT balance; -- 500 (неправильно!)
                                COMMIT;
ROLLBACK;  -- Откатили изменение

-- Баланс вернулся к исходному, но 2-я транзакция видела неправильное значение

Уровни изолированности (от низкого к высокому)

-- 1. READ UNCOMMITTED (самый низкий)
-- Можно читать незавершенные изменения
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
  SELECT balance FROM accounts WHERE id = 1;  -- Может быть грязное значение
COMMIT;

-- 2. READ COMMITTED (по умолчанию в большинстве БД)
-- Читаем только завершенные изменения
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
  SELECT balance FROM accounts WHERE id = 1;  -- Гарантировано завершенное значение
COMMIT;

-- 3. REPEATABLE READ
-- Один снимок данных на всю транзакцию
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
  SELECT * FROM accounts;  -- Снимок 1
  -- ... другие операции ...
  SELECT * FROM accounts;  -- Гарантировано ТАКОЙ ЖЕ результат
COMMIT;

-- 4. SERIALIZABLE (самый высокий)
-- Полная изолированность, как будто транзакции выполняются последовательно
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
  SELECT * FROM accounts FOR UPDATE;  -- Блокируем строки
COMMIT;
// Laravel: как указать уровень изолированности
DB::transaction(function () {
    // Код транзакции
}, null, DB::TRANSACTION_READ_COMMITTED);

// Выбор уровня:
// DB::TRANSACTION_READ_UNCOMMITTED
// DB::TRANSACTION_READ_COMMITTED
// DB::TRANSACTION_REPEATABLE_READ
// DB::TRANSACTION_SERIALIZABLE

Race conditions и их решение

// ❌ Race condition - два процесса могут купить один товар
public function buyProduct(int $productId, int $quantity) {
    $product = Product::find($productId);
    
    if ($product->stock >= $quantity) {
        $product->decrement('stock', $quantity);
        // Между проверкой и обновлением могло случиться что-то
    }
}

// ✅ Решение 1: lockForUpdate
public function buyProduct(int $productId, int $quantity) {
    DB::transaction(function () use ($productId, $quantity) {
        $product = Product::where('id', $productId)
            ->lockForUpdate()  // Блокируем строку
            ->first();
        
        if ($product->stock >= $quantity) {
            $product->decrement('stock', $quantity);
        }
    });
}

// ✅ Решение 2: SELECT FOR UPDATE в raw query
public function buyProduct(int $productId, int $quantity) {
    DB::transaction(function () use ($productId, $quantity) {
        $product = DB::select(
            'SELECT * FROM products WHERE id = ? FOR UPDATE',
            [$productId]
        )[0];
        
        if ($product->stock >= $quantity) {
            DB::update(
                'UPDATE products SET stock = stock - ? WHERE id = ?',
                [$quantity, $productId]
            );
        }
    });
}

D — Durability (Долговечность)

Долговечность означает, что как только транзакция завершена (COMMIT), данные сохранены и не потеряются даже при сбое системы.

BEGIN TRANSACTION;
INSERT INTO accounts (id, balance) VALUES (1, 1000);
COMMIT;  -- После этого данные гарантированно в disk, не в памяти

-- Даже если сервер упадет в следующую секунду,
-- данные будут восстановлены при перезагрузке
// От разработчика
$order = Order::create(['total' => 1000]);
// После save() - данные на диске

// От БД
// - Write-Ahead Logging (WAL) - логируем изменения ДО применения
// - Checkpoint - периодически сохраняем состояние на диск
// - Recovery - при запуске восстанавливаем из логов

Как ACID работают вместе

// Реальный пример: обработка платежа
DB::transaction(function () {
    // A - Atomicity: все шаги выполняются или ничего
    // 1. Создаем заказ
    $order = Order::create(['user_id' => 1, 'total' => 100, 'status' => 'pending']);
    
    // 2. Уменьшаем баланс счета (как при платеже)
    Account::where('id', 1)->decrement('balance', 100);
    
    // 3. Создаем лог платежа
    PaymentLog::create(['order_id' => $order->id, 'amount' => 100]);
    
    // C - Consistency: все constraints соблюдены
    // - user_id существует (FK constraint)
    // - balance >= 0 (CHECK constraint)
    // - total > 0 (CHECK constraint)
    
    // I - Isolation: другие транзакции не видят незавершенные изменения
    // Если в других транзакциях читать баланс/заказы - увидят старые значения
    
    // D - Durability: если COMMIT успешен, данные сохранены
    // Даже если сервер упадет, данные на диске
});

Trade-offs: ACID vs BASE

ACID (традиционные БД):      BASE (NoSQL, распределенные системы):

Strong Consistency           Eventual Consistency
(всегда актуальные данные)   (в итоге консистентные)

Simple transactions           Complex event-driven architecture
Easy to reason about          Better scalability
Slower for high load          Better availability


Когда выбрать ACID:          Когда выбрать BASE:
- Финансовые системы         - Social media
- Критичные данные           - Search engines
- Требуется точность         - High-traffic applications

Практические советы

1. PostgreSQL обеспечивает лучше всего ACID

// PostgreSQL - отличный выбор для ACID
// - Полная поддержка всех уровней изолированности
// - Отличная реализация транзакций
// - MVCC (Multi-Version Concurrency Control) для параллельной работы

DB::connection('pgsql')->transaction(function () {
    // Твой код
});

2. Используй правильный уровень изолированности

// Для большинства случаев достаточно READ COMMITTED
DB::transaction(function () {
    // Обычный код
});

// Для критичных операций - SERIALIZABLE
DB::transaction(function () {
    // Очень критичная логика
}, null, DB::TRANSACTION_SERIALIZABLE);

3. Избегай долгих транзакций

// ❌ Долгая транзакция блокирует ресурсы
DB::transaction(function () {
    $users = User::all(); // 1000 записей
    foreach ($users as $user) {
        sleep(1);  // Долгая операция
        $user->update(['status' => 'processed']);
    }
});

// ✅ Короткие транзакции
foreach (User::all() as $user) {
    sleep(1);  // Вне транзакции
    DB::transaction(function () use ($user) {
        $user->update(['status' => 'processed']);
    });
}

Итог

ACID принципы гарантируют надежность данных:

ПринципГарантируетПрименение
AВсе или ничегоФинансовые переводы
CКорректное состояниеConstraints и rules
IОтсутствие конфликтовПараллельные транзакции
DСохранение данныхRecovery после сбоев

Проектируя систему, подумай:

  • Какие операции требуют ACID?
  • Какой уровень изолированности нужен?
  • Как избежать race conditions?
  • Как обеспечить долговечность?

ACID - не просто теория, это практический инструмент для надежных систем.