再談mysql鎖機制及原理—鎖的詮釋

加鎖是實現數據庫併發控制的一個非常重要的技術。當事務在對某個數據對象進行操作前,先向系統發出請求,對其加鎖。加鎖後事務就對該數據對象有了一定的控制,在該事務釋放鎖之前,其他的事務不能對此數據對象進行更新操作。

什麼是鎖?

鎖是計算機協調多個進程或線程併發訪問某一資源的機制

  • 鎖保證數據併發訪問的一致性、有效性;

  • 鎖衝突也是影響數據庫併發訪問性能的一個重要因素。

  • 鎖是Mysql在服務器層和存儲引擎層的的併發控制。


v2-88d762a088e18b1281e52e9b31b46423_hd.jpg


爲什麼要加鎖

數據庫是一個多用戶使用的共享資源。當多個用戶併發地存取數據時,在數據庫中就會產生多個事務同時存取同一數據的情況。若對併發操作不加控制就可能會讀取和存儲不正確的數據,破壞數據庫的一致性。

鎖是用於管理對公共資源的併發控制。也就是說在併發的情況下,會出現資源競爭,所以需要加鎖。

加鎖解決了 多用戶環境下保證數據庫完整性和一致性。

Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。並且一般lock的對象僅在事務commit或rollback後進行釋放(不同事務隔離級別釋放的時間可能不同)。

鎖分類

  • 共享鎖||讀鎖||S 鎖(share lock):其他事務可以讀,但不能寫。允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。

  • 排他鎖||寫鎖||X 鎖(exclusive) :其他事務不能讀取,也不能寫。允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。 類型細分:

  • 意向共享鎖(IS Lock/intent share lock)

  • 意向排他鎖||互斥鎖(IX Lock/intent exclusive lock)

  • 悲觀鎖||保守鎖(pessimistic locking):假定會發生併發衝突,屏蔽一切可能違反數據完整性的操作。 悲觀鎖是數據庫層面加鎖,都會阻塞去等待鎖。

  • 樂觀鎖(optimistic locking):假設不會發生併發衝突,只在提交操作時檢查是否違反數據完整性。 樂觀鎖是一種思想,具體實現是,表中有一個版本字段,第一次讀的時候,獲取到這個字段。處理完業務邏輯開始更新的時候,需要再次查看該字段的值是否和第一次的一樣。如果一樣更新,反之拒絕。之所以叫樂觀,因爲這個模式沒有從數據庫加鎖,等到更新的時候再判斷是否可以更新。 缺點:併發很高的時候,多了很多無用的重試。樂觀鎖,不能解決髒讀的問題。


圖片上傳失敗

重試


鎖策略:鎖的粒度/粒度維度(lock granularity)

鎖的開銷是較爲昂貴的,鎖策略其實就是保證了線程安全的同時獲取最大的性能之間的平衡策略

行級鎖(row-level locking)

行鎖:即只允許事務讀一行數據。行鎖的粒度實在每一條行數據,當然也帶來了最大開銷,但是行鎖可以最大限度的支持併發處理。

開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

最大程度的支持併發,同時也帶來了最大的鎖開銷。

行級鎖更適合於有大量按索引條件併發更新少量不同數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統

在 InnoDB 中,除單個 SQL 組成的事務外,鎖是逐步獲得的,這就決定了在 InnoDB 中發生死鎖是可能的。

行級鎖只在存儲引擎層實現,而Mysql服務器層沒有實現。

表級鎖(table-level locking)

表鎖:允許事務在行級上的鎖和表級上的鎖同時存在。鎖定整個表,開銷最小,但是也阻塞了整個表。

開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。

這些存儲引擎通過總是一次性同時獲取所有需要的鎖以及總是按相同的順序獲取表鎖來避免死鎖。

表級鎖更適合於以查詢爲主,併發用戶少,只有少量按索引條件更新數據的應用,如Web 應用。

  • 若一個用戶正在執行寫操作,會獲取排他的“寫鎖”,這可能會鎖定整個表,阻塞其他用戶的讀、寫操作;

  • 若一個用戶正在執行讀操作,會先獲取共享鎖“讀鎖”,這個鎖運行其他讀鎖併發的對這個表進行讀取,互不干擾。只要沒有寫鎖的進入,讀鎖可以是併發讀取統一資源的。

Mysql的表級別鎖分爲兩類:元數據鎖(Metadata Lock,MDL)、表鎖。

元數據鎖(Metadata Lock,MDL)

元數據鎖(MDL) 不需要顯式使用,在訪問一個表的時候會被自動加上。這個特性需要MySQL5.5版本以上纔會支持,

  • 當對一個表做增刪改查的時候,該表會被加MDL讀鎖

  • 當對錶做結構變更的時候,加MDL寫鎖

MDL鎖規則:

  • 讀鎖之間不互斥,所以可以多線程多同一張表進行增刪改查。

  • 讀寫鎖、寫鎖之間是互斥的,爲了保證表結構變更的安全性,所以如果要多線程對同一個表加字段等表結構操作,就會變成串行化,需要進行鎖等待。

  • MDL的寫鎖優先級比MDL讀鎖的優先級,但是可以設置max_write_lock_count系統變量來改變這種情況,當寫鎖請求超過這個變量設置的數後,MDL讀鎖的優先級會比MDL寫鎖的優先級高。(默認情況下,這個數字會很大,所以不用擔心寫鎖的優先級下降)

  • MDL的鎖釋放必須要等到事務結束纔會釋放

頁面鎖(page-level locking)

頁級鎖定是 MySQL 中比較獨特的一種鎖定級別,在其他數據庫管理軟件中也並不是太常見。

頁面鎖開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

頁級鎖定的特點是鎖定顆粒度介於行級鎖定與表級鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的併發處理能力也同樣是介於上面二者之間。另外,頁級鎖定和行級鎖定一樣,會發生死鎖。

