Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# 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 - не просто теория, это практический инструмент для надежных систем.