Coser頂流Enako談遊戲直播異聞 或有靈體自動播
參考書籍《mysql是怎樣執行的》
推薦極客時間《mysql實戰45講》
一丶為什麼資料庫需要鎖
資料庫鎖設計的初衷是處理併發問題。作為多使用者共享 的資源,當出現併發訪問的時候,資料庫需要合理地控制資源的訪問規則。而鎖就是用來實 現這些訪問規則的重要資料結構。
根據加鎖的範圍,MySQL 裡面的鎖大致可以分成全域性鎖
、表級鎖
和行鎖
三類
二丶全域性鎖&全庫邏輯備份
全域性鎖就是對整個資料庫例項加鎖。全域性鎖的典型使用場景是,做全庫邏輯備份,全庫邏輯備份有以下幾種方式:
1.Flush tables with read lock (FTWRL)
Flush tables with read lock (FTWRL)
2.mysqldump –single-transaction
使用此命令會先啟動一個事務拿到一致性檢視。由於 MVCC 的支援, 這個過程中其他執行緒可以進行正常操作,但是使用mysqldump –single-transaction
的前提是需要支援事務,如果存在MyISAM引擎的表,並不能保證一致性。
3.set global readonly=true
set global readonly=true
- 在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。因此,修改 global 變數的方式影響面更大。
- 在異常處理機制上有差異。如果執行 FTWRL 命令之後由於客戶端發生異常斷開, 那麼 MySQL 會自動釋放這個全域性鎖,整個庫回到可以正常更新的狀態。而將整個庫設定為 readonly 之後,如果客戶端發生異常,則資料庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處於不可寫狀態,風險較高。
三丶鎖解決併發事務帶來的問題
1.鎖解決髒寫
多個未提交事務修改相繼對同一條記錄進行改動的時候,需要進行排隊,排隊過程其實就是通過為記錄加鎖實現的。當一個事務想修改記錄的時候,首先需要看下有沒有於記錄相關聯的鎖結構,如果沒有,那麼會在記憶體中生成與之對應的鎖結構
可能多個事務同時修改同一記錄,會產生多個鎖結構,其中只有一個事務可以獲取到鎖,其is_waiting為false,其他事務is_waiting為true,當獲取鎖的事務結束後,會喚醒其他等待的事務
2.髒讀,幻讀,不可重複讀如何避免
mysql innodb 在repeatable隔離級別下很大程度下避免了幻讀(後續詳細說到)
2.1 mvcc解決髒讀,幻讀,不可重複讀問題
Mysql InnoDB多版本併發控制MVCC 這篇部落格我們詳細說到了mvcc的實現的原理,簡單來說就是在事務執行時會生成read view,其包含四個部分
- m_ids:在生成read view時,當前系統中活躍的讀寫事務id列表
- min_trx_id:生成read view時,當前系統中活躍的讀寫事務中最小事務id,也就是m_ids中的最小值
- max_trx_id:生成read view時,系統應該分配給下一個事務的事務id值
- creator_trx_id:生成該read view的事務的事務id
查詢語句只可以查詢生成read view 時刻可以看到的資料,及事務id小於min_trx_id中的資料,寫操作則針對最新的資料。非當前讀的普通語句在讀資料的時候,生成read view的時機不同,在可重複讀的時候只會在第一次讀取(如果使用start transaction with consistent snapshot
則是在事務啟動時)生成一個read view 後續不變,達到可重複讀的目的,對於讀已提交,則是每次讀取都會生成新的read view,從重新整理未提交事務集合,和min_trx_id,讀取到已經提交的資料。使用mvcc讓讀寫並不衝突,資料庫的併發能力更強。
2.2讀寫均加鎖
比如在銀行存款業務中,我們需要先讀取賬戶餘額,然後加上新增存款,然後進行寫回操作,整個流程中,我們不希望存在另外一個ATM進行存取的操作,讀寫都需要排隊,這時候就得使用鎖。
上面我們說了,寫操作進行排隊可以解決髒寫,那麼髒讀,不可重複讀,幻讀是怎麼使用讀寫加鎖解決暱?
髒讀產生的原因是,當前事務讀取到了另外一個事務沒有提交的資料,那麼只需要另外一個事務對操作的記錄加鎖,當前事務無法獲取到鎖,自然就不會發生髒讀。
不可重複讀產生的原因是,當前事務先讀取了一條記錄,然後存在另外一個事務修改了此記錄的資料,那麼只要當前事務對記錄進行加鎖,自然後續的事務將無法修改,自然不會發生不可重複讀。
幻讀產生的原因是,當前事務根據條件查詢得到一批資料,然後後續事務新增了滿足這些條件的資料,導致再次查詢時發現多了一些資料,如同出現了幻覺。這裡加鎖則不是單對記錄加鎖,而是要鎖住一個範圍,讓其他事務無法插入資料,從而解決幻讀(後續會說到這種鎖)
很明顯讀寫都加鎖,併發能力不及mvcc
3.一致性讀
事務利用MVCC進行讀取操作稱為一致性讀(又稱一致性無鎖讀,快照讀)
,基本上所有的普通讀在可重複讀,讀提交隔離級別下,都是一致性讀。
4.鎖定讀
4.1共享鎖&獨佔鎖
共享鎖:簡稱S鎖,事務要讀取一條記錄的時候需要先獲取到記錄的共享鎖
獨佔鎖:簡稱x鎖,當事務需要改動記錄的時候,需要先獲取記錄的獨佔鎖
共享鎖和共享鎖相容,獨佔鎖和獨佔鎖,獨佔鎖和共享鎖互斥
4.2鎖定讀的語句
select xxx lock in share mode
可以對讀取的記錄加S鎖
select xxx for update
可以對讀取的記錄加X鎖
4.3 寫操作
-
delete
刪除記錄首先要在b+數中定位到這條記錄的位置,然後獲取x鎖,然後指向delete mark(標記記錄被刪除)
-
update
如果未修改鍵值,並且修改前後資料的儲存空間大小不變,那麼現在b+樹上定位記錄,然後加x鎖。反之需要在b+樹上定位資料,然後把記錄徹底刪除嗎,然後再插入一條新的記錄,對新增的這條資料加x鎖
-
insert
新插入記錄一般都是加隱式鎖(後面說)不需要在記憶體中生成對應的鎖結構。
三丶InnoDB表鎖
1.表級S,X鎖
innodb支援表級鎖,也支援行級鎖,表級鎖粒度相對更粗,佔用資源較少。使用表級鎖效果相當於為表中的所有記錄加鎖,所以效能比較差。
-
表級S鎖,X鎖
使用
Lock Tables t Read
,innodb儲存引擎會對錶t加共享鎖使用
Lock tables t write
,innodb儲存引擎會對錶t加獨佔鎖
類似於Java中的讀寫鎖,共享鎖和共享鎖不互斥,獨佔鎖和獨佔鎖,獨佔鎖和共享鎖互斥。
2.表級意向鎖
innodb儲存引擎中,當對錶中某些記錄加S鎖之前,會在表上加上一個IS鎖,同樣加X鎖之前會加表級IX鎖,這裡的I表示意向鎖,SX表示共享還是互斥,表級意向鎖存在的目的是後續對錶加S鎖,X鎖的時候,可以快速判斷表中是否存在加鎖的記錄,避免遍歷每一個記錄檢視是否被加鎖。
3.表級AUTO-INC鎖
mysql可以為某列執行Auto Increment自增,系統給自增列賦值的實現方式主要存在兩種
- 使用AUTO-INC鎖,執行插入語句的時候加一個表級AUTO-INC鎖,然後為每條待插入的記錄中的自增列,進行遞增賦值,
單個插入語句執行結束後釋放AUTO-INC鎖
。這樣會導致其他事務的插入被阻塞。 - 採用輕量級AUTO-INC鎖,在為插入列賦值結束後,就釋放輕量級鎖,而不是插入語句執行完後才釋放
四.MDL
一般情況下 對某一個表執行增刪改查的時候,都不會加表鎖,但是執行一些DDL修改表結構,刪除表時,其他事務對這表的增刪改查發生阻塞。這是由MDL鎖實現的,MDL鎖也分為讀鎖和寫鎖,在進行crud操作的時候,會加MDL讀鎖,進行DDL的時候會加MDL寫鎖。
我們需要注意MDL讀鎖寫鎖是互斥的
如圖四個不同的session先後依次執行語句,其中A,B都是獲取MDL讀鎖,互不阻塞,隨即C獲取MDL寫鎖,這時候C會被阻塞,這一阻塞不打緊,還會阻塞後續獲取MDL讀鎖的事務,造成整個表不可用。這啟發了我們,在做DDL的時候要解決長事務,事務不提交,就會一直佔著MDL鎖。在 MySQL 的 information_schema 庫的innodb_trx 表中,可以查到當前執行中的事務。如果要做 DDL 變更的表剛好有長事務在執行,要考慮先暫停 DDL,或者 kill 掉這個長事務。
五丶innodb 行鎖
1.Record Lock
官方名稱Lock_REC_NOT_GAP
記錄鎖有S鎖和X鎖,S型記錄鎖之間可以共享,X型記錄鎖和S型記錄鎖,X型記錄鎖互斥
2.GAP Lock
innodb的可重複讀級別,使用詞鎖解決幻讀問題,前面我們說過,其難點在於,加鎖的時候幻影記錄還未出現。官方使用Lock_GAP
實現如下操作
此處的gap鎖可以反之其他事務在number為8記錄前面的間隙插入新的記錄,在區間(3,8)內無法進行插入操作,當另外一個事務要插入number為4的記錄時,首先需要定位到該條記錄的下一條記錄,也就是number為8的記錄,此時number為8的記錄具備gap鎖,所以將阻塞插入操作,直到gap鎖被釋放,其他事務才能進行插入。gap鎖出現的目的,就是為了防止插入幻影記錄,如果對記錄上gap鎖,並不會限制其他事務對記錄加記錄鎖
。
innodb有兩個虛擬的記錄Infimum(虛擬最小),Supermun(虛擬最大)
當我們想在(xx,正無窮)範圍鎖住幻影記錄時就可以對Supermun加gap鎖。
3.Next-Key Lock
Next-Key Lock = 記錄鎖 + gap鎖,既鎖住記錄,也鎖住記錄之前的間隙
4.Insert Intention Lock
插入意向鎖,表示事務想在某個間隙插入新的記錄,但是當前處於等待狀態。
比如事務A持有(4,8)範圍內的gap鎖,事務B和C,想插入(4,8)範圍內的記錄,就會在記憶體中生成事務B,C對應的插入意向鎖,當前事務A釋放gap鎖的時候,將喚醒事務B和C,事務B和C可以同時獲取插入意向鎖,然後進行插入。插入意向鎖並不會阻止對記錄繼續上鎖。
5.隱式鎖
為事務生成記憶體中的鎖結構並不是一個0成本的事情,為了節省這個成本,提出隱式鎖
的概念。
當一個事務插入語一條記錄A,其他事務
-
select xxx Lock in share mode
讀取記錄A(獲取記錄A的S鎖),或者使用select xxx for update
(獲取記錄A的X鎖) - 立即修改記錄A(獲取x鎖)
對於聚簇索引來說,有一個隱藏列trx_id
此列儲存著最後更改記錄的事務id,在當前事務A插入記錄後,便是儲存著當前事務A的id,其他事務B企圖獲取x鎖,s鎖的時候,就需要下先看一下,trx_id隱藏列對應的事務是否存活,如果不是那麼正常獲取,反之需要為當前事務A建立一個x鎖記憶體結構,並標記is_waiting為false,然後事務B將為自己建立一個鎖結構,is_waiting 為true然後事務B進入等待狀態
對於二級索引來說,其不具備隱藏列trx_id
但是在二級索引頁面的page header中的page_maxt_trx_id
屬性,記錄了改動頁面最大的事務id,如果其屬性值小於當前最小的活躍事務id,那麼說明對頁面的改動事務已經提交,否則需要定位到二級索引記錄,然後回表對聚簇索引進行上述聚簇索引的操作。
一個事務對新插入的記錄不需要顯示的加鎖,由於事務id的存在相當於加了一個隱式鎖,別的事務需要加S鎖或者X鎖的時候,先幫之前的事務生成鎖結構,然後為自己生成鎖結構,再進入阻塞狀態。隱式鎖起到了延遲加鎖的作用,也許別的事務不會獲取於隱式鎖衝突的鎖,這時候可以減少記憶體中生成鎖結構。