MySQL 就是其中之一,它經歷了多個版本迭代。數據庫鎖是 MySQL 數據引擎的一部分,今天咱們就一塊兒來學習 MySQL 的數據庫鎖和它的優化。數據庫
MySQL 鎖分類併發
當多個事務或者進程訪問同一個資源的時候,爲了保證數據的一致性,就須要用到鎖機制。異步
從鎖定資源的角度來看,MySQL 中的鎖分爲:高併發
表級鎖:對整張表加鎖。開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。性能
行級鎖:對某行記錄加鎖。開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。學習
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。優化
在實際開發過程當中,主要會使用到表級鎖和行級鎖兩種。既然鎖是針對資源的,那麼這些資源就是數據,在 MySQL 提供插件式存儲引擎對數據進行存儲。插件
插件式存儲引擎的好處是,開發人員能夠根據須要選擇適合的存儲引擎。設計
在衆多的存儲引擎中,有兩種引擎被比較多的使用,他們分別是:3d
其特色是行鎖設計、支持外鍵,並支持相似於 Oracle 的非鎖定讀,即默認讀取操做不會產生鎖。
簡單來講,就是對數據的插入,更新操做比較多。從 MySQL 數據庫 5.5.8 版本開始,InnoDB 存儲引擎是默認的存儲引擎。
上面兩種存儲引擎在處理多進程數據操做的時候是如何表現的,就是咱們接下來要討論的問題。
爲了讓整個描述更加清晰,咱們將表級鎖和行級鎖以及 MyISAM,InnoDB 存儲引擎,就造成了一個 2*2 的象限。
2*2 錶行鎖,MyISAM,InnoDB 示意圖
因爲 MyISAM 存儲引擎不支持行級鎖,實際上後面討論的問題會圍繞三個象限的討論展開。
從內容上來看,InnoDB 做爲使用最多的存儲引擎遇到的問題和值得注意的地方較多,也是本文的重點。
MyISAM 存儲引擎和表級鎖
首先,來看第一象限的內容:
2*2 錶行鎖,MyISAM,InnoDB 示意圖-第一象限
MyISAM 存儲引擎支持表級鎖,而且支持兩種鎖模式:
MyISAM 優化建議
在使用 MyISAM 存儲引擎時。執行 SQL 語句,會自動爲 SELECT 語句加上共享鎖,爲 UDI(更新,刪除,插入)操做加上排他鎖。
因爲這個特性在多進程併發插入同一張表的時候,就會由於排他鎖而進行等待。
所以能夠經過配置 concurrent_insert 系統變量,來控制其併發的插入行爲。
①concurrent_insert=0 時,不容許併發插入。
②concurrent_insert=1 時,若是 MyISAM 表中沒有空洞(即表中沒有被刪除的行),容許一個進程讀表時,另外一個進程向表的尾部插入記錄(MySQL 默認設置)。
注:空洞是行記錄被刪除之後,只是被標記爲「已刪除」其存儲空間沒有被回收,也就是說沒有被物理刪除。由另一個進程,異步對這個數據進行刪除。
由於空間長度問題,刪除之後的物理空間不能被新的記錄所使用,從而造成了空洞。
③concurrent_insert=2 時,不管 MyISAM 表中有沒有空洞,都容許在表尾併發插入記錄。
若是在數據插入的時候,沒有併發刪除操做的話,能夠嘗試把 concurrent_insert 設置爲 1。
反之,在數據插入的時候有刪除操做且量較大時,也就是會產生「空洞」的時候,就須要把 concurrent_insert 設置爲 2。
另外,當一個進程請求某個 MyISAM 表的讀鎖,另外一個進程也請求同一表的寫鎖。
即便讀請求先到達,寫請求後到達,寫請求也會插到讀請求以前。由於 MySQL 的默認設置認爲,寫請求比讀請求重要。
咱們能夠經過 low_priority_updates 來調節讀寫行爲的優先級:
InnoDB 存儲引擎和表級鎖
再來看看第二象限的內容:
2*2 錶行鎖,MyISAM,InnoDB 示意圖-第二象限
InnoDB 存儲引擎表鎖。當沒有對數據表中的索引數據進行查詢時,會執行表鎖操做。
上面是 InnoDB 實現行鎖,同時它也能夠實現表鎖。其方式就是意向鎖(Intention Locks)。
這裏介紹兩種意向鎖:
注:意向共享鎖和意向排他鎖是數據庫主動加的,不須要咱們手動處理。對於 UPDATE、DELETE 和 INSERT 語句,InnoDB 會自動給數據集加排他鎖。
InnoDB表鎖的實現方式:假設有一個表 test2,有兩個字段分別是 id 和 name。
沒有設置主鍵同時也沒有設置任何索引(index)以下:
InnoDB 表鎖實現方式圖
InnoDB 存儲引擎和行級鎖
第四象限咱們使用的比較多,討論的內容也相對多些:
2*2 錶行鎖,MyISAM,InnoDB 示意圖-第四象限
InnoDB 存儲引擎行鎖,當數據查詢時針對索引數據進行時,會使用行級鎖。
共享鎖(S):當一個事務讀取一條記錄的時候,不會阻塞其餘事務對同一記錄的讀請求,但會阻塞對其的寫請求。當讀鎖釋放後,纔會執行其餘事務的寫操做。
例如:select … lock in share mode
排他鎖(X):當一個事務對一條記錄進行寫操做時,會阻塞其餘事務對同一表的讀寫操做,當該鎖釋放後,纔會執行其餘事務的讀寫操做。
例如:select … for update
行鎖的實現方式:假設有一個表 test1,有兩個字段分別是 id 和 name。
id 做爲主鍵同時也是 table 的索引(index)以下:
InnoDB 行鎖實現方式圖
在高併發的狀況下,多個事務同時請求更新數據,因爲資源被佔用等待事務增多。
如此,會形成性能問題,能夠經過 innodb_lock_wait_timeout 來解決。innodb_lock_wait_timeout 是事務等待獲取資源的最長時間,單位爲秒。若是超過期間還未分配到資源,則會返回應用失敗。
四種鎖的兼容狀況:
共享鎖,排他鎖,意向共享鎖,意向排他鎖兼容圖例
若是一個事務請求的鎖模式與當前的鎖兼容, InnoDB 就將請求的鎖授予該事務;反之, 若是二者不兼容,該事務就要等待鎖釋放。
間隙鎖
前面談到行鎖是針對一條記錄進行加鎖。當對一個範圍內的記錄加鎖的時候,咱們稱之爲間隙鎖。
當使用範圍條件索引數據時,InnoDB 會對符合條件的數據索引項加鎖。對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB 也會對這個「間隙」加鎖,這就是間隙鎖。間隙鎖和行鎖合稱(Next-Key鎖)。
若是表中只有 11 條記錄,其 id 的值分別是 1,2,...,10,11 下面的 SQL:
Select * from table_gapwhere id > 10 for update;
這是一個範圍條件的檢索,InnoDB 不只會對符合條件的 id 值爲 10 的記錄加鎖,會對 id 大於 10 的「間隙」加鎖,即便大於 10 的記錄不存在,例如 12,13。
InnoDB 使用間隙鎖的目的:
間隙鎖圖
死鎖
兩個事務都須要得到對方持有的排他鎖才能繼續完成任務,這種互相等待對方釋放資源的狀況就是死鎖。
死鎖圖
檢測死鎖:InnoDB 存儲引擎能檢測到死鎖的循環依賴並當即返回一個錯誤。
死鎖恢復:死鎖發生之後,只有部分或徹底回滾其中一個事務,才能打破死鎖。
InnoDB 方法是,將持有最少行級排他鎖的事務回滾。在應用程序設計時必須考慮處理死鎖,多數狀況下從新執行因死鎖回滾的事務便可。
避免死鎖:
這樣作會致使,當申請排他鎖時,其餘事務可能已經得到了相同記錄的共享鎖,從而形成鎖衝突,甚至死鎖。
簡單來講,若是你要更新記錄要作兩步操做,第一步查詢,第二步更新。就不要第一步上共享鎖,第二部上排他鎖了,直接在第一步就上排他鎖,搶佔先機。
MySQL 鎖定狀況的查詢
在實際開發中沒法避免數據被鎖的問題,那麼咱們能夠經過哪些手段來查詢鎖呢?
表級鎖能夠經過兩個變量的查詢:
行級鎖能夠經過下面幾個變量查詢:
MySQL 事務隔離級別
前面講的死鎖是由於併發訪問數據庫形成。當多個事務同時訪問數據庫,作併發操做的時候會發生如下問題。
髒讀(dirty read),一個事務在處理過程當中,讀取了另一個事務未提交的數據。未提交的數據稱之爲髒數據。
髒讀例子
不可重複讀(non-repeatable read),在事務範圍內,屢次查詢某條記錄,每次獲得不一樣的結果。
第一個事務中的兩次讀取數據之間,因爲第二個事務的修改,第一個事務兩次讀到的數據可能不同。
不可重複讀例子
幻讀(phantom read),是事務非獨立執行時發生的一種現象。
幻讀的例子
在同一時間點,數據庫容許多個併發事務,同時對數據進行讀寫操做,會形成數據不一致性。
四種隔離級別,解決事務併發問題對照圖
隔離性就是用來防止這種數據不一致的。事務隔離根據級別不一樣,從低到高包括:
當出現讀寫鎖衝突的時候,後訪問的事務必須等前一個事務執行完成,因此事務執行是串行的。可避免髒讀、不可重複讀、幻讀。
InnoDB 優化建議
從鎖機制的實現方面來講,InnoDB 的行級鎖帶來的性能損耗可能比表級鎖要高一點,但在併發方面的處理能力遠遠優於 MyISAM 的表級鎖。這也是大多數公司的 MySQL 都是使用 InnoDB 模式的緣由。
可是,InnoDB 也有脆弱的一面,下面提出幾個優化建議供你們參考:
總結
MySQL 數據庫鎖的思惟導圖
MySQL 的鎖主要分爲表級鎖和行級鎖。MyISAM 引擎使用的是表級鎖,針對表級的共享鎖和排他鎖,能夠經過 concurrent_insert 和 low_priority_updates 參數來優化。
InnoDB 支持表鎖和行鎖,根據索引來判斷如何選擇。行鎖有,行共享鎖和行排他鎖;表鎖有,意向共享鎖,意向排他鎖,表鎖是系統本身加上的;鎖範圍的是間隙鎖。遇到死鎖,咱們如何檢測,恢復以及如何避免。
MySQL 有四個事務級別分別是,讀未提交,讀提交,可重複讀,串行化。他們的隔離級別依次升高。
經過隔離級別的設置,能夠避免,髒讀,不可重複讀和幻讀的狀況。最後,對於使用比較多的 InnoDB 引擎,提出了一些優化建議。
轉自
大牛總結的MySQL鎖優化,寫得太好了 https://www.toutiao.com/a6748659273795895819/