在數據庫實現資源鎖定的過程中,隨着鎖定資源顆粒度的減小,鎖定相同數據量的數據所需要消耗的內存數量是越來越多的,實現算法也會越來越複雜。

不過,隨着鎖定資源顆粒度的減小,應用程序的訪問請求遇到鎖等待的可能性也會隨之降低,系統整體併發度也隨之提升。

使用頁級鎖定的主要是 BerkeleyDB 存儲引擎

全局鎖

MySQL 提供全局鎖來對整個數據庫實例加鎖。

FLUSH TABLES WITH READ LOCK

這條語句一般都是用來備份的,當執行這條語句後,數據庫所有打開的表都會被關閉,並且使用全局讀鎖鎖定數據庫的所有表,同時,其他線程的更新語句(增刪改),數據定義語句(建表,修改表結構)和更新類的事務提交都會被阻塞。

在mysql 8.0 以後,對於備份,mysql可以直接使用備份鎖。

LOCK INSTANCE FOR BACKUP UNLOCK INSTANCE

這個鎖的作用範圍更廣,這個鎖會阻止文件的創建,重命名,刪除,包括 REPAIR TABLE TRUNCATE TABLE, OPTIMIZE TABLE操作以及賬戶的管理都會被阻塞。當然這些操作對於內存臨時表來說是可以執行的,爲什麼內存表不受這些限制呢?因爲內存表不需要備份,所以也就沒必要滿足這些條件。

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行級鎖和表級鎖

InnoDB鎖模式:

InnoDB 實現了以下兩種類型的行鎖:

  • 共享鎖(S-shared):允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。

  • 排他鎖(X-exclusive):允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。

爲了支持在不同粒度上進行加鎖操作(允許行鎖和表鎖共存,實現多粒度鎖機制),InnoDB 還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖:

  • 意向共享鎖(IS- intent share lock)事務想要獲得一張表中某幾行的共享鎖 務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。

  • 意向排他鎖(IX -intent exclusive lock)事務想要獲得一張表中某幾行的排他鎖 事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的 IX 鎖。

由於InnoDB存儲引擎支持的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃以外的任何請求。故表級意向鎖與行級鎖的兼容性如下所示


v2-f0d8429e4b98504fad6be07953fe882c_hd.jpg


如果一個事務請求的鎖模式與當前的鎖兼容, InnoDB 就將請求的鎖授予該事務; 反之, 如果兩者不兼容,該事務就要等待鎖釋放


v2-a6369873739873d85144e18795f33f1e_hd.jpg


若將上鎖的對象看成一棵樹,那麼對最下層的對象上鎖,也就是對最細粒度的對象進行上鎖,那麼首先需要對粗粒度的對象上鎖。例上圖,如果需要對頁上的記錄r進行上X鎖,那麼分別需要對數據庫A、表、頁上意向鎖IX,最後對記錄r上X鎖。若其中任何一個部分導致等待,那麼該操作需要等待粗粒度鎖的完成。舉例來說,在對記錄r加X鎖之前,已經有事務對錶1進行了S表鎖,那麼表1上已存在S鎖,之後事務需要對記錄r在表1上加上IX,由於不兼容,所以該事務需要等待表鎖操作的完成。

意向鎖到底有什麼作用?

innodb的意向鎖主要用戶多粒度的鎖並存的情況。比如事務A要在一個表上加S鎖,如果表中的一行已被事務B加了X鎖,那麼該鎖的申請也應被阻塞。如果表中的數據很多,逐行檢查鎖標誌的開銷將很大,系統的性能將會受到影響。爲了解決這個問題,可以在表級上引入新的鎖類型來表示其所屬行的加鎖情況,這就引出了“意向鎖”的概念。

舉個例子,如果表中記錄1億,事務A把其中有幾條記錄上了行鎖了,這時事務B需要給這個表加表級鎖,如果沒有意向鎖的話,那就要去表中查找這一億條記錄是否上鎖了。如果存在意向鎖,那麼假如事務A在更新一條記錄之前,先加意向鎖,再加X鎖,事務B先檢查該表上是否存在意向鎖,存在的意向鎖是否與自己準備加的鎖衝突,如果有衝突,則等待直到事務A釋放,而無須逐條記錄去檢測。事務B更新表時,其實無須知道到底哪一行被鎖了,它只要知道反正有一行被鎖了就行了。

主要作用是處理行鎖和表鎖之間的矛盾,能夠顯示“某個事務正在某一行上持有了鎖,或者準備去持有鎖”

InnoDB 行鎖實現方式:

行鎖是加在索引上的


v2-e3fd038013a493f6c552d5029c89bb69_hd.jpg

v2-d387ac802766065a5a64360c29b2c7c3_hd.jpg


Innodb中的索引數據結構是 B+ 樹,數據是有序排列的,從根節點到葉子節點一層層找到對應的數據

普通索引,也叫做輔助索引,葉子節點存放的是主鍵值。主鍵上的索引叫做聚集索引,表裏的每一條記錄都存放在主鍵的葉子節點上。當通過輔助索引select 查詢數據的時候,會先在輔助索引中找到對應的主鍵值,然後用主鍵值在聚集索引中找到該條記錄。

舉個例子,用name=Alice來查詢的時候,會先找到對應的主鍵值是18 ,然後用18在下面的聚集索引中找到name=Alice的記錄內容是 77 和 Alice。

