Работая вокруг ошибки MySQL «Toblock найден при попытке получить замок; попробуйте перезапустить транзакцию »
-
25-09-2019 - |
Вопрос
У меня есть таблица 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»?
Заранее спасибо.
Решение
Если вы используете 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
}
}