这将很难重现,但希望有人可以根据所涉及的逻辑对我的问题有所了解。在事务期间遇到一些间歇性死锁问题后,我实施了一个重试策略,类似于这样(PHP):
public function execute_query($sql) {
$try = 1;
$log_msg = '';
while (true) {
$query = $this->link->query($sql);
$error_no = $this->link->errno;
$error_msg = $this->link->error;
if (!$error_no){
if ($try > 1) {
$this->debug_log->write('Deadlock Succeeded after ' . $try . ' Tries ::: ' . $sql);
}
return $query;
} else {
$log_msg = 'Error: ' . $error_msg . ' ::: Error No: ' . $error_no . ' ::: ' . ($try > 1 ? $try . ' Tries ::: ' : '') . $sql;
$this->debug_log->write($log_msg);
if ($error_no == 1213 && $try < self::DEADLOCK_RETRY) {
// retry when deadlock occurs
sleep($try * 2);
$try++;
} else {
throw new ErrorException($log_msg);
exit();
}
}
}
}
这似乎产生了与我所希望的完全相反的影响——我的事务的前半部分似乎被回滚,但后半部分提交。
例如:
START TRANSACTION;
Query 1
Query 2
Query 3 (deadlock occurs, query gets retried and succeeds on 2nd attempt)
Query 4
Query 5
COMMIT;
在所有这一切结束时,我留下了查询 3、4 和 5 成功运行,但查询 1 和 2 没有。我不明白这是怎么可能的,但现在已经发生了两次,我无法重现死锁来测试或制定工作策略。
谁能解释为什么在 InnoDB 事务中重试失败的查询会导致一半事务回滚而另一半提交?
当查询 3 发生死锁时,死锁可能涉及查询 1 或 2。您必须重新开始
START TRANSACTION
并重播所有查询。如果其中任何一个是控制
SELECTs
的结果,SELECT
随后将是什么UPDATEd
,DELETEd
等等,那么你有FOR UPDATE
吗SELECT
?您必须在事务中的每个查询之后检查错误。如果发生死锁,请重新开始,如上所述。
发生了什么
需要什么
ROLLBACK
并跳转回START
.(显式
ROLLBACK
可能是多余的,但无害。但是,分支回到START
客户端代码。)应该以这种方式处理“死锁”,但任何其他“错误”都应通过中止处理。
而且,由于几乎在任何查询之后都可能发生死锁,因此您应该在任何地方进行测试。