Работать вокруг ошибки 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"?
спасибо заранее.
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
}
}