2010-02-25 218 views
192

我有一個innoDB表記錄在線用戶。它在用戶刷新的每個頁面上得到更新,以跟蹤他們在哪些頁面上以及他們上次訪問網站的日期。然後我有一個每15分鐘運行一次的cron來刪除舊記錄。如何避免mysql'嘗試獲取鎖時發現死鎖;嘗試重新啓動交易'

我得到了'嘗試鎖定時發現的死鎖;嘗試重新啓動事務'昨天晚上大約5分鐘,它似乎是在此表中運行INSERT時。有人可以建議如何避免這個錯誤?

===編輯===

這裏是正在運行的查詢:

第一次訪問網站:

INSERT INTO onlineusers SET 
ip = 123.456.789.123, 
datetime = now(), 
userid = 321, 
page = '/thispage', 
area = 'thisarea', 
type = 3 

在每個頁面刷新:

UPDATE onlineusers SET 
ips = 123.456.789.123, 
datetime = now(), 
userid = 321, 
page = '/thispage', 
area = 'thisarea', 
type = 3 
WHERE id = 888 

克龍每隔15分鐘:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND 

然後做一些罪名來記錄一些統計數據(即:會員在線,訪客在線)。

+0

你能提供一些關於表結構的更多細節嗎?有沒有聚集或非聚集索引? – 2010-03-06 19:31:26

+9

http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - 運行「顯示引擎innodb狀態」將提供有用的診斷。 – Martin 2010-03-07 05:48:18

回答

205

一個簡單的技巧,可以幫助解決大多數死鎖是按特定順序排序操作。

你當兩個事務試圖鎖定在相反的順序兩把鎖,即一個僵局:

  • 連接1:鎖鍵(1),鎖鍵(2);
  • 連接2:鎖鍵(2),鎖鍵(1);

如果兩者同時運行,連接1將鎖定密鑰(1),連接2將鎖定密鑰(2),並且每個連接將等待另一方釋放密鑰 - >死鎖。現在

,如果你改變了你的查詢,使得連接將鎖定在同一順序按鍵,即:

  • 連接1:鎖定鍵(1),鎖鍵(2);
  • 連接2:鎖鑰匙(),鎖鑰匙();

這將不可能發生死鎖。

所以這是我的建議:

  1. 確保您有一個鎖定除了delete語句訪問超過一個鍵多在同一時間沒有其他的查詢。如果你這樣做(並且我懷疑你是這樣做的),則按升序排列(k1,k2,... kn)中的WHERE。

  2. 解決您的delete語句按升序工作:

變化

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND 

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers 
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u; 

另一件事要記住的是,MySQL的文件表明,在發生死鎖的情況下,客戶端應該自動重試。您可以將此邏輯添加到您的客戶端代碼。 (比如說,在放棄之前對這個特定的錯誤進行了3次重試)。

+1

如果我有事務(autocommit = false),則拋出死鎖異常。僅僅是重試相同的statement.executeUpdate()就足夠了,或者整個事務現在都是瘸子,應該回滾+重新運行它中運行的所有東西? – Whome 2014-09-16 07:26:42

+2

如果您啓用了交易,則全部或全部無效。如果您有任何形式的例外,可以保證整個交易無效。在這種情況下,你會想重新啓動整個事情。 – 2014-09-16 18:18:38

+1

基於巨大表格上的選擇進行刪除比簡單刪除更慢 – Thermech 2014-12-24 11:40:57

49

當兩個事務彼此等待獲取鎖時發生死鎖。例如:

  • 的Tx 1:鎖A,則B
  • 的Tx 2:鎖B,則A

大約有死鎖許多問題和答案。每次插入/更新/刪除一行時,都會獲取一個鎖。爲避免死鎖,您必須確保併發事務不會以可能導致死鎖的順序更新行。一般而言,即使在不同的交易中(例如總是首先是表A,然後是表B)嘗試始終以相同順序獲取鎖

數據庫死鎖的另一個原因可能是缺少索引。當一行被插入/更新/刪除時,數據庫需要檢查關係約束,也就是確保關係是一致的。爲此,數據庫需要檢查相關表中的外鍵。它可能導致其他鎖獲取比被修改的行。請確保始終在外鍵(當然還有主鍵)上有索引,否則可能會導致表鎖而不是行鎖。如果表鎖定發生,則鎖定爭用更高並且死鎖的可能性增加。

+3

所以也許我的問題是,用戶已刷新頁面,從而在cron試圖在記錄上運行DELETE的同時觸發記錄的更新。 但是,即時通訊在INSERTS上發生錯誤,所以cron不會刪除剛剛創建的記錄。那麼如何在尚未插入的記錄上發生死鎖? – David 2010-02-25 09:32:52

+0

你能否提供更多有關表格的信息以及交易的具體內容? – ewernli 2010-02-25 09:42:07

+0

我編輯了與查詢信息的初始職位。 – David 2010-02-25 10:22:05

4

刪除語句可能會影響表中總行數的很大一部分。最終,這可能會導致在刪除時獲取表鎖。堅持一個鎖(在這種情況下是行鎖或頁鎖)並獲取更多的鎖總是具有死鎖風險。然而,我無法解釋爲什麼插入語句會導致鎖升級 - 這可能與頁面拆分/添加有關,但有人更好地瞭解Mysql將不得不填寫。

首先,值得嘗試爲刪除語句顯式獲取表鎖。請參閱LOCK TABLESTable locking issues

4

您可以嘗試具有delete工作,首先將每行的鍵被刪除到一個臨時表像這樣的僞

create temporary table deletetemp (userid int); 

insert into deletetemp (userid) 
    select userid from onlineusers where datetime <= now - interval 900 second; 

delete from onlineusers where userid in (select userid from deletetemp); 

打破它,這樣是效率較低的操作,但它避免了在delete期間保持鍵盤鎖定。

此外,修改您的select查詢以添加一個where子句,排除超過900秒的行。這可以避免依賴cron作業,並允許您重新安排它以減少運行次數。

關於死鎖的理論:我沒有很多MySQL的背景,但是這裏是... delete將爲日期時間保存鍵範圍鎖定,以防止添加匹配其where子句的行被添加在事務的中間,並且當它發現要刪除的行時,它將嘗試在它正在修改的每個頁面上獲取一個鎖。 insert將在其插入的頁面上獲取鎖定,然後然後嘗試獲取密鑰鎖定。通常,insert將耐心等待該鍵鎖打開,但如果delete試圖鎖定insert正在使用的相同頁面,則這會死鎖,因爲delete需要該頁鎖定,而insert需要該鍵鎖定。雖然這似乎不適合插入,但deleteinsert正在使用不重疊的日期時間範圍,因此可能還有其他事情正在進行。

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html

2

對於使用Spring的Java程序員,我使用AOP方面是自動重試運行進入短暫的死鎖交易避免了這個問題。

請參閱@RetryTransaction Javadoc瞭解更多信息。

+0

你能更新你的鏈接嗎?它已經死了 – 2017-01-19 13:26:10

+0

更新 - 謝謝! – Archie 2017-01-19 17:32:38

相關問題