MySQL中?LBCC?和?MVCC?的理解及常見問題示例
1. 事務
介紹MVCC之前,先介紹下事務:事務是為了保證數(shù)據(jù)庫中數(shù)據(jù)的完整性和一致性。
事務的4個基本要素:
- 原子性(Atomicity):要么同時成功,要么同時失敗。(通過undo log回滾日志實現(xiàn))
- 一致性(Consistency):一方扣款 xxx 元,另一方收款 xxx 元,符合事物發(fā)展的正常邏輯(通過lock鎖實現(xiàn))
- 隔離性(Isolation):此時有多個類似 扣款/收款 事件同時發(fā)生,每個事件之間是相互獨立的(通過 lock鎖 + MVCC實現(xiàn))
- 持久性(Durability):不管數(shù)據(jù)庫宕機或重啟,數(shù)據(jù)最終都落到了磁盤上,下次加載依然可見 (通過 redo log實現(xiàn))
2. MVCC初探
目的:主要是為了 提高數(shù)據(jù)庫并發(fā)性能。用更好的方式去處理 讀/寫 沖突,做到即使有 讀/寫 沖突時,也能做到不加鎖,非阻塞并發(fā)讀。
不同隔離級別下,可能引發(fā)的問題: 臟讀:并發(fā)情況下,一方事務讀到了另一方事務 “已 update
但未 commit
” 的數(shù)據(jù),破壞了事務隔離性。不可重復讀:并發(fā)情況下,一方事務讀到了另一方事務 “已 update
或 delete
,并 commit
” 的數(shù)據(jù),破壞了事務隔離性。幻讀:并發(fā)情況下,一方事務讀到了另一方事務" insert
并 commit
"的數(shù)據(jù),導致前后讀取結果不一致。
MVCC中的四種事務隔離級別:
提問:V1、V2、V3在不同事務隔離級別下讀取到的值分別是:
- RU-讀未提交 級別:20、20、20(可能發(fā)生:臟讀、不可重復讀)
- RC-讀已提交 級別:18、20、20(不可能發(fā)生:臟讀、可能發(fā)生:不可重復度)
- RR-可重復讀 級別:18、18、20 (不可能發(fā)生:臟讀、不可重復讀;但是因為事務A已提交,所以V3再次查詢時跟事務A是沒有隔離性的要求的,因此V3讀取到的是20)
3. LBCC & MVCC
- LBCC(Lock-Base Concurrency Control)基于鎖的并發(fā)控制;
- MVCC(Multiversion Concurrency Control)多版本并發(fā)控制;
LBCC 鎖相關:
MySQL 5.5 版本之前,默認的存儲引擎是MyISAM,5.5之后默認引擎是Innodb。Innodb支持事務,包括:行鎖/表鎖,MyISAM不支持。 意向鎖 意向共享鎖/讀鎖(表鎖類型,無法手動創(chuàng)建),mysql 中語法: lock in share mode
意向排它鎖/寫鎖(表鎖類型,無法手動創(chuàng)建),mysql 中語法: for update
常見問題:為什么要加入意向鎖?
意向鎖并不是真正用來鎖定數(shù)據(jù)的,而是用來告訴你當前表中是否已經有了被 共享鎖/排它鎖
鎖定的數(shù)據(jù)行。如果有就沒必要再去加無用的表鎖了,起到一個標識作用,提高加表鎖的效率(相當于高鐵洗手間門上方是否有人正在使用的 “指示燈”)。
記錄鎖(Record Lock)、間隙鎖(Gap Lock)、臨鍵鎖(Next-Key Lock):
- 介紹:臨鍵鎖 = 記錄鎖 + 間隙鎖,是 RR 可重復讀-隔離級別下獨有的,
- 目的:間隙鎖的出現(xiàn)就是為了解決可重復讀隔離級別下的幻讀問題
問題:如圖示:執(zhí)行此sql語句(先開啟事務):BEGIN; SELECT * FROM tbl WHERE id > 15 FOR UPDATE;
,以下兩個sql語句可以執(zhí)行成功嗎?
MVCC底層實現(xiàn)詳解:
快照讀(實際上為讀相關的操作):讀取的是記錄的可見版本 (有可能是歷史版本),不用加鎖。
簡單的 SELECT
操作,屬于快照讀,不加鎖。
SELECT * FROM user WHERE ?
當前讀(實際上為寫相關的操作):在事務中,update
數(shù)據(jù)前,還要去MySQL中重新讀取一遍該數(shù)據(jù)對應最新版本的記錄,并且 當前讀 返回的記錄都會加上鎖,保證其他事務不會再并發(fā)修改這條記錄。以下兩種方式都屬于當前讀,需要加鎖:
- 特殊讀 (加鎖讀):
SELECT
* FROM user WHERE id = xxxLOCK IN SHARE MODE
; INSERT
/UPDATE
/DELETE
等寫操作。
問題:在 RR-可重復讀 的默認隔離級別下,假設起始的age為18,那么Q1和Q2對應的age分別是多少呢?
- 針對 “事務B” 分析:因為存在
UPDATE
寫操作,觸發(fā)了 當前讀,所以要先去讀最新提交的版本號記錄(即:事務CUPDATE
后提交的記錄),然后事務B再去執(zhí)行自己的UPDATE
操作。也就是要先去讀事務C提交的最新數(shù)據(jù)為19,然后事務B自身再UPDATE
加1最終變?yōu)?0。 - 針對 “事務A” 分析:因為事務A本身是沒有任何的寫操作,僅僅是
SELECT
查詢操作,觸發(fā) 快照讀。所以事務A只認準事務 BEGIN 開始之前記錄的 最新最后提交的版本號,其記錄值也就是初始的18。
BEGIN
事務開始的時候會創(chuàng)建一個快照,并為對應事務分配一個事務id,即 TRX_ID- 開啟事務之前最后的版本號為:up_limit_id=999,對應 age=18
- 事務B和事務C都有
UPDATE
操作(當前讀),所以 row_trx_id 為自身的 TRX_ID 的值,分別是1001和1002。而事務A沒有UPDATE
操作(快照讀),所以只認準事務A在 事務開始前 最后的版本號 up_limit_id=999,其 age=18。
總結
- 事務:是為了保證數(shù)據(jù)庫中數(shù)據(jù)的完整性和一致性。事務的4個特性:ACID。
- MVCC的好處:提高數(shù)據(jù)庫并發(fā)性能。用更好的方式去處理 讀/寫 沖突,做到即使有 讀/寫 沖突時,也能做到不加鎖,非阻塞并發(fā)讀。
- MVCC四種隔離級別 :讀未提交、讀已提交、可重復讀(MySQL默認級別)、串行化。
- MVCC事務隔離級別中,常見的三種問題:臟讀、幻讀、不可重復讀。在RR的默認隔離級別下,單純的
SELECT
只觸發(fā) “快照讀” 。而當你包含INSERT
/UPDATE
/DELETE
等 寫操作 時,這時就會觸發(fā) 當前讀,也就是在事務中,在相關寫操作之前會再去讀取一次其他事務的最后提交記錄。這里的關鍵在于你事務中的sql是單純的SELECT
語句(快照讀),還是你事務在的sql是包含了INSERT
/UPDATE
/DELETE
等 寫操作(當前讀)。 - 沒有建立索引或索引失效,行鎖會升級為表鎖,因為找不到對應行記錄。所以為了避免兩個事務同時修改一張表的不同記錄會導致表鎖的問題,建議加上索引,這樣就只是行鎖,而不會升級為表鎖!
- 幻讀的解決關鍵在于 間隙鎖 和 臨鍵鎖(臨鍵鎖 = 記錄鎖 + 間隙鎖) 。
最后,補充一個問題點:
如果不聲明的創(chuàng)建主鍵,會有哪些危害? 比如你的id(假設int類型)沒有聲明為主鍵,并且也沒有聲明唯一索引(當未聲明主鍵時,唯一索引會被取代為主鍵)
- 行鎖升級為表鎖
- 當數(shù)據(jù)量達到頂峰的時候,可能會造成“主鍵沖突”,int的取值范圍為2^32 -1,當未聲明主鍵時,達到最大值范圍時,id會再次重新從0開使自增,這時候可能會出現(xiàn)覆蓋之前row_id記錄的情況,造成數(shù)據(jù)丟失。相反的,如果聲明主鍵的話,那么當id達到上限時,再次insert時會報“主鍵沖突”錯誤,這時候可以將之前的int 類型的id改為big int。
- MySQL會自動聲明一個“隱藏主鍵 row_id”,占6字節(jié)。而你自己聲明int類型的主鍵時,只會消耗4字節(jié)。因此這是一種資源的浪費!
到此這篇關于MySQL中 LBCC 和 MVCC 的理解及常見問題示例的文章就介紹到這了,更多相關MySQL中LBCC和 MVCC內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MySQL?從0到1打開數(shù)據(jù)庫管理操作方法
數(shù)據(jù)庫管理系統(tǒng)(DataBase?Management?System)是用來創(chuàng)建數(shù)據(jù)庫和管理數(shù)據(jù)庫數(shù)據(jù)的一個管理軟件,我們口頭說的MySQL數(shù)據(jù)庫就是這個管理系統(tǒng),這篇文章主要介紹了MySQL從0到1打開數(shù)據(jù)庫管理,需要的朋友可以參考下2023-06-06MySQL?中的count(*)?與?count(1)?誰更快一些?
這篇文章主要討論MySQL?中?count(*)?與?count(1)?誰更快一些?以下討論基于?InnoDB?存儲引擎,并且再文末單獨說一下MyISAM?,感興趣的小伙伴可以參考一下2022-02-02