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

StackOverflow https://stackoverflow.com/questions/2596005

Вопрос

У меня есть таблица 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»?

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

Это было полезно?

Решение

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

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

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

Другие советы

Ответ правильный, однако документация PERL о том, как обрабатывать тупики, немного редким и, возможно, сбивает с толку PrintError, RoadeError и VericalError. Кажется, что вместо того, чтобы идти с рулем, используйте на печати и поднять, а затем использовать что-то вроде попробуйте: крошечный, чтобы обернуть свой код и проверять наличие ошибок. Ниже приведен код, приведен пример, в котором код БД находится внутри цикла в то время, что повторно выполнит ошибку 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 Чтобы проверить уникальность перед вставкой, вы получите тупик на каждую гонку, если вы не включите innodb_locks_unsafe_for_binlog вариант. Метод без тупика для проверки уникальности - это слепо вставить строку в таблицу с помощью уникального индекса, используя INSERT IGNORE, Затем, чтобы проверить подсчет пораженной строки.

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

innodb_blocks_unsafe_for_binlog = 1.

#

1 - на
0

#

Идея повторного попытки запроса в случае исключения в тупике - это хорошо, но он может быть ужасно медленным, поскольку MySQL Query будет ожидать выпуске замков. И в случае тупика 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
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top