1. 程式人生 > >MySQL——InnoDB鎖問題(六)

MySQL——InnoDB鎖問題(六)

一、在InnoDB情況下什麼時候使用表鎖。

對於InnoDB表,大多數情況都應該使用行鎖,因為事務和行鎖往往是我們選擇InnoDB表的重要原因。但在特殊的情況下,也可以使用表級鎖。

(1)、事務需要更新大部分資料或者全部資料,表又比較大,如果使用預設的行級鎖,不僅使得事務執行比較慢效率低,而且可能造成其他事務長時間鎖等待和鎖衝突,這樣的情況可以考慮使用表級鎖來提高執行效率。

(2)、事務涉及多個表,又比較複雜,很可能引起死鎖,造成大量事務回滾,可以考慮一次性獲取事務涉及的所有表,從而避免死鎖,減少資料庫因事務回滾產生的開銷。

當然了,應用中這樣的情況不能太多,否則可以考慮換成MyISAM表了,在InnoDB下使用表級鎖應該注意以下兩點。


、死鎖問題。

對於MyISAM表是不會產生死鎖的,因為表鎖總是一次性獲取所有SQL語句涉及到的所有表級鎖。但是在InnoDB表中,除了單個SQL語句組成的事務外,不然鎖都是逐步獲取的。這就讓InnoDB表產生死鎖的可能。如下是發生死鎖的栗子。


在上面的例子中,兩個事務都需要獲得對方佔有的排他鎖才能繼續完成事務,這種迴圈鎖等待就是典型的死鎖。

發生死鎖後,一般情況下InnoDB都能夠自動檢測到,使一個事務釋放鎖並退回。另一個事務獲得鎖,繼續完成事務。但是在設計外部鎖或者表鎖時,InnoDB無法檢查到死鎖情況,這時需要設定鎖等待超時引數,innodb_lock_wait_timeout來解決。需要說明的是,這個引數不只是用來解決死鎖問題,在併發訪問比較高的情況下,如果大量事務因無法獲得所需的鎖而掛起,會佔用計算機大量的資源,造成嚴重效能問題,甚至拖垮資料庫。通過設定合適的鎖等待超時閥值,可以有效避免這樣的情況發生。

通常來說,死鎖都是應用設計的問題,通過調整業務流程,資料庫物件設計,事務大小,以及訪問資料庫SQL語句,絕大部分死鎖都可以避免。下面通過例項介紹幾種避免死鎖的常用方法。

(1)、在應用中,如果不同的程式會併發存取多個表,應儘量以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會,在下面的例子中,兩個session訪問兩個表的順序不同,發生死鎖機會比較高,但是以相同的順序來訪問,死鎖就能避免。

2)、在程式以批量方式處理資料的時候,如果事先對資料排序,保證每個執行緒按照固定的順序來處理記錄,也大大降低出現死鎖額可能。

3)、在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖。而不應該先申請共享鎖,更新的時候再申請排他鎖。因為當用戶申請排他鎖時,其他事務可能又獲得了相同記錄的共享鎖,從而造成鎖衝突,甚至死鎖。

(4)、在前面的情況中,在REPEATABLE-READ隔離級別下,如果兩個執行緒對相同條件記錄用SELECT .... FOR UPDATE 加排他鎖,在沒有符合記錄的條件下,兩個執行緒都會加鎖成功。程式發現記錄尚不存在,就試圖插入一條新紀錄。如果兩個執行緒都折這麼做,就會造成死鎖。這種情況下,將隔離級別改為READ COMMITED,就可以避免問題。

5)、當隔離級別為READ COMMITED 時,如果兩個執行緒都先執行 SELECT  ..... FOR UPDATE ,判斷是否存在符合條件的記錄,如果沒有,就插入記錄,此時,只有一個執行緒能夠插入成功,另一個執行緒會出現鎖等待,當第一個執行緒提交後,第二個執行緒會因主鍵重出錯。但雖然這個執行緒出錯了,卻會獲得一個排他鎖,這時如果第三個執行緒進來申請排他鎖,也會出現死鎖。

對這樣的情況,可以直接做插入操作,然後再捕獲主鍵重異常,或者遇到主鍵重錯誤時,總是執行ROLLBACK釋放獲得的排他鎖。

儘管通過上面的設計和SQL優化等措施,可以減少死鎖,但死鎖很難完全避免,因此,在程式設計中,總是捕獲並處理死鎖異常是一個很好的程式設計習慣。

如果出現死鎖,可以使用show  innodb status 命令來檢視最後一個產生死鎖的原因。返回結果中包含死鎖相關事務的詳細資訊。如引發死鎖的的SQL語句,事務已經獲取鎖,正在等待什麼鎖,以及被回滾的事務等。