Обход ошибки MySQL: «При попытке установить блокировку обнаружена тупиковая ситуация; попробуйте перезапустить транзакцию». - PullRequest
26 голосов
/ 08 апреля 2010

У меня есть таблица MySQL с около 5 000 000 строк, которые постоянно обновляются небольшими способами параллельными процессами Perl, соединяющимися через DBI. Таблица содержит около 10 столбцов и несколько индексов.

Одна довольно распространенная операция иногда приводит к следующей ошибке:

DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction at Db.pm line 276.

Оператор SQL, который вызывает ошибку, выглядит примерно так:

UPDATE file_table SET a_lock = 'process-1234' WHERE param1 = 'X' AND param2 = 'Y' AND param3 = 'Z' LIMIT 47

Ошибка срабатывает только иногда. Я бы оценил в 1% звонков или меньше. Тем не менее, это никогда не происходило с маленькой таблицей и стало более распространенным по мере роста базы данных.

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

Я не особо настраивался на MySQL или DBD :: mysql. MySQL является стандартным развертыванием Solaris, и подключение к базе данных настроено следующим образом:

my $dsn = "DBI:mysql:database=" . $DbConfig::database . ";host=${DbConfig::hostname};port=${DbConfig::port}";
my $dbh = DBI->connect($dsn, $DbConfig::username, $DbConfig::password, { RaiseError => 1, AutoCommit => 1 }) or die $DBI::errstr;

Я видел в Интернете, что несколько других людей сообщали об аналогичных ошибках и что это может быть реальной тупиковой ситуацией.

У меня есть два вопроса:

  1. Что именно в моей ситуации вызывает вышеуказанную ошибку?

  2. Есть ли простой способ обойти это или уменьшить его частоту? Например, как именно я могу «перезапустить транзакцию в строке 276 Db.pm»?

Заранее спасибо.

Ответы [ 4 ]

69 голосов
/ 08 апреля 2010

Если вы используете InnoDB или любую транзакционную СУБД на уровне строк, то возможно, что любая транзакция записи может вызвать взаимоблокировку даже в совершенно нормальных ситуациях. Большие таблицы, большие записи и длинные блоки транзакций часто увеличивают вероятность возникновения взаимоблокировок. В вашей ситуации это, вероятно, комбинация из них.

Единственный способ по-настоящему справиться с взаимоблокировками - написать код, ожидающий их. Это обычно не очень сложно, если код вашей базы данных хорошо написан. Часто вы можете просто поставить try/catch вокруг логики выполнения запроса и искать тупик при возникновении ошибок. Если вы его поймаете, обычная вещь - просто попытаться снова выполнить неудачный запрос.

Я настоятельно рекомендую прочитать эту страницу в руководстве по MySQL. В нем есть список действий, которые помогут справиться с тупиками и снизить их частоту.

9 голосов
/ 16 февраля 2012

Ответ правильный, однако документация perl о том, как обрабатывать взаимоблокировки, немного скудна и, возможно, путает с опциями PrintError, RaiseError и HandleError. Кажется, что вместо использования HandleError, используйте Print и Raise, а затем используйте что-то вроде Try: Tiny, чтобы обернуть ваш код и проверить ошибки. Приведенный ниже код дает пример, в котором код db находится внутри цикла while, который будет повторять ошибочный оператор sql каждые 3 секунды. Блок catch получает $ _, который является конкретным сообщением об ошибке. Я передаю это в функцию-обработчик "dbi_err_handler", которая проверяет $ _ на наличие множества ошибок и возвращает 1, если код должен продолжить (тем самым нарушая цикл), или 0, если это тупик и его следует повторить ...

$sth = $dbh->prepare($strsql);
my $db_res=0;
while($db_res==0)
{
   $db_res=1;
   try{$sth->execute($param1,$param2);}
   catch
   {
       print "caught $_ in insertion to hd_item_upc for upc $upc\n";
       $db_res=dbi_err_handler($_); 
       if($db_res==0){sleep 3;}
   }
}

dbi_err_handler должен иметь как минимум следующее:

sub dbi_err_handler
{
    my($message) = @_;
    if($message=~ m/DBD::mysql::st execute failed: Deadlock found when trying to get lock; try restarting transaction/)
    {
       $caught=1;
       $retval=0; # we'll check this value and sleep/re-execute if necessary
    }
    return $retval;
}

Вы должны включить другие ошибки, которые хотите обработать, и установить $ retval в зависимости от того, хотите ли вы выполнить повторно или продолжить ..

Надеюсь, это кому-нибудь поможет -

7 голосов
/ 16 февраля 2012

Обратите внимание, что если вы используете SELECT FOR UPDATE для проверки уникальности перед вставкой, вы получите тупик для каждого условия гонки, если вы не включите опцию innodb_locks_unsafe_for_binlog.Метод без тупиков для проверки уникальности состоит в том, чтобы вслепую вставить строку в таблицу с уникальным индексом, используя INSERT IGNORE, а затем проверить количество строк, на которое влияют.

добавить строку ниже в my.cnf файл

innodb_locks_unsafe_for_binlog = 1

#

1 - ВКЛ
0 - ВЫКЛ

#
0 голосов
/ 30 марта 2013

Идея повторения запроса в случае исключения Deadlock хороша, но она может быть ужасно медленной, так как запрос mysql будет продолжать ожидать разблокировки блокировки. И в случае тупика mysql пытается выяснить, есть ли какой-либо тупик, и даже после выяснения того, что существует тупик, он ждет некоторое время, прежде чем выбить поток, чтобы выйти из ситуации тупика.

Что я сделал, столкнувшись с такой ситуацией, так это внедрил блокировку в свой собственный код, поскольку именно механизм блокировки mysql не работает из-за ошибки. Поэтому я реализовал собственную блокировку на уровне строк в своем коде Java:

private HashMap<String, Object> rowIdToRowLockMap = new HashMap<String, Object>();
private final Object hashmapLock = new Object();
public void handleShortCode(Integer rowId)
{
    Object lock = null;
    synchronized(hashmapLock)
    {
      lock = rowIdToRowLockMap.get(rowId);
      if (lock == null)
      {
          rowIdToRowLockMap.put(rowId, lock = new Object());
      }
    }
    synchronized (lock)
    {
        // Execute your queries on row by row id
    }
}
...