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

Что будет при параллельном выполнении транзакций?

2.2 Middle🔥 171 комментариев
#Архитектура и паттерны#Базы данных и SQL

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

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

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

Концепция параллельных транзакций и проблемы, возникающие при их выполнении

При параллельном выполнении транзакций несколько пользователей или процессов одновременно пытаются читать и изменять данные в базе данных. Без должного контроля это приводит к конфликтам, нарушению целостности данных и некорректным результатам. Основные проблемы можно разделить на три классические категории.

Основные проблемы конкурентного доступа

1. Проблема потерянных обновлений Когда две транзакции читают одну запись, затем независимо её изменяют и пытаются записать. Вторая транзакция "перезаписывает" изменения первой, полностью их теряя.

-- Транзакция A: читает баланс (100$), добавляет 50$, записывает 150$
-- Транзакция B: читает баланс (100$), списывает 30$, записывает 70$
-- Фактический результат: 70$, потеряны изменения транзакции A

2. Проблема "грязного" чтения Транзакция читает данные, которые были изменены другой, ещё незавершённой транзакцией. Если эта транзакция откатится, первая будет работать с несуществующими ("грязными") данными.

-- Транзакция A: изменяет баланс с 100$ на 200$ (но ещё не завершилась)
-- Транзакция B: читает новый баланс 200$
-- Транзакция A: откатывается, баланс возвращается к 100$
-- Транзакция B: работает с некорректным значением 200$

3. Проблема невоспроизводимого чтения Транзакция дважды читает одну запись, но между этими чтениями другая транзакция изменяет данные, поэтому результаты двух чтений внутри одной транзакции различаются.

-- Транзакция A: первый SELECT получает баланс 100$
-- Транзакция B: изменяет баланс на 150$ и завершается
-- Транзакция A: второй SELECT получает баланс 150$
-- Внутри одной транзакции получены разные значения для одной записи

Решения: уровни изоляции транзакций

Для управления этими проблемами SQL стандарт определяет уровни изоляции транзакций, которые регулируют, "сколько конфликтов" мы допускаем ради повышения параллельности.

-- В PostgreSQL установка уровня изоляции для текущей транзакции
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

В контексте PHP и базы данных мы обычно управляем этим через драйвер или ORM:

// Пример с Doctrine ORM в PHP
$entityManager->getConnection()->setTransactionIsolationLevel(
    \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE
);

// Или на уровне конкретной транзакции в PDO
$pdo->beginTransaction();
// Уровень изоляции зависит от настроек драйвера и базы данных

Основные уровни изоляции:

  • READ UNCOMMITTED: Минимальная изоляция. Возможны все три проблемы. Используется редко.
  • READ COMMITTED: Базовая изоляция (часто default). Защищает от "грязного" чтения, но возможны потерянные обновления и невоспроизводимое чтение.
  • REPEATABLE READ: Гарантирует, что данные, прочитанные в транзакции, не изменятся другими транзакциями. Защищает от грязного чтения и невоспроизводимого чтения, но возможны потерянные обновления в некоторых реализациях.
  • SERIALIZABLE: Максимальная изоляция. Транзакции выполняются так, как если бы они были строго последовательными. Защищает от всех проблем, но серьёзно снижает параллельность и может вызывать ошибки из-за конфликтов сериализации.

Практические подходы в разработке PHP Backend

Блокировки — механизм явного контроля конкурентного доступа:

  • Пессимистичные блокировки: Предполагаем, что конфликты часты, блокируем данные заранее (SELECT ... FOR UPDATE в SQL).
  • Оптимистичные блокировки: Предполагаем, что конфликты редки, проверяем их при коммите (версионирование записей).
// Пример пессимистичной блокировки в SQL для MySQL
$stmt = $pdo->prepare('SELECT * FROM accounts WHERE id = :id FOR UPDATE');
$stmt->execute(['id' => $accountId]);
// Теперь эта запись заблокирована для других транзакций до конца нашей

// Пример оптимистичной блокировки в PHP (версионирование)
class Account {
    private $id;
    private $balance;
    private $version; // Специальное поле для контроля изменений
}
// При UPDATE проверяем, что version не изменился с момента чтения

В современных PHP приложениях:

  • ORM-системы (Doctrine, Eloquent) часто предоставляют абстракции для управления транзакциями и уровнями изоляции.
  • Шаблон "Unit of Work" помогает группировать изменения и коммитить их атомарно.
  • Для высоконагруженных систем рассматривают альтернативные подходы: event sourcing, CQRS, или компенсационные транзакции вместо классических ACID.

Выбор стратегии зависит от требований:

  • Для финансовых операций — строгая изоляция (SERIALIZABLE) или пессимистичные блокировки.
  • Для большинства веб-приложений — READ COMMITTED или REPEATABLE READ с оптимистичными блокировками, чтобы балансировать между целостностью и производительностью.
  • Ключевое — понимать бизнес-логику: какие данные критичны для параллельного изменения, и применять соответствующие механизмы защиты.