Работать вокруг ошибки MySQL "взаимоблокировка при попытке получить блокировку; попробуйте перезапустить транзакцию"

у меня есть таблица 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. есть ли простой способ обойти его или уменьшить его частоту? Например, как именно я могу "перезапустить транзакцию в Db.линия pm 276"?

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

4 ответов


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

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

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


ответ правильный, однако документация perl о том, как обрабатывать тупики, немного разрежена и, возможно, путается с параметрами PrintError, RaiseError и HandleError. Кажется, что вместо того, чтобы идти с HandleError, используйте на Print и Raise, а затем используйте что-то вроде Try:Tiny, чтобы обернуть свой код и проверить ошибки. В приведенном ниже коде приведен пример, когда код БД находится внутри цикла while, который будет повторно выполнять ошибочную инструкцию sql каждые 3 секунды. Блок catch получает $_ который является специфичным сообщение ERR. Я передаю это функции обработчика "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 в зависимости от того, хотите ли вы повторно выполнить или продолжать..

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


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

добавить ниже строки

innodb_locks_unsafe_for_binlog = 1

#

1-ON
Ноль - Выкл

#

идея повторной попытки запроса в случае исключения взаимоблокировки хороша, но она может быть ужасно медленной, так как запрос 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
    }
}