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

zhoulujun發表於2019-10-20

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

什麼是鎖?

鎖是計算機協調多個程式或執行緒併發訪問某一資源的機制

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

  • 鎖衝突也是影響資料庫併發訪問效能的一個重要因素。

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

mysql伺服器邏輯架構

為什麼要加鎖

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

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

加鎖解決了 多使用者環境下保證資料庫完整性和一致性。

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儲存引擎支援的是行級別的鎖,因此意向鎖其實不會阻塞除全表掃以外的任何請求。故表級意向鎖與行級鎖的相容性如下所示

鎖模式的相容情況

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

mysql資料儲存結構

若將上鎖的物件看成一棵樹,那麼對最下層的物件上鎖,也就是對最細粒度的物件進行上鎖,那麼首先需要對粗粒度的物件上鎖。例上圖,如果需要對頁上的記錄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 行鎖實現方式:

行鎖是加在索引上的

輔助索引聚集索引

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)

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

記錄鎖(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技術。

Next-Key Lock 鎖定機制Previous-Key Locking

間隙鎖(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 的恢復機制要求:在一個事務未提交前,其他併發事務不能插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀

這張圖裡出現了三種鎖

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

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

  • 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 ,索引鎖定如下:

,Next-key Lock降級為Record Lock

這時候會話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之間是互相感知不到的。

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 有啥用呢?防止丟失更新。

SERIALIZABLE的作用

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

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

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

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

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

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

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

對於許多 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查詢條件的記錄,都會被加鎖。同時,這些記錄在主鍵索引上的記錄,也會被加鎖。

id主鍵+RCid唯一索引+RCid非唯一索引+RC


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所示:

id非唯一索引+RRid無索引+RCid無索引+RR

結論:在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],這樣就造成了死鎖。

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

3. 不同索引鎖衝突

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

4. gap鎖衝突

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

不同索引鎖衝突gap鎖衝突

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

一些優化鎖效能的建議

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

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

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

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

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

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

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

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

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

相關連結:

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

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

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

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

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

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

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

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

資料庫兩大神器【索引和鎖】 zhuanlan.zhihu.com/p/40396971

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

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


相關文章