mysql如何实现读提交锁_MySQL学习笔记(二)—MySQL事务及锁详解
一、事務(wù)
數(shù)組庫的一組操作,要么全部成功,要么全部失敗
舉例:銀行轉(zhuǎn)賬 A賬戶向B賬戶轉(zhuǎn)100
A賬戶余額扣去100
B賬戶余額增加100
上述兩個操作要么全部成功,要么全部失敗,部分成功或失敗,數(shù)據(jù)就錯亂了
1. 事務(wù)的四大特征
原子性:事務(wù)是原子性操作,要么全部成功,要么全部失敗
一致性:多個事務(wù)對數(shù)據(jù)庫操作會保證數(shù)據(jù)一致性
隔離性:并發(fā)時,事務(wù)之間互不影響
持久性:事務(wù)提交之后對數(shù)據(jù)庫的影響是持久性的,不會因為數(shù)據(jù)庫宕機導(dǎo)致數(shù)據(jù)丟失
2. 并發(fā)事務(wù)帶來的問題
臟讀
在一個事務(wù)中,讀取了其他事務(wù)未提交的數(shù)據(jù)
不可重復(fù)讀
在一個事務(wù)中,同一行記錄被訪問了兩次卻得到了不同的結(jié)果
幻讀
在一個事務(wù)中,同一個范圍內(nèi)的記錄被讀取時,其他事務(wù)向這個范圍添加了新的記錄。
前面臟讀和不可重復(fù)讀容易理解,幻讀稍微難一點
假設(shè)圖一test開始是空表,事物1第一次查詢得到空表,事物2在事物1執(zhí)行期間插入一條數(shù)據(jù),
事物1第二次查詢由于滿足可重復(fù)讀,所以查詢結(jié)果依然為空,但是事物1插入同樣一條數(shù)據(jù),報重復(fù)主鍵錯誤
幻讀兩個要素:
可重復(fù)讀隔離級別下,快照讀看到的是一致性視圖,只有當(dāng)前讀才會產(chǎn)生幻讀
幻讀專指新插入發(fā)行,更新不算,將上述查詢后面加上For Update,就會將事務(wù)2插入的數(shù)據(jù)讀出來,這就是幻讀
3. 事務(wù)隔離級別
為了解決上述并發(fā)事務(wù)問題,MySQL數(shù)據(jù)庫提供了事務(wù)隔離級別
事物隔離級別
臟讀
不可重復(fù)讀
幻讀
讀未提交(read-uncommitted)
是
是
是
讀已提交(read-committed)
否
是
是
可重復(fù)讀(repeatable-read)
否
否
是
串行化(serializable)
否
否
否
可重復(fù)讀是MySQL默認級別
二、重要概念
1. MVCC和事務(wù)隔離的實現(xiàn)
同一數(shù)據(jù)庫記錄可以在系統(tǒng)中存在多個版本,這就是MVCC (多版本并發(fā)控制)
不同時刻開啟的事務(wù)會創(chuàng)建不同的視圖,后續(xù)直接從視圖讀取數(shù)據(jù),達到數(shù)據(jù)隔離,當(dāng)然數(shù)據(jù)隔離還需要數(shù)據(jù)庫鎖的幫助
InnoDB 里面每個事務(wù)有一個唯一的事務(wù) ID,叫作 transaction id,在事務(wù)開始的時
候向 InnoDB 的事務(wù)系統(tǒng)申請的,是按申請順序嚴格遞增
在MySQL中,每條記錄的更新都會記錄一條undo Log,記錄上最新的值通過回滾可以,都可以得到前一個狀態(tài)的值
上圖中,數(shù)據(jù)庫一行記錄有多個版本,每個版本有自己的 row trx_id,最新版本V4的k=22,是被row trx_id=25事務(wù)更新的,不同時刻啟動的事務(wù)看到不同的視圖,而V1,V2,V3不是物理上真實存在的,要想得到它們需要根據(jù)當(dāng)前版本和undo Log(回滾日志)計算,比如V1的值需要執(zhí)行U3,U2,U1才能得到
undo Log日志如果一直存在,可能會嚴重占據(jù)磁盤空間,當(dāng)系統(tǒng)沒有比undo Log更早的視圖時,就會把undo Log刪除掉
長事務(wù)一般會保存很老的事務(wù)視圖,導(dǎo)致其它事務(wù)的undo Log無法刪除,所以在這個事務(wù)提交前,可能會導(dǎo)致大量undo Log存在,我們需要避免使用長事務(wù)
2. 視圖
用查詢語句定義的虛擬表,在調(diào)用的時候執(zhí)行查詢語句并生成結(jié)果。這是我們常說的視圖
InnoDB 用來實現(xiàn) MVCC 時用到的一致性讀視圖,即 consistent read view, 用于支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復(fù)讀)隔離級別的實現(xiàn)。沒有物理結(jié)構(gòu),僅僅是邏輯上用來定義在事務(wù)執(zhí)行期間能看到什么數(shù)據(jù)
3. 事務(wù)的起點
begin/start transaction 命令并不是一個事務(wù)的起點,在執(zhí)行到它們之后的第一個操作 InnoDB 表的語句,事務(wù)才真正啟動, start transaction with consistent snapshot 該命令可以立即啟動事務(wù)
4. 隔離級別與視圖的關(guān)系
“讀未提交”隔離級別下直接返回記錄上的最新值,沒有視圖概念
“讀提交”隔離級別,這個視圖是在每個 SQL 語句開始執(zhí)行的時候創(chuàng)建的
“可重復(fù)讀”隔離級別: 視圖是在事務(wù)啟動 (執(zhí)行第一條語句或者使用特定命令) 時創(chuàng)建的,整個事務(wù)存在期間都用同一個視圖
“串行化”隔離級別下直接用加鎖的方式來避免并行訪問
5. 當(dāng)前讀與快照讀
當(dāng)前讀,在事務(wù)執(zhí)行過程中可以讀到其它已已提交事務(wù)的最新數(shù)據(jù)
快照讀,在事務(wù)執(zhí)行過程中只能看到從事務(wù)起點創(chuàng)建的一致性視圖,并不能讀到其它已提交數(shù)據(jù)
在RR(可重復(fù)讀)級別下,快照讀滿足以下兩個規(guī)則:
讀取的記錄:更新的事務(wù)ID <= 當(dāng)前事務(wù)ID
讀取的記錄:刪除的事務(wù)ID > 當(dāng)前事務(wù)ID(小于的話數(shù)據(jù)都刪了,肯定讀不到)
三、MySQL鎖分類
按照不同維度可分為:
1)
悲觀鎖
樂觀鎖
2)
共享鎖(寫鎖)
排它鎖(讀鎖)
3)
意向共享鎖
意向互斥鎖
意向鎖其實不會阻塞全表掃描之外的任何請求
假設(shè)沒有意向鎖,兩個請求,一個修改數(shù)據(jù)某一行記錄,另一個需要修改該表所有行記錄,這時需要就需要對所有的行是否被鎖定進行掃描,引入意向鎖,只需要判斷該表有沒有意向鎖,等待修改單行事務(wù)提交,意向鎖釋放
4)
全局鎖
表鎖和元數(shù)據(jù)鎖(meta data lock 簡稱(MDL))
行鎖
全局鎖:對整個數(shù)據(jù)庫實例加鎖
作用: MyISAM不支持事務(wù)拿不到一致性視圖,需要加全局讀鎖做邏輯備份。加讀鎖期間數(shù)據(jù)庫只能讀,不能寫。
表鎖:使用lock tables 命令來鎖住整個表,一般不使用
MDL: 當(dāng)對表做增刪改查操作時,需要加MDL讀鎖;當(dāng)需要對表做結(jié)構(gòu)變更操作時需要加MDL寫鎖(見其它篇文章)
所以如果有兩個線程,一個對表做讀操操作,一個需要給表加字段,第二個操作會被阻塞。
在給表加字段的時候,如果該表請求頻繁,這時會無法獲取MDL寫鎖,同時會阻塞后續(xù)業(yè)務(wù)請求拿讀鎖。
解決方法:在 alter table語句里面設(shè)定等待時間,如果在指定的等待時間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業(yè)務(wù)語句,先放棄。
行鎖: 在引擎層實現(xiàn),MyISAM不支持行鎖。
重要概念: 兩階段加鎖
在數(shù)據(jù)庫更新時會給掃描的數(shù)據(jù)行加行鎖,更新結(jié)束不會立馬釋放行鎖,需要等到事務(wù)提交才會釋放行鎖。
由于兩階段鎖的存在,所以在一個事務(wù)中,更新語句如果放在前面,會阻塞其它事務(wù)對表的更新,影響并發(fā)。對于更新頻繁的語句盡量放在事務(wù)的靠后部分
死鎖
解決方案:
超時等待
發(fā)起死鎖檢測,主動回滾其中某個事務(wù)
超時等待的時間根據(jù)業(yè)務(wù)執(zhí)行時間制定,太短誤傷,太長會影響并發(fā)量
死鎖檢測有額外負擔(dān),在事務(wù)被鎖住,需要查看其依賴的線程是否被鎖住,一直循環(huán),最后判斷出現(xiàn)死鎖,在多個線程并發(fā)修改同一行數(shù)據(jù)時,時間復(fù)雜度會變成O(n^2),會導(dǎo)致CPU利用率很高,卻執(zhí)行不了幾個事務(wù)。一般通過控制并發(fā)來解決
5)
記錄鎖(record Lock)
間隙鎖(Gap lock)
next-key
在另一篇文章中詳細講解了加鎖情況
數(shù)據(jù)庫的行鎖實際上record Lock,會對掃描的行加鎖,如果沒有走索引,掃描全表,會鎖住整個表的所有行。
例:
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
語句1:select * from t where id >3 for update;
語句2:select * from t where d > 3 for update;
語句1會走主鍵索引,對掃描到的行數(shù)加鎖
語句2不走索引,掃描全表,對所有行加record lock
在可重復(fù)讀的隔離級別下:
每次開啟事務(wù),會生成一致性視圖,看不到其它事務(wù)已經(jīng)提交的修改,在前面已經(jīng)提過
更新語句先讀后寫,這個讀是當(dāng)前讀,就算我們對所有數(shù)據(jù)加上record lock,也不能阻止數(shù)據(jù)的插入。這樣我們在當(dāng)前讀中還是會讀到插入的數(shù)據(jù),形成幻讀。
如何避免幻讀?
使用Gap lock + record lock
間隙鎖是對索引記錄中的一段連續(xù)區(qū)域的鎖
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
這個語句阻止其他事務(wù)向表中插入 id = 15 的記錄,因為整個范圍都被間隙鎖鎖定
雖然間隙鎖中也分為共享鎖和互斥鎖,不過它們之間并不是互斥的,也就是不同的事務(wù)可以同時持有一段相同范圍的共享鎖和互斥鎖,它唯一阻止的就是其他事務(wù)向這個范圍中添加新的記錄
間隙鎖的引入,可能會導(dǎo)致同樣的語句鎖住更大的范圍,但是它只在可重復(fù)讀級別下才會生效
Next-Key是記錄鎖和記錄前的間隙鎖的結(jié)合,每個 next-key lock 是前開后閉區(qū)間
select * from t where id = 5
會加上(4, 5]的next-key,同時會加上(5, 6]的間隙鎖
next-key的加鎖原則是鎖定的是當(dāng)前值和前面的范圍
注:一般生產(chǎn)都會設(shè)置讀已提交級別,這個時候為了防止binlog和數(shù)據(jù)庫數(shù)據(jù)不一致需要設(shè)置binlog格式為row,在代碼中使用鎖來解決并發(fā)問題。數(shù)據(jù)庫應(yīng)該盡可能簡單,不管是語句,還是隔離級別,保證數(shù)據(jù)庫的性能。
參考
丁奇老師 MySQL45講
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的mysql如何实现读提交锁_MySQL学习笔记(二)—MySQL事务及锁详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux mysql无符号整型_Mys
- 下一篇: Mysql主从异常 表被回滚_oracl