2010-04-07 126 views
25

我有一個大約5,000,000行的MySQL表,它們通過並行Perl進程以小方式不斷更新,這些並行Perl進程通過DBI連接。該表有大約10列和幾個索引。解決MySQL錯誤「嘗試獲取鎖定時發現死鎖;嘗試重新啓動事務」

一個相當普遍的操作產生了有時以下錯誤:

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%的電話或更少。然而,它從來沒有發生在一張小桌子上,隨着數據庫的增長而變得越來越普遍。

請注意,我正在使用file_table中的a_lock字段來確保我運行的四個幾乎完全相同的進程不會嘗試在同一行上工作。限制旨在將他們的工作分解爲小塊。

我還沒有對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行的交易」?

在此先感謝。

回答

61

如果你正使用InnoDB或者行級事務RDBMS,那麼很可能是任何寫事務可能導致死鎖,即使在完全正常的情況。較大的表,較大的寫入和較長的事務塊通常會增加發生死鎖的可能性。在你的情況下,這可能是這些的組合。

真正處理死鎖的唯一方法是編寫代碼以期待它們。如果你的數據庫代碼寫得很好,這通常不是很困難。通常,您可以在查詢執行邏輯周圍放置一個try/catch,並在出現錯誤時查找死鎖。如果你抓住一個,正常的事情就是試圖再次執行失敗的查詢。

我強烈建議您在MySQL手冊中閱讀this page。它有一系列的事情可以幫助解決僵局並減少它們的頻率。

+2

什麼是我們需要捕捉,然後錯誤代碼?僅靠1205就足夠了嗎? http://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html中有超過900個錯誤代碼。您如何知道我們需要執行的所有代碼,以便爲您的try/catch建議實施適當的解決方案? – Pacerier 2014-12-19 03:52:12

+0

這是否意味着除了「InnoDB或任何行級事務性RDBMS」之外沒有這些問題? – 2015-12-31 19:49:07

5

請注意,如果您使用SELECT FOR UPDATE插入之前執行唯一性檢查,你將獲得每一場比賽條件的僵局,除非你啓用innodb_locks_unsafe_for_binlog選項。用於檢查唯一性的無死鎖方法是使用INSERT IGNORE盲目地將行插入具有唯一索引的表中,然後檢查受影響的行數。

下面的行添加到my.cnf文件

innodb_locks_unsafe_for_binlog = 1

1 - ON
0 - OFF

+0

這解決了在多線程環境中保存ActiveRecord關聯的所有問題。 – lightyrs 2014-05-09 23:08:11

+2

啓用'innodb_locks_unsafe_for_binlog'可能會導致幻影問題,因爲禁用間隙鎖定時,其他會話可以將新行插入到間隙中。 – shivam 2015-07-31 06:04:32

9

答案是正確的,但是Perl文檔關於如何處理死鎖有點稀疏,並可能與PrintError,RaiseError和HandleError混淆選項。看起來,與使用HandleError不同,在Print和Raise上使用,然後使用Try:Tiny來包裝代碼並檢查錯誤。下面的代碼給出了一個例子,其中db代碼在一個while循環中,每3秒會重新執行一次錯誤的sql語句。 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 ..

希望這可以幫助別人 -

0

在死鎖異常的情況下重試查詢的想法是好的,但它可能會非常慢,因爲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 
    } 
} 
+4

不幸的是,大多數遇到這種情況的用戶可能會處理多臺機器或將數據轉儲到單個MySQL實例中。對於大多數用戶來說,應用程序中的行級鎖定不是一種選擇。 – dgtized 2015-03-12 19:48:53

相關問題