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

В проекте существует таблица с аналитическими данными об регионах, с которыми работает компания. Обычно регионы, с которыми работает компания, только добавляются и исключительно редко регион может исключён из работы. На регион есть внешние ключи из других таблиц.

Честная реализация задачи будет следующей: 1 Успешно запросить данные от хранилища данных 2 Запросить данные из локальной БД 3 Найти пересечение данных - эти записи мы обновим 4 Найти разность данных из локальной БД и из удаленной: эти записи нужно удалить, если на них нет внешних ссылок 5 Найти разность удаленных данных и из локальной БД: эти записи нужно добавить

Но с учетом того, что записи редко удаляются, я решил сделать по быстрому по нечестной схеме: 1 Успешно запросить данные от хранилища данных 2 Открыть транзакцию 2 Отменить проверку ключей 3 Удалить все записи из таблицы 4 Вставить все записи которые пришли из удаленного хранилища 5 Закоммитить транзакцию, если не получилось, то логировать ошибку, чтобы знать какой внешний ключ не удалось проверить

В инструкции к yii(и не только к yii) для транзакций приводится следующий код:

 $transaction=$connection->beginTransaction();
 try
 {
     $connection->createCommand($sql1)->execute();
     $connection->createCommand($sql2)->execute();
     //… прочие SQL-запросы
     $transaction->commit();
 }
 catch(Exception $e) // в случае возникновения ошибки при выполнении одного из запросов выбрасывается исключение
 {
     $transaction->rollback();
 }

Сам код не вызывает вопросов:

  • мы открываем транзакцию
  • выполняем комманды
  • если все хорошо, то комитем транзацию, если нет , то откатываем транзакцию.

    После старта скрипта получаем 2 ошибки:

    SQLSTATE[23503]: Foreign key violation: 7 ОШИБКА:  UPDATE или DELETE в таблице "region" нарушает ограничение внешнего ключа 
    
    exception 'PDOException' with message 'There is no active transaction' 

    Первая ошибка понятна: в БД есть записи, которые ведут на регионы, которые больше не поддерживаются.

    А вот вторая ошибка вызывает вопросы: фактически она означает следующее в блоке try произощла ошибка, при этом, когда выполнялся код catch, транзакция на уровне pdo уже не существует. Что интересно - транзакция на уровне yii еще есть.

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

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

После того как причины поведения стали понятны, исправление получилось тривиальным. В будущем, я бы советовал реализовывать честные варианты. Иногда попытка сделать побыстрее приводит к более долгому варианту.