淺談MySQL數據庫中的鎖與事務

  1. MySQL中的鎖與鎖策略

在MySQL中,爲了應對併發場景下的讀寫,鎖通常分爲兩類:共享鎖以及排他鎖。其中,共享鎖允許多個連接在同一時間併發的讀取相同的資源,彼此之間互不影響,所以又稱爲讀鎖。排他鎖則會阻塞其他嘗試獲取共享鎖或者排他鎖的操作,確保同一時間只有一個連接可以寫入數據,並禁止其他用戶的讀寫,又稱寫鎖。

在實際使用下,加鎖往往意味着高昂的開銷,MySQL爲了平衡鎖的開銷以及併發的線程之間的安全,採用了兩種不同的鎖策略:

 

  1. table lock(表鎖)

表鎖會鎖定整張表,如果當前有用戶正在執行寫操作並且獲取了寫鎖,這可能導致整張表被鎖定,阻塞其他用戶的讀寫操作。如果用戶執行的是讀操作,則會獲取讀鎖,此時其他用戶的併發讀操作將被接受,寫操作會被阻塞。

舉個例子,執行語句

:

如果b字段不存在索引,那麼會鎖住所有的記錄,即鎖上了表鎖。

 

  1. row lock(行鎖)

行鎖的粒度是在每一條行數據,這意味行鎖可以儘可能的支持併發處理,相應的行鎖

開銷也會比較大。並且,在InnoDB中的行鎖是針對索引加的鎖,不是針對記錄加的鎖,並且該索引不能失效,否則行鎖將會自動升級爲表鎖

 

       相比較而言,表鎖的優勢在於開銷小,加鎖快,無死鎖,劣勢是鎖的粒度大,發生鎖衝突的概率較高,併發能力較弱。而行鎖則相反。實際使用中,兩者都會由MySQL自動加鎖。行鎖衝突可以通過執行 show status like 'innodb_row_lock%'語句進行分析,表鎖衝突則可通過執行show status like 'table_locks%' 進行查看。

 

 

 

  1. MySQL中的事務與隔離級別

事務就是一組原子性的sql,要麼MySQL引擎會全部執行這一組sql語句,要麼全部不

行(不允許任何一條失敗)。失敗的語句將導致事務的整個回滾。事務系統通常滿足四個特性,分別爲原子性(要麼全部執行、要麼全部回滾)、一致性(數據必須從一個一致性狀態轉換爲另一種一致性狀態)、隔離性(事務未執行成功,其他人無法看到結果)、持久性(事務在commit之後,數據不會丟失)。

       由上述概念可知,事務是用來保障數據的一致性以及完整性的。也是MySQL中用來平衡效率與安全之間的一種手段,所以,InnoDB引擎下的事務通常提供了四種事務的隔離級別,方便用戶自己在效率和安全之間做出權衡。

 

  1. READ UNCOMMITED(未提交讀)

事務中的修改,即使該事務未提交,對其他的事務也是可見的。可以讀取到其他事務

中的數據,又稱爲髒讀,在實際數據庫事務中,髒讀會破壞數據的一致性,對業務產生極大影響,所以一般不推薦採用READ UNCOMMITED作爲數據庫事務的隔離級別。

  1. READ COMMITED(提交讀)

在提交讀級別中,數據庫將保證如果一個事務沒有完全執行成功(commit完成),事務中的操作對其他的事務是不可見的。在該隔離級別下,雖然杜絕了髒讀的發生,但是還是存在着不可重複讀以及幻讀的問題。不可重複讀發生在事務T1讀取了一行數據,事務T2接着修改或者刪除了該行數據(已提交),當T1事務再次讀取同一行數據的時候,發現數據已經被修改或者被刪除。示例如下圖:

幻讀則發生在事務T1讀取了滿足某條件的一個數據集,事務T2此時插入了一行或者多行滿足T1查詢條件的的數據並提交,當T1再次採用相同的條件進行讀取時,得到了與第一次不同的結果集。示例如下:

  1. REPEATABLE READ(提交讀)

