Mysql鎖機制及原理簡析
一.前言
1.什麼是鎖?
鎖是計算機協調多個進程或線程併發訪問某一資源的機制。
- 鎖保證數據併發訪問的一致性、有效性;
- 鎖衝突也是影響數據庫併發訪問性能的一個重要因素。
- 鎖是Mysql在服務器層和存儲引擎層的的併發控制
2.爲什麼要加鎖?
數據庫是一個多用戶使用的共享資源。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的情況。若對併發操作不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。
鎖是用於管理對公共資源的併發控制。也就是說在併發的情況下,會出現資源競爭,所以需要加鎖。
加鎖解決了 多用戶環境下保證數據庫完整性和一致性
。
Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。並且一般lock的對象僅在事務commit或rollback後進行釋放(不同事務隔離級別釋放的時間可能不同)。
二.鎖的分類
總共可以按照四個方向對鎖進行分類
1.按照加鎖的機制進行分類
1).悲觀鎖
- 假定會發生併發衝突,屏蔽一切可能違反數據完整性的操作。悲觀鎖是數據庫層面加鎖,都會阻塞去等待鎖。
2).樂觀鎖
- 假設不會發生併發衝突,只在提交操作時檢查是否違反數據完整性。
- 樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,需要再次查看該字段的值是否和第一次的一樣。如果一樣更新,反之拒絕。之所以叫樂觀,因爲這個模式沒有從數據庫加鎖,等到更新的時候再判斷是否可以更新。
- 缺點:併發很高的時候,多了很多無用的重試。樂觀鎖,不能解決髒讀的問題。
2.按照鎖的兼容性或鎖的可重入性進行分類
1).共享鎖/讀鎖/S 鎖(share lock)
- 其他事務可以讀,但不能寫。允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。
2).排他鎖/寫鎖/X 鎖(exclusive)
- 其他事務不能讀取,也不能寫。允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。
3.按照鎖的粒度進行分類
1).行鎖(row-level locking)
- 即只允許事務讀一行數據。行鎖的粒度實在每一條行數據,當然也帶來了最大開銷,但是行鎖可以最大限度的支持併發處理。
- 開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
- 最大程度的支持併發,同時也帶來了最大的鎖開銷。
- 行級鎖更適合於有大量按索引條件併發更新少量不同數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統
- 在 InnoDB 中,除單個 SQL 組成的事務外,鎖是逐步獲得的,這就決定了在 InnoDB 中發生死鎖是可能的。
- 行級鎖只在存儲引擎層實現,而Mysql服務器層沒有實現。
2).表鎖
- 允許事務在行級上的鎖和表級上的鎖同時存在。鎖定整個表,開銷最小,但是也阻塞了整個表。
- 開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。這些存儲引擎通過總是一次性同時獲取所有需要的鎖以及總是按相同的順序獲取表鎖來避免死鎖。
- 表級鎖更適合於以查詢爲主,併發用戶少,只有少量按索引條件更新數據的應用,如Web 應用。
- 若一個用戶正在執行寫操作,會獲取排他的“寫鎖”,這可能會鎖定整個表,阻塞其他用戶的讀、寫操作;
- 若一個用戶正在執行讀操作,會先獲取共享鎖“讀鎖”,這個鎖運行其他讀鎖併發的對這個表進行讀取,互不干擾。只要沒有寫鎖的進入,讀鎖可以是併發讀取統一資源的。
- Mysql的表級別鎖分爲兩類:元數據鎖(Metadata Lock,MDL)、表鎖。
- 元數據鎖/MDL鎖
- 元數據鎖(MDL) 不需要顯式使用,在訪問一個表的時候會被自動加上。這個特性需要MySQL5.5版本以上纔會支持
- 當對一個表做增刪改查的時候,該表會被加MDL讀鎖,當對錶做結構變更的時候,加MDL寫鎖
- MDL鎖的規則:
- 讀鎖之間不互斥,所以可以多線程多同一張表進行增刪改查。
- 讀寫鎖、寫鎖之間是互斥的,爲了保證表結構變更的安全性,所以如果要多線程對同一個表加字段等表結構操作,就會變成串行化,需要進行鎖等待。
- MDL的寫鎖優先級比MDL讀鎖的優先級,但是可以設置max_write_lock_count系統變量來改變這種情況,當寫鎖請求超過這個變量設置的數後,MDL讀鎖的優先級會比MDL寫鎖的優先級高。(默認情況下,這個數字會很大,所以不用擔心寫鎖的優先級下降)
- MDL的鎖釋放必須要等到事務結束纔會釋放
- 元數據鎖/MDL鎖
3).頁面鎖
- 頁級鎖定是 MySQL 中比較獨特的一種鎖定級別,在其他數據庫管理軟件中也並不是太常見。
- 頁面鎖開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
- 頁級鎖定的特點是鎖定顆粒度介於行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的併發處理能力也同樣是介於上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發生死鎖。
- 在數據庫實現資源鎖定的過程中,隨着鎖定資源顆粒度的減小,鎖定相同數據量的數據所需要消耗的內存數量是越來越多的,實現算法也會越來越複雜。
- 不過,隨着鎖定資源顆粒度的減小,應用程序的訪問請求遇到鎖等待的可能性也會隨之降低,系統整體併發度也隨之提升。
- 使用頁級鎖定的主要是 BerkeleyDB 存儲引擎
4).全局鎖
-
MySQL 提供全局鎖來對整個數據庫實例加鎖。
-
FLUSH TABLES WITH READ LOCK
- 這條語句一般都是用來備份的,當執行這條語句後,數據庫所有打開的表都會被關閉,並且使用全局讀鎖鎖定數據庫的所有表,同時,其他線程的更新語句(增刪改),數據定義語句(建表,修改表結構)和更新類的事務提交都會被阻塞。
-
LOCK INSTANCE FOR BACKUP UNLOCK INSTANCE
- 這個鎖的作用範圍更廣,這個鎖會阻止文件的創建,重命名,刪除,包括 REPAIR TABLE TRUNCATE TABLE, OPTIMIZE TABLE操作以及賬戶的管理都會被阻塞。當然這些操作對於內存臨時表來說是可以執行的,爲什麼內存表不受這些限制呢?因爲內存表不需要備份,所以也就沒必要滿足這些條件。
-
在mysql 8.0 以後,對於備份,mysql可以直接使用備份鎖。
4.按照鎖的實現模式進行分類
1).記錄鎖
- 某條記錄的鎖,加鎖後鎖住的只是表的某一條記錄。
- 例如:update user_info set name=’張三’ where id=1 ,這裏的id是唯一索引。
- Record Lock總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定
- 記錄鎖的作用:加了記錄鎖之後可以避免數據在查詢的時候被修改的重複讀問題,也避免了在修改的事務未提交前被其他事務讀取的髒讀問題。
2).間隙鎖/gap鎖
-
當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖
-
很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件。
-
間隙鎖的目的:
- 防止幻讀,以滿足相關隔離級別的要求;
- 滿足恢復和複製的需要:
-
產生間隙鎖的條件(RR事務隔離級別下):
-
使用普通索引鎖定;
-
使用多列唯一索引;
-
使用唯一索引鎖定多行記錄。
-
3).next-key鎖/臨鍵鎖
- 臨鍵鎖是INNODB的行鎖默認算法,它是記錄鎖和間隙鎖的組合,臨鍵鎖會把查詢出來的記錄鎖住,同時也會把該範圍查詢內的所有間隙空間也會鎖住,再之它會把相鄰的下一個區間也會鎖住。
- 臨鍵鎖出現條件:範圍查詢並命中,查詢命中了索引。
- 比如下面表的數據執行 select * from user_info where id>1 and id<=13 for update ;會鎖住ID爲 1,5,10的記錄;同時會鎖住,1至5,5至10,10至15的區間。
- 臨鍵鎖的作用:結合記錄鎖和間隙鎖的特性,臨鍵鎖避免了在範圍查詢時出現髒讀、重複讀、幻讀問題。加了臨鍵鎖之後,在範圍區間內數據不允許被修改和插入。
- Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對於行的查詢都是採用這種鎖定算法。
4).意向鎖
- innodb的意向鎖主要用戶多粒度的鎖並存的情況。比如事務A要在一個表上加S鎖,如果表中的一行已被事務B加了X鎖,那麼該鎖的申請也應被阻塞。如果表中的數據很多,逐行檢查鎖標誌的開銷將很大,系統的性能將會受到影響。爲了解決這個問題,可以在表級上引入新的鎖類型來表示其所屬行的加鎖情況,這就引出了“意向鎖”的概念。
- 舉個例子,如果表中記錄1億,事務A把其中有幾條記錄上了行鎖了,這時事務B需要給這個表加表級鎖,如果沒有意向鎖的話,那就要去表中查找這一億條記錄是否上鎖了。如果存在意向鎖,那麼假如事務A在更新一條記錄之前,先加意向鎖,再加X鎖,事務B先檢查該表上是否存在意向鎖,存在的意向鎖是否與自己準備加的鎖衝突,如果有衝突,則等待直到事務A釋放,而無須逐條記錄去檢測。事務B更新表時,其實無須知道到底哪一行被鎖了,它只要知道反正有一行被鎖了就行了。
- 主要作用是處理行鎖和表鎖之間的矛盾,能夠顯示“某個事務正在某一行上持有了鎖,或者準備去持有鎖”
三.mysql不同的存儲引擎支持不同的鎖機制
所有的存儲引擎都以自己的方式顯現了鎖機制,服務器層完全不瞭解存儲引擎中的鎖實現:
- MyISAM、MEMORY、CSV存儲引擎採用的是表級鎖(table-level locking)
- BDB(Berkeley DB) 存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖
- InnoDB 存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認情況下是採用行級鎖。
- innoDB行鎖是通過給索引上的索引項加鎖來實現的,InnoDB這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!
- 行級鎖都是基於索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖。行級鎖的缺點是:由於需要請求大量的鎖資源,所以速度慢,內存消耗大。
默認情況下,表鎖和行鎖都是自動獲得的, 不需要額外的命令。
但是在有的情況下, 用戶需要明確地進行鎖表或者進行事務的控制, 以便確保整個事務的完整性,這樣就需要使用事務控制和鎖定語句來完成。
InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是採用了行級鎖。
innodb存儲引擎由於實現了行級鎖定,雖然在鎖定機制的實現方面所帶來的性能損耗可能比表級鎖定會要更高一些,但是在整體併發處理能力方面要遠遠優於MyISAM的表級鎖定的。當系統併發量較高的時候,Innodb的整體性能和MyISAM相比就會有比較明顯的優勢了。
但是,Innodb的行級鎖定同樣也有其脆弱的一面,當我們使用不當的時候,可能會讓Innodb的整體性能表現不僅不能比MyISAM高,甚至可能會更差。
四.InnoDB行鎖的實現原理
行鎖是加在索引上的
舉個例子,用name=Alice來查詢的時候,會先找到對應的主鍵值是18 ,然後用18在下面的聚集索引中找到name=Alice的記錄內容是 77 和 Alice。
普通索引,也叫做輔助索引,葉子節點存放的是主鍵值。主鍵上的索引叫做聚集索引,表裏的每一條記錄都存放在主鍵的葉子節點上。當通過輔助索引select 查詢數據的時候,會先在輔助索引中找到對應的主鍵值,然後用主鍵值在聚集索引中找到該條記錄。
Innodb中的索引數據結構是 B+ 樹,數據是有序排列的,從根節點到葉子節點一層層找到對應的數據。
表中每一行的數據,是組織存放在聚集索引中的,所以叫做索引組織表。
-
InnoDB 行鎖是通過給索引上的索引項加鎖來實現的,這一點 MySQL 與 Oracle 不同,後者是通過在數據塊中對相應數據行加鎖來實現的。InnoDB 這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖!
-
不論是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖。
-
只有執行計劃真正使用了索引,才能使用行鎖:即便在條件中使用了索引字段,但是否使用索引來檢索數據是由 MySQL 通過判斷不同執行計劃的代價來決定的,如果 MySQL 認爲全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。因此,在分析鎖衝突時,別忘了檢查 SQL 的執行計劃(可以通過 explain 檢查 SQL 的執行計劃),以確認是否真正使用了索引。
-
由於 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然多個session是訪問不同行的記錄, 但是如果是使用相同的索引鍵, 是會出現鎖衝突的(後使用這些索引的session需要等待先使用索引的session釋放鎖後,才能獲取鎖)。 應用設計的時候要注意這一點。
五.InnoDB加鎖方法
意向鎖是 InnoDB 自動加的, 不需用戶干預。
對於 UPDATE、 DELETE 和 INSERT 語句, InnoDB 會自動給涉及數據集加排他鎖(X);
對於普通 SELECT 語句,InnoDB 不會加任何鎖;
事務可以通過以下語句顯式給記錄集加共享鎖或排他鎖:
- 共享鎖(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 其他 session 仍然可以查詢記錄,並也可以對該記錄加 share mode 的共享鎖。但是如果當前事務需要對該記錄進行更新操作,則很有可能造成死鎖。
- 排他鎖(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其他 session 可以查詢該記錄,但是不能對該記錄加共享鎖或排他鎖,而是等待獲得鎖
1.隱式鎖定
隱式鎖定:
InnoDB在事務執行過程中,使用兩階段鎖協議:
- 隨時都可以執行鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖;
- 鎖只有在執行commit或者rollback的時候纔會釋放,並且所有的鎖都是在同一時刻被釋放。
2.顯式鎖定
select ... lock in share mode //共享鎖
select ... for update //排他鎖複製代碼
樣例如下:
1.select for update
在執行這個 select 查詢語句的時候,會將對應的索引訪問條目進行上排他鎖(X 鎖),也就是說這個語句對應的鎖就相當於update帶來的效果。
select *** for update 的使用場景:爲了讓自己查到的數據確保是最新數據,並且查到後的數據只允許自己來修改的時候,需要用到 for update 子句。
2.select * from user where id=10 for update
通過鎖住聚集索引中的節點來鎖住這條記錄(鎖住id=10的索引,即鎖住了這條記錄)。
3.select * from user where name=‘b’ for update
這裏的name上加了唯一索引,唯一索引本質上是輔助索引,加了唯一約束。所以會先在輔助索引上找到name爲d的索引記錄,在輔助索引中加鎖,然後查找聚集索引,鎖住對應索引記錄。
4.select lock in share mode
in share mode 子句的作用就是將查找到的數據加上一個 share 鎖,這個就是表示其他的事務只能對這些數據進行簡單的select 操作,並不能夠進行 DML 操作。
select *** lock in share mode 使用場景:爲了確保自己查到的數據沒有被其他的事務正在修改,也就是說確保查到的數據是最新的數據,並且不允許其他人來修改數據。但是自己不一定能夠修改數據,因爲有可能其他的事務也對這些數據 使用了 in share mode 的方式上了 S 鎖。
5.for update 和 lock in share mode 的區別:
前一個上的是排他鎖(X 鎖),一旦一個事務獲取了這個鎖,其他的事務是沒法在這些數據上執行 for update ;後一個是共享鎖,多個事務可以同時的對相同數據執行 lock in share mode。
6.爲什麼聚簇索引上的記錄也要加鎖?
試想一下,如果有併發的另外一個SQL,是直接通過主鍵索引id=30來更新,會先在聚集索引中請求加鎖。如果只在輔助索引中加鎖的話,兩個併發SQL之間是互相感知不到的。
7.顯式鎖定對性能影響(performance impact)
select for update 語句,相當於一個 update 語句。在業務繁忙的情況下,如果事務沒有及時的commit或者rollback 可能會造成其他事務長時間的等待,從而影響數據庫的併發使用效率。
select lock in share mode 語句是一個給查找的數據上一個共享鎖(S 鎖)的功能,它允許其他的事務也對該數據上S鎖,但是不能夠允許對該數據進行修改。如果不及時的commit 或者rollback 也可能會造成大量的事務等待。
8.默認的讀操作,上鎖嗎
默認是 MVCC 機制(“一致性非鎖定讀-consistent nonlocking read”)保證 RR 級別的隔離正確性,是不上鎖的。
可以選擇手動上鎖:select xxxx for update (排他鎖); select xxxx lock in share mode(共享鎖),稱之爲“一致性鎖定讀”。
使用鎖之後,就能在 RR 級別下,避免幻讀。當然,默認的 MVCC 讀,也能避免幻讀。
既然 RR 能夠防止幻讀,那麼,SERIALIZABLE 有啥用呢?防止丟失更新。
最後,行鎖的實現原理就是鎖住聚集索引,如果你查詢的時候,沒有正確地擊中索引,MySql 優化器將會拋棄行鎖,使用表鎖。
9.不同隔離級別下鎖的差異
對於許多 SQL,隔離級別越高,InnoDB 給記錄集加的鎖就越嚴格(尤其是使用範圍條件的時候),產生鎖衝突的可能性也就越高,從而對併發性事務處理性能的 影響也就越大。
因此, 我們在應用中, 應該儘量使用較低的隔離級別, 以減少鎖爭用的機率。實際上,通過優化事務邏輯,大部分應用使用 Read Commited 隔離級別就足夠了。對於一些確實需要更高隔離級別的事務, 可以通過在程序中執行 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 或 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE 動態改變隔離級別的方式滿足需求。
3.InnoDB 行鎖優化建議
合理利用 InnoDB 的行級鎖定,做到揚長避短
- 儘可能讓所有的數據檢索都通過索引來完成,從而避免 InnoDB 因爲無法通過索引鍵加鎖而升級爲表級鎖定。
- 合理設計索引,讓 InnoDB 在索引鍵上面加鎖的時候儘可能準確,儘可能的縮小鎖定範圍,避免造成不必要的鎖定而影響其他 Query 的執行。
- 儘可能減少基於範圍的數據檢索過濾條件,避免因爲間隙鎖帶來的負面影響而鎖定了不該鎖定的記錄。
- 儘量控制事務的大小,減少鎖定的資源量和鎖定時間長度。
- 在業務環境允許的情況下,儘量使用較低級別的事務隔離,以減少 MySQL 因爲實現事務隔離級別所帶來的附加成本。
4.infoDB什麼時候加表鎖?
對於 InnoDB 表,在絕大部分情況下都應該使用行級鎖,因爲事務和行鎖往往是我們之所以選擇 InnoDB 表的理由。
事務需要更新大部分或全部數據,表又比較大,如果使用默認的行鎖,不僅這個事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖衝突,這種情況下可以考慮使用表鎖來提高該事務的執行速度。
事務涉及多個表,比較複雜,很可能引起死鎖,造成大量事務回滾。這種情況也可以考慮一次性鎖定事務涉及的表,從而避免死鎖、減少數據庫因事務回滾帶來的開銷。
六.死鎖
死鎖產生:
- 死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性循環。
- 當事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖。
- 鎖的行爲和順序和存儲引擎相關。以同樣的順序執行語句,有些存儲引擎會產生死鎖有些不會——死鎖有雙重原因:真正的數據衝突;存儲引擎的實現方式。
檢測死鎖:數據庫系統實現了各種死鎖檢測和死鎖超時的機制。InnoDB存儲引擎能檢測到死鎖的循環依賴並立即返回一個錯誤。
死鎖恢復:死鎖發生以後,只有部分或完全回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。所以事務型應用程序在設計時必須考慮如何處理死鎖,多數情況下只需要重新執行因死鎖回滾的事務即可。
外部鎖的死鎖檢測:發生死鎖後,InnoDB 一般都能自動檢測到,並使一個事務釋放鎖並回退,另一個事務獲得鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 並不能完全自動檢測到死鎖, 這需要通過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決
死鎖影響性能:死鎖會影響性能而不是會產生嚴重錯誤,因爲InnoDB會自動檢測死鎖狀況並回滾其中一個受影響的事務。在高併發系統上,當許多線程等待同一個鎖時,死鎖檢測可能導致速度變慢。 有時當發生死鎖時,禁用死鎖檢測(使用innodb_deadlock_detect配置選項)可能會更有效,這時可以依賴innodb_lock_wait_timeout設置進行事務回滾。
1.MyISAM如何避免死鎖?
在自動加鎖的情況下,MyISAM 表不會出現死鎖(MyISAM 總是一次獲得 SQL 語句所需要的全部鎖)。
2.InnoDB如何避免死鎖?
- 爲了在單個InnoDB表上執行多個併發寫入操作時避免死鎖,可以在事務開始時通過爲預期要修改的每個元祖(行)使用SELECT ... FOR UPDATE語句來獲取必要的鎖,即使這些行的更改語句是在之後才執行的。
- 在事務中,如果要更新記錄,應該直接申請足夠級別的鎖,即排他鎖,而不應先申請共享鎖、更新時再申請排他鎖,因爲這時候當用戶再申請排他鎖時,其他事務可能又已經獲得了相同記錄的共享鎖,從而造成鎖衝突,甚至死鎖
- 如果事務需要修改或鎖定多個表,則應在每個事務中以相同的順序使用加鎖語句。 在應用中,如果不同的程序會併發存取多個表,應儘量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會
- 通過SELECT ... LOCK IN SHARE MODE獲取行的讀鎖後,如果當前事務再需要對該記錄進行更新操作,則很有可能造成死鎖。
- 改變事務隔離級別,如降低隔離級別(如果業務允許,將隔離級別調低也是較好的選擇,比如將隔離級別從RR調整爲RC,可以避免掉很多因爲gap鎖造成的死鎖)
- 爲表添加合理的索引。可以看到如果不走索引將會爲表的每一行記錄添加上鎖,死鎖的概率大大增大。
如果出現死鎖,可以用 SHOW INNODB STATUS 命令來確定最後一個死鎖產生的原因。返回結果中包括死鎖相關事務的詳細信息,如引發死鎖的 SQL 語句,事務已經獲得的鎖,正在等待什麼鎖,以及被回滾的事務等。據此可以分析死鎖產生的原因和改進措施。
3.死鎖案例
1. 不同表相同記錄行鎖衝突
這種情況很好理解,事務A和事務B操作兩張表,但出現循環等待鎖情況。
2.相同表記錄行鎖衝突
這種情況比較常見,之前遇到兩個job在執行數據批量更新時,jobA處理的的id列表爲[1,2,3,4],而job處理的id列表爲[8,9,10,4,2],這樣就造成了死鎖。
3.不同索引鎖衝突
這種情況比較隱晦,事務A在執行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執行時,只在聚簇索引上加鎖,加鎖順序是[1,2,3,4,5],這樣就造成了死鎖的可能性。
4.gap鎖衝突
innodb在RR級別下,如下的情況也會產生死鎖,比較隱晦。不清楚的同學可以自行根據上節的gap鎖原理分析下。
七.一些優化鎖性能的建議
- 儘量使用較低的隔離級別;
- 精心設計索引, 並儘量使用索引訪問數據, 使加鎖更精確, 從而減少鎖衝突的機會
- 選擇合理的事務大小,小事務發生鎖衝突的機率也更小
- 給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。比如要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖
- 不同的程序訪問一組表時,應儘量約定以相同的順序訪問各表,對一個表而言,儘可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機會
- 儘量用相等條件訪問數據,這樣可以避免間隙鎖對併發插入的影響
- 不要申請超過實際需要的鎖級別
- 除非必須,查詢時不要顯示加鎖。 MySQL的MVCC可以實現事務中的查詢不用加鎖,優化事務性能;MVCC只在COMMITTED READ(讀提交)和REPEATABLE READ(可重複讀)兩種隔離級別下工作
- 對於一些特定的事務,可以使用表鎖來提高處理速度或減少死鎖的可能