1、MySQL中的鎖與鎖策略
在MySQL中,爲了應對併發場景下的讀寫,鎖一般分爲兩類:共享鎖以及排他鎖。其中,共享鎖容許多個鏈接在同一時間併發的讀取相同的資源,彼此之間互不影響,因此又稱爲讀鎖。排他鎖則會阻塞其餘嘗試獲取共享鎖或者排他鎖的操做,確保同一時間只有一個鏈接能夠寫入數據,並禁止其餘用戶的讀寫,又稱寫鎖。
在實際使用下,加鎖每每意味着高昂的開銷,MySQL爲了平衡鎖的開銷以及併發的線程之間的安全,採用了兩種不一樣的鎖策略:
表鎖會鎖定整張表,若是當前有用戶正在執行寫操做而且獲取了寫鎖,這可能致使整張表被鎖定,阻塞其餘用戶的讀寫操做。若是用戶執行的是讀操做,則會獲取讀鎖,此時其餘用戶的併發讀操做將被接受,寫操做會被阻塞。
若是b字段不存在索引,那麼會鎖住全部的記錄,即鎖上了表鎖。
行鎖的粒度是在每一條行數據,這意味行鎖能夠儘量的支持併發處理,相應的行鎖開銷也會比較大。而且,在InnoDB中的行鎖是針對索引加的鎖,不是針對記錄加的鎖,而且該索引不能失效,不然行鎖將會自動升級爲表鎖。
相比較而言,表鎖的優點在於開銷小,加鎖快,無死鎖,劣勢是鎖的粒度大,發生鎖衝突的機率較高,併發能力較弱。而行鎖則相反。實際使用中,二者都會由MySQL自動加鎖。行鎖衝突能夠經過執行 show status like 'innodb_row_lock%'語句進行分析,表鎖衝突則可經過執行show status like 'table_locks%' 進行查看。
2、MySQL中的事務與隔離級別
事務就是一組原子性的sql,要麼MySQL引擎會所有執行這一組sql語句,要麼所有不行(不容許任何一條失敗)。失敗的語句將致使事務的整個回滾。事務系統一般知足四個特性,分別爲原子性(要麼所有執行、要麼所有回滾)、一致性(數據必須從一個一致性狀態轉換爲另外一種一致性狀態)、隔離性(事務未執行成功,其餘人沒法看到結果)、持久性(事務在commit以後,數據不會丟失)。
由上述概念可知,事務是用來保障數據的一致性以及完整性的。也是MySQL中用來平衡效率與安全之間的一種手段,因此,InnoDB引擎下的事務一般提供了四種事務的隔離級別,方便用戶本身在效率和安全之間作出權衡。
事務中的修改,即便該事務未提交,對其餘的事務也是可見的。能夠讀取到其餘事務中的數據,又稱爲髒讀,在實際數據庫事務中,髒讀會破壞數據的一致性,對業務產生極大影響,因此通常不推薦採用READ UNCOMMITED做爲數據庫事務的隔離級別。
在提交讀級別中,數據庫將保證若是一個事務沒有徹底執行成功(commit完成),事務中的操做對其餘的事務是不可見的。在該隔離級別下,雖然杜絕了髒讀的發生,可是仍是存在着不可重複讀以及幻讀的問題。不可重複讀發生在事務T1讀取了一行數據,事務T2接着修改或者刪除了該行數據(已提交),當T1事務再次讀取同一行數據的時候,發現數據已經被修改或者被刪除。示例以下圖:
幻讀則發生在事務T1讀取了知足某條件的一個數據集,事務T2此時插入了一行或者多行知足T1查詢條件的的數據並提交,當T1再次採用相同的條件進行讀取時,獲得了與第一次不一樣的結果集。示例以下:
REPEATEABLE READ是MySQL的默認隔離級別,它確保同一個事務的多個實例在併發讀取數據時,會看到一樣的數據。按照該隔離級別定義,仍是會存在幻讀的問題。MySQL經過InnoDB存儲引擎的多版本併發控制機制(MVCC)解決了部分該問題的場景,但仍然存在,此處後文會另有分析。
串行化是最嚴格的隔離級別,經過給事務中的每次讀寫操做都加鎖,保證了不產生任何
髒讀、不可重複讀以及幻讀問題,可是隨之引入的是大量的讀超時以及鎖競爭,致使數據庫性能的嚴重降低。
另外來看看ANSI SQL STANDARD中,對於數據庫隔離級別以及相應問題的規定:
因此,對於REPEATABLE READ隔離級別下,是容許出現幻讀的。
3、MySQL中的MVCC
MVCC(multiple-version-concurrency-control)是個行級鎖的變種,它在普通讀狀況下避免了加鎖操做,所以開銷更低。其原理具體爲,在InnoDB存儲引擎中,每行數據會加入一些隱藏字段DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE_BIT。DATA_TRX_ID 字段記錄了數據的建立和刪除時間,這個時間指的是對數據進行操做的事務的id,DATA_ROLL_PTR 指向當前數據的undo log記錄,回滾數據就是經過這個指針,DELETE BIT位用於標識該記錄是否被刪除,這裏的不是真正的刪除數據,而是標誌出來的刪除。真正意義的刪除是在mysql進行數據的GC,清理歷史版本數據的時候。
SELECT語句先查找DATA_TRX_ID早於當前事務ID的數據行。這樣就保證了讀取的數據要麼是在這個事務開始以前就已經commit了的(早於當前事務ID),要麼是在這個事務中自身建立的數據(等於當前事務ID)。查找行的DELETE_BIT爲1時,查找刪除事務ID對應的事務,肯定此條記錄在當前事務開始以前,行沒有被刪除。
INSERT語句會在新插入行數據以後,保存當前事務ID做爲行的DATA_TRX_ID。
DELETE語句爲每一條刪除的記錄保存當前的事務ID做爲行的刪除標記。
UPDATE語句將複製變動的記錄,並把新記錄的DATA_TRX_ID置爲當前事務ID,同時更新老記錄中的DB_ROLL_PT指向了上一個版本。
因此在併發讀的時候,不須要等到訪問行上的鎖釋放,只須要讀取一個行的快照便可。既然是多版本的讀取,就確定讀取不到其餘事務中的新插入的數據了,也就避免了上述場景中提到的幻讀。
4、幻讀
從上述信息咱們已經知道,在REPEATABLE READ級別下,InnoDB採起多版本策略成功
避免了部分幻讀現象,可是實際使用中,仍是會有幻讀產生,先看場景:
經過MVCC,在事務中的屢次讀取不會出現幻讀,可是此時的插入操做依舊會發生主鍵重複的錯誤,而且由於MVCC機制,在上圖中的會話1不管讀取多少次都不會讀到致使衝突產生的數據,確實就如「幻影」通常詭異。
爲了解決上述場景中的幻讀,須要簡單提一下InnoDB的行鎖機制,在InnoDB引擎下存在三種行鎖,分別爲:
一般狀況下,INSERT/UPDATE/DELETE默認會在操做的記錄上加上Next-Key Lock,而
普通的SELECT由於MVCC的關係反而只須要讀取快照便可,因此若是業務須要再REPEATABLE READ場景下保證絕對不產生幻讀,須要手動給SELECT加鎖,在相似SELECT…WHERE加入FOR UPDATE(排它鎖)或者LOCK IN SHARE MODE(共享鎖)
5、總結
-
InnoDB中使用索引做爲檢索條件修改數據時採用行鎖,不然使用表鎖
-
InnoDB自動給修改操做加鎖,給查詢操做不自動加鎖
-
在REPEATABLE READ級別下,若是要徹底杜絕幻讀,須要手動給關鍵查詢語句加鎖
-
表的大部分數據須要修改時,行鎖反而不如表鎖更有效率
最後,本文只是就網易雲信業務中數據庫的一些使用場景和問題做了總結,數據庫的實
際使用還存在諸多學問,但願能拋磚引玉,讓更多人分享出本身的心得。