Взаимная блокировка транзакций БД. Как решать проблему?

В данной статье описывается решение проблемы взаимных блокировок транзакций БД, когда одна транзакция блокирует данные и не дает запуститься другой.

Сегодня столкнулись с такой проблемой. Есть группа действия с БД, которые обернуты в транзакцию. На сервер в один момент времени стали поступать запросы на функцию, которая выполняла эти действия. При этом MySQL выдавал следующее сообщение:

SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction

Пользователь получал ошибку, и не мог завершить действие, все введённые данные в форму терялись. Естественно такое поведение не является нормальным, но сохранить данные в тот момент невозможно. Делать без транзакций – тоже не вариант, т.к. они необходимы при обработке наших данных. Первой идеей было увеличение таймаута ожидания транзакции “innodb_lock_wait_timeout”.

innodb_lock_wait_timeout – Время простоя (в секундах), на протяжении которого транзакция InnoDB может ожидать блокировки прежде, чем будет произведен откат. InnoDB автоматически обнаруживает зависшие транзакции в своей таблице блокировок и производит откат транзакций. Если в той же самой транзакции используется команда LOCK TABLES, или другие обработчики таблиц с безопасными транзакциями, отличными от InnoDB, то может возникнуть зависание, которое не будет обнаружено InnoDB. В таких ситуациях параметр времени простоя помогает устранить проблему.

Но это привело бы к тому, что скапливались ожидающие процессы, а это лишнее соединение к БД, лишнее HTTP соединение в пуле nginx, процессорное время и память. Так что, этот способ не подходит.

Было решено сделать следующим образом. Все ошибки, вызывают исключение в коде, которое в Zend Framework перехватывается с помощью error_handler, управление зетем передается в ErrorController, где можно узнать код и текст ошибки. При наличии такой ошибки, пользователю нужно выдавать страницу с сообщением вида “В данный момент сервер не может обработать ваш запрос. Попробуйте через несколько секунд” и добавить таймер обратного отсчета, секунд на 15, и кнопку “обновить”. После того, как сработает таймер или юзер нажмет кнопку, HTTP-запрос отправится повторно.

Важно будет сохранить данные запроса в скрытой форме, а потом отправить её. Не следует делать это через jQuery::post(), т.к. она отправит Ajax-запрос, а нам надо отправить обычный HTTP-запрос, отобразив его результат. За 15 секунд все транзакции успеют рассосаться, и мы не будем висеть и ждать вместе с БД.

Leave a Comment