浅谈MySQL数据库中的锁与事务
在MySQL中,為了應對并發場景下的讀寫,鎖通常分為兩類:共享鎖以及排他鎖。其中,共享鎖允許多個連接在同一時間并發的讀取相同的資源,彼此之間互不影響,所以又稱為讀鎖。排他鎖則會阻塞其他嘗試獲取共享鎖或者排他鎖的操作,確保同一時間只有一個連接可以寫入數據,并禁止其他用戶的讀寫,又稱寫鎖。
在實際使用下,加鎖往往意味著高昂的開銷,MySQL為了平衡鎖的開銷以及并發的線程之間的安全,采用了兩種不同的鎖策略:
?
表鎖會鎖定整張表,如果當前有用戶正在執行寫操作并且獲取了寫鎖,這可能導致整張表被鎖定,阻塞其他用戶的讀寫操作。如果用戶執行的是讀操作,則會獲取讀鎖,此時其他用戶的并發讀操作將被接受,寫操作會被阻塞。
舉個例子,執行語句
:
如果b字段不存在索引,那么會鎖住所有的記錄,即鎖上了表鎖。
?
行鎖的粒度是在每一條行數據,這意味行鎖可以盡可能的支持并發處理,相應的行鎖
開銷也會比較大。并且,在InnoDB中的行鎖是針對索引加的鎖,不是針對記錄加的鎖,并且該索引不能失效,否則行鎖將會自動升級為表鎖。
?
?????? 相比較而言,表鎖的優勢在于開銷小,加鎖快,無死鎖,劣勢是鎖的粒度大,發生鎖沖突的概率較高,并發能力較弱。而行鎖則相反。實際使用中,兩者都會由MySQL自動加鎖。行鎖沖突可以通過執行 show status like 'innodb_row_lock%'語句進行分析,表鎖沖突則可通過執行show status like 'table_locks%' 進行查看。
?
?
?
事務就是一組原子性的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隔離級別下,是允許出現幻讀的。
?
?
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指向了上一個版本。
所以在并發讀的時候,不需要等到訪問行上的鎖釋放,只需要讀取一個行的快照即可。既然是多版本的讀取,就肯定讀取不到其他事務中的新插入的數據了,也就避免了上述場景中提到的幻讀。
從上述信息我們已經知道,在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(共享鎖)。
?
最后,本文只是就網易云信業務中數據庫的一些使用場景和問題作了總結,數據庫的實
際使用還存在諸多學問,希望能拋磚引玉,讓更多人分享出自己的心得。
邀請好友使用網易云信,好友下單成功即可獲得500元網易考拉/嚴選無門檻現金券,點擊立即推薦>>
網易云信(NeteaseYunXin)是集網易19年IM以及音視頻技術打造的PaaS服務產品,來自網易核心技術架構的通信與視頻云服務,穩定易用且功能全面,致力于提供全球領先的技術能力和場景化解決方案。開發者通過集成客戶端SDK和云端OPEN API,即可快速實現包含IM、音視頻通話、直播、點播、互動白板、短信等功能。
?
總結
以上是生活随笔為你收集整理的浅谈MySQL数据库中的锁与事务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 延迟开学不停学|网易云信在线教育解决方案
- 下一篇: 网易干货 | 浅析视频Jitter Bu