REPEATEABLE READ是MySQL的默認隔離級別,它確保同一個事務的多個實例在並

發讀取數據時,會看到同樣的數據。按照該隔離級別定義,還是會存在幻讀的問題。MySQL通過InnoDB存儲引擎的多版本併發控制機制(MVCC)解決了部分該問題的場景,但仍然存在,此處後文會另有分析。

 

  1. SERIALIZABLE(串行化)

串行化是最嚴格的隔離級別,通過給事務中的每次讀寫操作都加鎖,保證了不產生任何

髒讀、不可重複讀以及幻讀問題,但是隨之引入的是大量的讀超時以及鎖競爭,導致數據庫性能的嚴重下降。

 

另外來看看ANSI SQL STANDARD中,對於數據庫隔離級別以及相應問題的規定:

所以,對於REPEATABLE READ隔離級別下,是允許出現幻讀的。

 

 

  1. 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,清理歷史版本數據的時候。

       相應的,其DML的處理方式也發生了變化:

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指向了上一個版本。

所以在併發讀的時候,不需要等到訪問行上的鎖釋放,只需要讀取一個行的快照即可。既然是多版本的讀取,就肯定讀取不到其他事務中的新插入的數據了,也就避免了上述場景中提到的幻讀。

  1. 幻讀

從上述信息我們已經知道,在REPEATABLE READ級別下,InnoDB採取多版本策略成功

避免了部分幻讀現象,但是實際使用中,還是會有幻讀產生,先看場景:

通過MVCC,在事務中的多次讀取不會出現幻讀,但是此時的插入操作依舊會發生主鍵重複的錯誤,並且因爲MVCC機制,在上圖中的會話1無論讀取多少次都不會讀到導致衝突產生的數據,確實就如“幻影”一般詭異。

 

 

爲了解決上述場景中的幻讀,需要簡單提一下InnoDB的行鎖機制,在InnoDB引擎下存在三種行鎖,分別爲:

    1. Record Lock:在單行記錄上的鎖
    2. Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身,。GAP鎖的目的,是爲了防止同一事務的兩次讀出現幻讀的情況
    3. Next-Key Lock: 前兩個鎖的共同使用,即鎖定了記錄本身,也鎖定了一定的範圍。

 

通常情況下,INSERT/UPDATE/DELETE默認會在操作的記錄上加上Next-Key Lock,而

普通的SELECT因爲MVCC的關係反而只需要讀取快照即可,所以如果業務需要再REPEATABLE READ場景下保證絕對不產生幻讀,需要手動給SELECT加鎖,在類似SELECT…WHERE加入FOR UPDATE(排它鎖)或者LOCK IN SHARE MODE(共享鎖)。

 

  1. 總結
  1. InnoDB中使用索引作爲檢索條件修改數據時採用行鎖,否則使用表鎖
  2. InnoDB自動給修改操作加鎖,給查詢操作不自動加鎖
  3. 在REPEATABLE READ級別下,如果要完全杜絕幻讀,需要手動給關鍵查詢語句加鎖
  4. 表的大部分數據需要修改時,行鎖反而不如表鎖更有效率

最後,本文只是就網易雲信業務中數據庫的一些使用場景和問題作了總結,數據庫的實

際使用還存在諸多學問,希望能拋磚引玉,讓更多人分享出自己的心得。

邀請好友使用網易雲信,好友下單成功即可獲得500元網易考拉/嚴選無門檻現金券,點擊立即推薦>>


網易雲信(NeteaseYunXin)是集網易19IM以及音視頻技術打造的PaaS服務產品,來自網易核心技術架構的通信與視頻雲服務,穩定易用且功能全面,致力於提供全球領先的技術能力和場景化解決方案。開發者通過集成客戶端SDK和雲端OPEN API,即可快速實現包含IM、音視頻通話、直播、點播、互動白板、短信等功能。

 

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