Подтвердить что ты не робот

Работа над ошибкой 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;

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Ответ верный, однако документация 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 в зависимости от того, хотите ли вы повторно выполнить или продолжить.

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

Ответ 3

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

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

innodb_locks_unsafe_for_binlog = 1

#

1 - ON
0 - ВЫКЛ

#

Ответ 4

Идея повторного запроса запроса в случае исключения 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
    }
}