表中每一行的數據,是組織存放在聚集索引中的,所以叫做索引組織表

  • InnoDB 行鎖是通過給索引上的索引項加鎖來實現的,這一點 MySQL 與 Oracle 不同,後者是通過在數據塊中對相應數據行加鎖來實現的。InnoDB 這種行鎖實現特點意味着:只有通過索引條件檢索數據,InnoDB 才使用行級鎖,否則,InnoDB 將使用表鎖!

  • 不論是使用主鍵索引、唯一索引或普通索引,InnoDB 都會使用行鎖來對數據加鎖

  • 只有執行計劃真正使用了索引,才能使用行鎖:即便在條件中使用了索引字段,但是否使用索引來檢索數據是由 MySQL 通過判斷不同執行計劃的代價來決定的,如果 MySQL 認爲全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。因此,在分析鎖衝突時,別忘了檢查 SQL 的執行計劃(可以通過 explain 檢查 SQL 的執行計劃),以確認是否真正使用了索引。(更多閱讀:MySQL索引總結

  • 由於 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然多個session是訪問不同行的記錄, 但是如果是使用相同的索引鍵, 是會出現鎖衝突的(後使用這些索引的session需要等待先使用索引的session釋放鎖後,才能獲取鎖)。 應用設計的時候要注意這一點。

行鎖的算法(算法維度)

  • Record Lock(單行記錄)

  • Gap Lock(間隙鎖,鎖定一個範圍,但不包含鎖定記錄)

  • Next-Key Lock(Record Lock + Gap Lock,鎖定一個範圍,並且鎖定記錄本身, MySql 防止幻讀,就是使用此鎖實現)

記錄鎖、間隙鎖、臨鍵鎖都是排它鎖

記錄鎖(Record Lock)

事務加鎖後鎖住的只是表的某一條記錄。


圖片上傳失敗

重試


記錄鎖出現條件:精準條件命中,並且命中的條件字段是唯一索引;

例如:update user_info set name=’張三’ where id=1 ,這裏的id是唯一索引。

Record Lock總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。

記錄鎖的作用:加了記錄鎖之後可以避免數據在查詢的時候被修改的重複讀問題,也避免了在修改的事務未提交前被其他事務讀取的髒讀問題。

臨鍵鎖(Next-Key Lock)

臨鍵鎖是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對於行的查詢都是採用這種鎖定算法。

除了Next-Key Locking,還有Previous-Key Locking技術。


v2-ac509a97dc73a8593dfefeccb1a53814_hd.jpg

v2-2a8ffa802abba381729de7b77da93171_hd.jpg


間隙鎖(gap lcok ):

當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖

很顯然,在使用範圍條件檢索並鎖定記錄時,InnoDB這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是併發插入比較多的應用,我們要儘量優化業務邏輯,儘量使用相等條件來訪問更新數據,避免使用範圍條件。

間隙鎖的目的:

  1. 防止幻讀,以滿足相關隔離級別的要求;

  2. 滿足恢復和複製的需要:

產生間隙鎖的條件(RR事務隔離級別下):

  1. 使用普通索引鎖定;

  2. 使用多列唯一索引;

  3. 使用唯一索引鎖定多行記錄。

以上情況,都會產生間隙鎖

推薦閱讀《MySQL的鎖機制 - 記錄鎖、間隙鎖、臨鍵鎖

MySQL 通過 BINLOG 錄入執行成功的 INSERT、UPDATE、DELETE 等更新數據的 SQL 語句,並由此實現 MySQL 數據庫的恢復和主從複製。MySQL 的恢復機制(複製其實就是在 Slave Mysql 不斷做基於 BINLOG 的恢復)有以下特點:

  • 一是 MySQL 的恢復是 SQL 語句級的,也就是重新執行 BINLOG 中的 SQL 語句。

  • 二是 MySQL 的 Binlog 是按照事務提交的先後順序記錄的, 恢復也是按這個順序進行的。

由此可見,MySQL 的恢復機制要求:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀


v2-2b9d0662bb6a50d2ab3dd2085386c82c_hd.jpg

圖片上傳失敗

重試

圖片上傳失敗

重試


這張圖裏出現了三種鎖

  • 記錄鎖:單行記錄上的鎖

  • 間隙鎖:鎖定記錄之間的範圍,但不包含記錄本身。

  • Next Key Lock: 記錄鎖+ 間隙鎖,鎖定一個範圍,包含記錄本身。

不是所有索引都會加上Next-key Lock的,在查詢的列是唯一索引(包含主鍵索引)的情況下,Next-key Lock會降級爲Record Lock。

CREATE TABLE z (a INT,b INT,PRIMARY KEY(a),KEY(b));// a是主鍵索引,b是普通索引
INSERT INTO z select1,1;
INSERT INTO z select3,1;
INSERT INTO z select5,3;
INSERT INTO z select7,6;
INSERT INTO z select10,8;

這時候在會話A中執行 SELECT*FROM z WHERE b=3FOR UPDATE ,索引鎖定如下:


v2-e27f49fb2bdbcea8053c20b6a0b7d89b_hd.jpg


這時候會話B執行的語句落在鎖定範圍內的都會進行waiting

SELECT * FROM z WHERE a =5 LOCK IN SHARE MODE;
INSERT INTO z SELECT 4,2;
INSERT INTO z SELECT 6,5;


用戶可以通過以下兩種方式來顯示的關閉Gap Lock:

  • 將事務的隔離級別設爲 READ COMMITED。

  • 將參數innodblocksunsafeforbinlog設置爲1。

從上面的例子可以看出來,Gap Lock的作用是爲了阻止多個事務將記錄插入到同一個範圍內,設計它的目的是用來解決Phontom Problem(幻讀問題)。在MySQL默認的隔離級別(Repeatable Read)下,InnoDB就是使用它來解決幻讀問題。

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 可以查詢該記錄,但是不能對該記錄加共享鎖或排他鎖,而是等待獲得鎖

隱式鎖定:

InnoDB在事務執行過程中,使用兩階段鎖協議:

  • 隨時都可以執行鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖;

  • 鎖只有在執行commit或者rollback的時候纔會釋放,並且所有的鎖都是在同一時刻被釋放。

顯式鎖定 :

select ... lock in share mode //共享鎖 
select ... for update //排他鎖

select for update

在執行這個 select 查詢語句的時候,會將對應的索引訪問條目進行上排他鎖(X 鎖),也就是說這個語句對應的鎖就相當於update帶來的效果。

select *** for update 的使用場景:爲了讓自己查到的數據確保是最新數據,並且查到後的數據只允許自己來修改的時候,需要用到 for update 子句。

select * from user where id=10 for update    

通過鎖住聚集索引中的節點來鎖住這條記錄(鎖住id=10的索引,即鎖住了這條記錄)。

select * from user where name=‘b’ for update

這裏的name上加了唯一索引,唯一索引本質上是輔助索引,加了唯一約束。所以會先在輔助索引上找到name爲d的索引記錄,在輔助索引中加鎖,然後查找聚集索引,鎖住對應索引記錄。

爲什麼聚簇索引上的記錄也要加鎖?

試想一下,如果有併發的另外一個SQL,是直接通過主鍵索引id=30來更新,會先在聚集索引中請求加鎖。如果只在輔助索引中加鎖的話,兩個併發SQL之間是互相感知不到的。


v2-818ce19721f6979914c10b9b223713dc_hd.jpg

v2-d80be08fd1283a9a04ae92f1c21a0047_hd.jpg


select lock in share mode

in share mode 子句的作用就是將查找到的數據加上一個 share 鎖,這個就是表示其他的事務只能對這些數據進行簡單的select 操作,並不能夠進行 DML 操作。

select *** lock in share mode 使用場景:爲了確保自己查到的數據沒有被其他的事務正在修改,也就是說確保查到的數據是最新的數據,並且不允許其他人來修改數據。但是自己不一定能夠修改數據,因爲有可能其他的事務也對這些數據 使用了 in share mode 的方式上了 S 鎖。

for update 和 lock in share mode 的區別:

前一個上的是排他鎖(X 鎖),一旦一個事務獲取了這個鎖,其他的事務是沒法在這些數據上執行 for update ;後一個是共享鎖,多個事務可以同時的對相同數據執行 lock in share mode。

顯式鎖定對性能影響(performance impact):

select for update 語句,相當於一個 update 語句。在業務繁忙的情況下,如果事務沒有及時的commit或者rollback 可能會造成其他事務長時間的等待,從而影響數據庫的併發使用效率。

select lock in share mode 語句是一個給查找的數據上一個共享鎖(S 鎖)的功能,它允許其他的事務也對該數據上S鎖,但是不能夠允許對該數據進行修改。如果不及時的commit 或者rollback 也可能會造成大量的事務等待。

默認的讀操作,上鎖嗎

默認是 MVCC 機制(“一致性非鎖定讀-consistent nonlocking read”)保證 RR 級別的隔離正確性,是不上鎖的

可以選擇手動上鎖:select xxxx for update (排他鎖); select xxxx lock in share mode(共享鎖),稱之爲“一致性鎖定讀”。

使用鎖之後,就能在 RR 級別下,避免幻讀。當然,默認的 MVCC 讀,也能避免幻讀。

既然 RR 能夠防止幻讀,那麼,SERIALIZABLE 有啥用呢?防止丟失更新。


v2-4b76421c48f15c3a94a63af2cc660e91_hd.jpg


這個時候,我們必須使用 SERIALIZABLE 級別進行串行讀取。

最後,行鎖的實現原理就是鎖住聚集索引,如果你查詢的時候,沒有正確地擊中索引,MySql 優化器將會拋棄行鎖,使用表鎖。

InnoDB 在不同隔離級別下的一致性讀及鎖的差異:

鎖和多版本數據(MVCC)是 InnoDB 實現一致性讀和 ISO/ANSI SQL92 隔離級別的手段。

因此,在不同的隔離級別下,InnoDB 處理 SQL 時採用的一致性讀策略和需要的鎖是不同的:


圖片上傳失敗

重試



v2-a104f32137441b7b91f338f81ec25b15_hd.jpg


對於許多 SQL,隔離級別越高,InnoDB 給記錄集加的鎖就越嚴格(尤其是使用範圍條件的時候),產生鎖衝突的可能性也就越高,從而對併發性事務處理性能的 影響也就越大。

因此, 我們在應用中, 應該儘量使用較低的隔離級別, 以減少鎖爭用的機率。實際上,通過優化事務邏輯,大部分應用使用 Read Commited 隔離級別就足夠了。對於一些確實需要更高隔離級別的事務, 可以通過在程序中執行 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 或 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE 動態改變隔離級別的方式滿足需求。

InnoDB 行鎖優化建議

合理利用 InnoDB 的行級鎖定,做到揚長避短

  1. 儘可能讓所有的數據檢索都通過索引來完成,從而避免 InnoDB 因爲無法通過索引鍵加鎖而升級爲表級鎖定。

  2. 合理設計索引,讓 InnoDB 在索引鍵上面加鎖的時候儘可能準確,儘可能的縮小鎖定範圍,避免造成不必要的鎖定而影響其他 Query 的執行。

  3. 儘可能減少基於範圍的數據檢索過濾條件,避免因爲間隙鎖帶來的負面影響而鎖定了不該鎖定的記錄。

  4. 儘量控制事務的大小,減少鎖定的資源量和鎖定時間長度。

  5. 在業務環境允許的情況下,儘量使用較低級別的事務隔離,以減少 MySQL 因爲實現事務隔離級別所帶來的附加成本。


infoDB什麼時候加表鎖

對於 InnoDB 表,在絕大部分情況下都應該使用行級鎖,因爲事務和行鎖往往是我們之所以選擇 InnoDB 表的理由。

事務需要更新大部分或全部數據,表又比較大,如果使用默認的行鎖,不僅這個事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖衝突,這種情況下可以考慮使用表鎖來提高該事務的執行速度。

事務涉及多個表,比較複雜,很可能引起死鎖,造成大量事務回滾。這種情況也可以考慮一次性鎖定事務涉及的表,從而避免死鎖、減少數據庫因事務回滾帶來的開銷。

在 InnoDB 下,使用表鎖要注意以下兩點:

  1. 使用 LOCK TABLES 雖然可以給 InnoDB 加表級鎖,但必須說明的是,表鎖不是由 InnoDB 存儲引擎層管理的,而是由其上一層──MySQL Server 負責的。僅當 autocommit=0(不自動提交,默認是自動提交的)、InnoDB_table_locks=1(默認設置)時,InnoDB 層才能知道 MySQL 加的表鎖,MySQL Server 也才能感知 InnoDB 加的行鎖。這種情況下,InnoDB 才能自動識別涉及表級鎖的死鎖,否則,InnoDB 將無法自動檢測並處理這種死鎖。

  2. 在用 LOCK TABLES 對 InnoDB 表加鎖時要注意,要將 AUTOCOMMIT 設爲 0,否則 MySQL 不會給表加鎖。事務結束前,不要用 UNLOCK TABLES 釋放表鎖,因爲 UNLOCK TABLES 會隱含地提交事務。COMMIT 或 ROLLBACK 並不能釋放用 LOCK TABLES 加的表級鎖,必須用 UNLOCK TABLES 釋放表鎖。

正確的方式見如下語句,例如,如果需要寫表 t1 並從表 t 讀,可以按如下做:

SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;


MyISAM 表鎖

MyISAM表級鎖模式:

  • 表共享讀鎖 (Table Read Lock):不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;

  • 表獨佔寫鎖 (Table Write Lock):會阻塞其他用戶對同一表的讀和寫操作;

MyISAM 表的讀操作與寫操作之間,以及寫操作之間是串行的。當一個線程獲得對一個表的寫鎖後, 只有持有鎖的線程可以對錶進行更新操作。 其他線程的讀、 寫操作都會等待,直到鎖被釋放爲止。

默認情況下,寫鎖比讀鎖具有更高的優先級:當一個鎖釋放時,這個鎖會優先給寫鎖隊列中等候的獲取鎖請求,然後再給讀鎖隊列中等候的獲取鎖請求。 (This ensures that updates to a table are not “starved” even when there is heavy SELECT activity for the table. However, if there are many updates for a table, SELECT statements wait until there are no more updates.)。

這也正是 MyISAM 表不太適合於有大量更新操作和查詢操作應用的原因,因爲,大量的更新操作會造成查詢操作很難獲得讀鎖,從而可能永遠阻塞。同時,一些需要長時間運行的查詢操作,也會使寫線程“餓死” ,應用中應儘量避免出現長時間運行的查詢操作(在可能的情況下可以通過使用中間表等措施對SQL語句做一定的“分解” ,使每一步查詢都能在較短時間完成,從而減少鎖衝突。如果複雜查詢不可避免,應儘量安排在數據庫空閒時段執行,比如一些定期統計可以安排在夜間執行)。

可以設置改變讀鎖和寫鎖的優先級:

  • 通過指定啓動參數low-priority-updates,使MyISAM引擎默認給予讀請求以優先的權利。

  • 通過執行命令SET LOW_PRIORITY_UPDATES=1,使該連接發出的更新請求優先級降低。

  • 通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先級。

  • 給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL就暫時將寫請求的優先級降低,給讀進程一定獲得鎖的機會。

MyISAM加表鎖方法:

  • 在執行查詢語句(SELECT)前,會自動給涉及的表加讀鎖

  • 在執行更新操作(UPDATE、DELETE、INSERT 等)前,會自動給涉及的表加寫鎖

這個過程並不需要用戶干預,因此,用戶一般不需要直接用 LOCK TABLE 命令給 MyISAM 表顯式加鎖。

在自動加鎖的情況下,MyISAM 總是一次獲得 SQL 語句所需要的全部鎖,這也正是 MyISAM 表不會出現死鎖(Deadlock Free)的原因

MyISAM存儲引擎支持併發插入,以減少給定表的讀和寫操作之間的爭用:

如果MyISAM表在數據文件中間沒有空閒塊,則行始終插入數據文件的末尾。 在這種情況下,你可以自由混合併發使用MyISAM表的INSERT和SELECT語句而不需要加鎖——你可以在其他線程進行讀操作的時候,同時將行插入到MyISAM表中。 文件中間的空閒塊可能是從表格中間刪除或更新的行而產生的。 如果文件中間有空閒快,則併發插入會被禁用,但是當所有空閒塊都填充有新數據時,它又會自動重新啓用。 要控制此行爲,可以使用MySQL的concurrent_insert系統變量。

如果你使用LOCK TABLES顯式獲取表鎖,則可以請求READ LOCAL鎖而不是READ鎖,以便在鎖定表時,其他會話可以使用併發插入。

  • 當concurrent_insert設置爲0時,不允許併發插入。

  • 當concurrent_insert設置爲1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個線程讀表的同時,另一個線程從表尾插入記錄。這也是MySQL的默認設置。

  • 當concurrent_insert設置爲2時,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。

顯示鎖sql語句

  • 共享讀鎖:lock table tableName read

  • 獨佔寫鎖:lock table tableName write

  • 同時加多鎖:lock table t1 write,t2 read

  • 批量解鎖:unlock tables

MyISAM 表鎖優化建議

縮短鎖定時間

  1. 唯一的辦法就是讓我們的 Query 執行時間儘可能的短

  2. 儘量減少大的複雜 Query,將複雜 Query 分拆成幾個小的 Query 分佈進行。

  3. 儘可能的建立足夠高效的索引,讓數據檢索更迅速。

  4. 儘量讓 MyISAM 存儲引擎的表只存放必要的信息,控制字段類型。

  5. 利用合適的機會優化 MyISAM 表數據文件。

分離能並行的操作

MyISAM 存儲引擎有一個控制是否打開 Concurrent Insert 功能的參數選項:


  • concurrent_insert=2,無論 MyISAM 表中有沒有空洞,都允許在表尾併發插入記錄。

  • concurrent_insert=1,如果 MyISAM 表中沒有空洞(即表的中間沒有被刪除的行),MyISAM 允許在一個進程讀表的同時,另一個進程從表尾插入記錄。這也是 MySQL 的默認設置。

  • concurrent_insert=0,不允許併發插入

可以利用 MyISAM 存儲引擎的併發插入特性,來解決應用中對同一表查詢和插入的鎖爭用。

例如,將 concurrent_insert 系統變量設爲 2,總是允許併發插入;同時,通過定期在系統空閒時段執行 OPTIMIZE TABLE 語句來整理空間碎片,收回因刪除記錄而產生的中間空洞。

合理利用讀寫優先級

  1. 通過執行命令 SET LOW_PRIORITY_UPDATES=1,使該連接讀比寫的優先級高,如果我們的系統是一個以讀爲主,可以設置此參數,如果以寫爲主,則不用設置。

  2. 通過指定 INSERT、UPDATE、DELETE 語句的 LOW_PRIORITY 屬性,降低該語句的優先級。

  3. MySQL 也提供了一種折中的辦法來調節讀寫衝突,即給系統參數 max_write_lock_count 設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL 就暫時將寫請求的優先級降低,給讀進程一定獲得鎖的機會。

  4. 需要長時間運行的查詢操作,也會使寫進程“餓死”,儘量避免出現長時間運行的查詢操作,不要總想用一條 SELECT 語句來解決問題,因爲這種看似巧妙的 SQL 語句,往往比較複雜,執行時間較長。

  5. 多表級聯。事務涉及多個表,比較複雜的關聯查詢,很可能引起死鎖,造成大量事務回滾,這種情況若能一次性鎖定事務涉及的表,從而可以避免死鎖、減少數據庫因事務回滾帶來的開銷。

死鎖(Deadlock Free)

  • 死鎖產生:

  • 死鎖是指兩個或多個事務在同一資源上相互佔用,並請求鎖定對方佔用的資源,從而導致惡性循環。

  • 當事務試圖以不同的順序鎖定資源時,就可能產生死鎖。多個事務同時鎖定同一個資源時也可能會產生死鎖。

  • 鎖的行爲和順序和存儲引擎相關。以同樣的順序執行語句,有些存儲引擎會產生死鎖有些不會——死鎖有雙重原因:真正的數據衝突;存儲引擎的實現方式。

  • 檢測死鎖:數據庫系統實現了各種死鎖檢測和死鎖超時的機制。InnoDB存儲引擎能檢測到死鎖的循環依賴並立即返回一個錯誤。

  • 死鎖恢復:死鎖發生以後,只有部分或完全回滾其中一個事務,才能打破死鎖,InnoDB目前處理死鎖的方法是,將持有最少行級排他鎖的事務進行回滾。所以事務型應用程序在設計時必須考慮如何處理死鎖,多數情況下只需要重新執行因死鎖回滾的事務即可。

  • 外部鎖的死鎖檢測:發生死鎖後,InnoDB 一般都能自動檢測到,並使一個事務釋放鎖並回退,另一個事務獲得鎖,繼續完成事務。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB 並不能完全自動檢測到死鎖, 這需要通過設置鎖等待超時參數 innodb_lock_wait_timeout 來解決

  • 死鎖影響性能:死鎖會影響性能而不是會產生嚴重錯誤,因爲InnoDB會自動檢測死鎖狀況並回滾其中一個受影響的事務。在高併發系統上,當許多線程等待同一個鎖時,死鎖檢測可能導致速度變慢。 有時當發生死鎖時,禁用死鎖檢測(使用innodb_deadlock_detect配置選項)可能會更有效,這時可以依賴innodb_lock_wait_timeout設置進行事務回滾。


MyISAM避免死鎖:

在自動加鎖的情況下,MyISAM 表不會出現死鎖(MyISAM 總是一次獲得 SQL 語句所需要的全部鎖)。

InnoDB避免死鎖:

  • 爲了在單個InnoDB表上執行多個併發寫入操作時避免死鎖,可以在事務開始時通過爲預期要修改的每個元祖(行)使用SELECT ... FOR UPDATE語句來獲取必要的鎖,即使這些行的更改語句是在之後才執行的。

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

  • 如果事務需要修改或鎖定多個表,則應在每個事務中以相同的順序使用加鎖語句。 在應用中,如果不同的程序會併發存取多個表,應儘量約定以相同的順序來訪問表,這樣可以大大降低產生死鎖的機會

  • 通過SELECT ... LOCK IN SHARE MODE獲取行的讀鎖後,如果當前事務再需要對該記錄進行更新操作,則很有可能造成死鎖。

  • 改變事務隔離級別,如降低隔離級別(如果業務允許,將隔離級別調低也是較好的選擇,比如將隔離級別從RR調整爲RC,可以避免掉很多因爲gap鎖造成的死鎖)

  • 爲表添加合理的索引。可以看到如果不走索引將會爲表的每一行記錄添加上鎖,死鎖的概率大大增大。

如果出現死鎖,可以用 SHOW INNODB STATUS 命令來確定最後一個死鎖產生的原因。返回結果中包括死鎖相關事務的詳細信息,如引發死鎖的 SQL 語句,事務已經獲得的鎖,正在等待什麼鎖,以及被回滾的事務等。據此可以分析死鎖產生的原因和改進措施。

加鎖處理分析

下面兩條簡單的SQL,他們加什麼鎖?

select * from t1 where id = 10

delete from t1 where id = 10

如果要分析加鎖情況,必須還要知道以下的一些前提,前提不同,加鎖處理的方式也不同

  1. id列是不是主鍵?

  2. 當前系統的隔離級別是什麼?

  3. id列如果不是主鍵,那麼id列上有索引嗎?

  4. id列上如果有二級索引,那麼這個索引是唯一索引嗎?

  5. 兩個SQL的執行計劃是什麼?索引掃描?全表掃描?

根據上述情況,有以下幾種組合

  1. id列是主鍵,RC隔離級別

  2. id列是二級唯一索引,RC隔離級別

  3. id列是二級非唯一索引,RC隔離級別

  4. id列上沒有索引,RC隔離級別

  5. id列是主鍵,RR隔離級別

  6. id列是二級唯一索引,RR隔離級別

  7. id列是二級非唯一索引,RR隔離級別

  8. id列上沒有索引,RR隔離級別

  9. Serializable隔離級別

排列組合還沒有列舉完全,但是看起來,已經很多了。真的有必要這麼複雜嗎?事實上,要分析加鎖,就是需要這麼複雜。但是從另一個角度來說,只要你選定了一種組合,SQL需要加哪些鎖,其實也就確定了。接下來挑幾個比較經典的組合

1. id主鍵+RC

這個組合,是最簡單,最容易分析的組合。id是主鍵,Read Committed隔離級別,給定SQL:delete from t1 where id =  10; 只需要將主鍵上,id = 10的記錄加上X鎖即可。如下圖1:

結論:id是主鍵時,此SQL只需要在id=10這條記錄上加X鎖即可。

2. id唯一索引+RC

這個組合,id不是主鍵,而是一個Unique的二級索引鍵值。那麼在RC隔離級別下,delete from t1 where id = 10;  需要加什麼鎖呢?見下圖2:

id是unique索引,而主鍵是name列。此時,加鎖的情況由於組合一有所不同。由於id是unique索引,因此delete語句會選擇走id列的索引進行where條件的過濾,在找到id=10的記錄後,首先會將unique索引上的id=10索引記錄加上X鎖,同時,會根據讀取到的name列,回主鍵索引(聚簇索引),然後將聚簇索引上的name  = ‘d’ 對應的主鍵索引項加X鎖。

結論:若id列是unique列,其上有unique索引。那麼SQL需要加兩個X鎖,一個對應於id unique索引上的id =  10的記錄,另一把鎖對應於聚簇索引上的[name='d',id=10]的記錄、

3. id非唯一索引+RC

相對於組合一、二,組合三又發生了變化,隔離級別仍舊是RC不變,但是id列上的約束又降低了,id列不再唯一,只有一個普通的索引。假設delete  from t1 where id = 10; 語句,仍舊選擇id列上的索引進行過濾where條件,那麼此時會持有哪些鎖?同樣見下圖3:根據此圖,可以看到,首先,id列索引上,滿足id =  10查詢條件的記錄,均已加鎖。同時,這些記錄對應的主鍵索引上的記錄也都加上了鎖。與組合二唯一的區別在於,組合二最多隻有一個滿足等值查詢的記錄,而組合三會將所有滿足查詢條件的記錄都加鎖。

結論:若id列上有非唯一索引,那麼對應的所有滿足SQL查詢條件的記錄,都會被加鎖。同時,這些記錄在主鍵索引上的記錄,也會被加鎖。


v2-a91117fbfcc08b61a3c5f72b8abdebd9_hd.jpg

v2-7f74352f69813f58e878007d8899ad92_hd.jpg

圖片上傳失敗

重試



4. id非唯一索引+RR

還記得前面提到的MySQL的四種隔離級別的區別嗎?RC隔離級別允許幻讀,而RR隔離級別,不允許存在幻讀。但是在組合五、組合六中,加鎖行爲又是與RC下的加鎖行爲完全一致。那麼RR隔離級別下, 組合七,Repeatable  Read隔離級別,id上有一個非唯一索引,執行delete from t1 where id = 10;  假設選擇id列上的索引進行條件過濾,最後的加鎖行爲,是怎麼樣的呢?同樣看下圖1:

結論:Repeatable Read隔離級別下,id列上有一個非唯一索引,對應SQL:delete from t1 where id =  10;  首先,通過id索引定位到第一條滿足查詢條件的記錄,加記錄上的X鎖,加GAP上的GAP鎖,然後加主鍵聚簇索引上的記錄X鎖,然後返回;然後讀取下一條,重複進行。直至進行到第一條不滿足條件的記錄[11,f],此時,不需要加記錄X鎖,但是仍舊需要加GAP鎖,最後返回結束。什麼時候會取得gap  lock或nextkey lock 這和隔離級別有關,只在REPEATABLE READ或以上的隔離級別下的特定操作纔會取得gap  lock或nextkey lock。

5.id無索引+RC

相對於前面三個組合,這是一個比較特殊的情況。id列上沒有索引,where id =  10;這個過濾條件,沒法通過索引進行過濾,那麼只能走全表掃描做過濾。對應於這個組合,SQL會加什麼鎖?或者是換句話說,全表掃描時,會加什麼鎖?這個答案也有很多:有人說會在表上加X鎖;有人說會將聚簇索引上,選擇出來的id  = 10;的記錄加上X鎖。那麼實際情況呢?請看下圖2:

由於id列上沒有索引,因此只能走聚簇索引,進行全部掃描。從圖中可以看到,滿足刪除條件的記錄有兩條,但是,聚簇索引上所有的記錄,都被加上了X鎖。無論記錄是否滿足條件,全部被加上X鎖。既不是加表鎖,也不是在滿足條件的記錄上加行鎖。 有人可能會問?爲什麼不是隻在滿足條件的記錄上加鎖呢?這是由於MySQL的實現決定的。如果一個條件無法通過索引快速過濾,那麼存儲引擎層面就會將所有記錄加鎖後返回,然後由MySQL  Server層進行過濾。因此也就把所有的記錄,都鎖上了。

結論:若id列上沒有索引,SQL會走聚簇索引的全掃描進行過濾,由於過濾是由MySQL  Server層面進行的。因此每條記錄,無論是否滿足條件,都會被加上X鎖。但是,爲了效率考量,MySQL做了優化,對於不滿足條件的記錄,會在判斷後放鎖,最終持有的,是滿足條件的記錄上的鎖,但是不滿足條件的記錄上的加鎖/放鎖動作不會省略。同時,優化也違背了2PL的約束。


6:id無索引+RR

組合八,Repeatable Read隔離級別下的最後一種情況,id列上沒有索引。此時SQL:delete from t1 where id =  10; 沒有其他的路徑可以選擇,只能進行全表掃描。最終的加鎖情況,圖3所示:


圖片上傳失敗

重試

v2-841c13e0631a631b4e3e570e8a06077a_hd.jpg

v2-9de6f6fdf66f96be0e0681b264869e09_hd.jpg


結論:在Repeatable  Read隔離級別下,如果進行全表掃描的當前讀,那麼會鎖上表中的所有記錄,同時會鎖上聚簇索引內的所有GAP,杜絕所有的併發 更新/刪除/插入  操作。當然,也可以通過觸發semi-consistent read,來緩解加鎖開銷與併發影響,但是semi-consistent  read本身也會帶來其他問題,不建議使用。

7 id主鍵+RR

上面的四個組合,都是在Read Committed隔離級別下的加鎖行爲,接下來的四個組合,是在Repeatable  Read隔離級別下的加鎖行爲。 組合五,id列是主鍵列,Repeatable Read隔離級別,針對delete from t1 where id  = 10; 這條SQL,加鎖與組合一:[id主鍵,Read Committed]一致。

8. 組合六:id唯一索引+RR

與組合五類似,組合六的加鎖,與組合二:[id唯一索引,Read  Committed]一致。兩個X鎖,id唯一索引滿足條件的記錄上一個,對應的聚簇索引上的記錄一個。

9. 組合九:Serializable

針對前面提到的簡單的SQL,最後一個情況:Serializable隔離級別。對於SQL2:delete from t1 where id = 10;  來說,Serializable隔離級別與Repeatable Read隔離級別完全一致,因此不做介紹。 Serializable隔離級別,影響的是SQL1:select  * from t1 where id = 10;  這條SQL,在RC,RR隔離級別下,都是快照讀,不加鎖。但是在Serializable隔離級別,SQL1會加讀鎖,也就是說快照讀不復存在,MVCC併發控制降級爲Lock-Based  CC。

結論:在MySQL/InnoDB中,所謂的讀不加鎖,並不適用於所有的情況,而是隔離級別相關的。Serializable隔離級別,讀不加鎖就不再成立,所有的讀操作,都是當前讀。

五、死鎖案例

1. 不同表相同記錄行鎖衝突

這種情況很好理解,事務A和事務B操作兩張表,但出現循環等待鎖情況。

2. 相同表記錄行鎖衝突

這種情況比較常見,之前遇到兩個job在執行數據批量更新時,jobA處理的的id列表爲[1,2,3,4],而job處理的id列表爲[8,9,10,4,2],這樣就造成了死鎖。


v2-5118569498f93f1ea0770bcb5cf7eb75_hd.jpg

v2-b272da305bdd254aba0ce11a7f0a037d_hd.jpg


3. 不同索引鎖衝突

這種情況比較隱晦,事務A在執行時,除了在二級索引加鎖外,還會在聚簇索引上加鎖,在聚簇索引上加鎖的順序是[1,4,2,3,5],而事務B執行時,只在聚簇索引上加鎖,加鎖順序是[1,2,3,4,5],這樣就造成了死鎖的可能性。

4. gap鎖衝突

innodb在RR級別下,如下的情況也會產生死鎖,比較隱晦。不清楚的同學可以自行根據上節的gap鎖原理分析下。


v2-eded08fc9e6eda67466cf7ae6e0e8526_hd.jpg

v2-3247e24bbe405ad438f852788ac64a55_hd.jpg


結論:在MySQL/InnoDB中,所謂的讀不加鎖,並不適用於所有的情況,而是隔離級別相關的。Serializable隔離級別,讀不加鎖就不再成立,所有的讀操作,都是當前讀

一些優化鎖性能的建議

  • 儘量使用較低的隔離級別;

  • 精心設計索引, 並儘量使用索引訪問數據, 使加鎖更精確, 從而減少鎖衝突的機會

  • 選擇合理的事務大小,小事務發生鎖衝突的機率也更小

  • 給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。比如要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖

  • 不同的程序訪問一組表時,應儘量約定以相同的順序訪問各表,對一個表而言,儘可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機會

  • 儘量用相等條件訪問數據,這樣可以避免間隙鎖對併發插入的影響

  • 不要申請超過實際需要的鎖級別

  • 除非必須,查詢時不要顯示加鎖。 MySQL的MVCC可以實現事務中的查詢不用加鎖,優化事務性能;MVCC只在COMMITTED READ(讀提交)和REPEATABLE READ(可重複讀)兩種隔離級別下工作

  • 對於一些特定的事務,可以使用表鎖來提高處理速度或減少死鎖的可能


相關鏈接:

MySQL鎖總結 https://zhuanlan.zhihu.com/p/29150809

MySQL鎖機制——你想知道的都在這了! https://zhuanlan.zhihu.com/p/75673270

詳解mysql的各種鎖(表鎖、行鎖、共享鎖、意向共享鎖、記錄鎖、間隙鎖、臨鍵鎖) https://zhuanlan.zhihu.com/p/52312376

對於MySQL你必須要了解的鎖知識 https://zhuanlan.zhihu.com/p/62525459

mysql鎖機制總結,以及優化建議  https://zhuanlan.zhihu.com/p/70889229

深入理解MySQL――鎖、事務與併發控制 這纔是正確的! https://zhuanlan.zhihu.com/p/36060546

深入理解MySQL鎖 https://zhuanlan.zhihu.com/p/8355298

MySQL(Innodb)索引的原理 https://zhuanlan.zhihu.com/p/62018452

數據庫兩大神器【索引和鎖】 https://zhuanlan.zhihu.com/p/40396971

深入理解MySQL鎖 https://zhuanlan.zhihu.com/p/83552985

原文:再談mysql鎖機制及原理-鎖的詮釋 - mysql - 周陸軍的個人網站 修改更新只在原文,文有不妥之處,請留言告知。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章