Mysql

InnoDB 事務重試的意外行為

  • May 6, 2021

這將很難重現,但希望有人可以根據所涉及的邏輯對我的問題有所了解。在事務期間遇到一些間歇性死鎖問題後,我實施了一個重試策略,類似於這樣(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隨後將是什麼UPDATEdDELETEd等等,那麼你有FOR UPDATESELECT

您必須在事務中的每個查詢之後檢查錯誤。如果發生死鎖,請重新開始,如上所述。

發生了什麼

  1. 錯誤發生了,所以它回滾了事務。
  2. 客戶端程式碼忽略了錯誤並繼續前進。
  3. 查詢 4、5、6 已執行並送出。

需要什麼

  1. 錯誤發生了,所以它回滾了事務。
  2. 客戶端程式碼注意到了這個錯誤
  3. 客戶端程式碼應該有程式碼ROLLBACK並跳轉回START.

(顯式ROLLBACK可能是多餘的,但無害。但是,分支回到START客戶端程式碼。)

應該以這種方式處理“死鎖”,但任何其他“錯誤”都應通過中止處理。

而且,由於幾乎在任何查詢之後都可能發生死鎖,因此您應該在任何地方進行測試。

引用自:https://dba.stackexchange.com/questions/291018