mysql 如何實(shí)現(xiàn)事務(wù)的隔離級(jí)別:
mvcc :
MVCC的全稱是“多版本并發(fā)控制”。這項(xiàng)技術(shù)使得InnoDB的事務(wù)隔離級(jí)別下執(zhí)行一致性讀操作有了保證,換言之,
就是為了查詢一些正在被另一個(gè)事務(wù)更新的行,并且可以看到它們被更新之前的值。這是一個(gè)可以用來(lái)增強(qiáng)并發(fā)性
的強(qiáng)大的技術(shù),因?yàn)檫@樣的一來(lái)的話查詢就不用等待另一個(gè)事務(wù)釋放鎖。這項(xiàng)技術(shù)在數(shù)據(jù)庫(kù)領(lǐng)域并不是普遍使用的。
一些其它的數(shù)據(jù)庫(kù)產(chǎn)品,以及mysql其它的存儲(chǔ)引擎并不支持它。
在沒有mvcc機(jī)制的時(shí)候:如果a事務(wù)給數(shù)據(jù)加上了鎖,其他所有讀寫請(qǐng)求只能排隊(duì)。mvcc通過(guò)在不同的時(shí)間節(jié)點(diǎn)
構(gòu)建不同的視圖來(lái)支持多事務(wù)讀操作。在通過(guò)innodb的undolog來(lái)實(shí)現(xiàn)的
undo log:
在數(shù)據(jù)修改的時(shí)候,不僅記錄了redo,還記錄了相對(duì)應(yīng)的undo,如果因?yàn)槟承┰驅(qū)е率聞?wù)失敗或回滾了,可以借助該undo進(jìn)行回滾。
undo log和redo log記錄物理日志不一樣,它是邏輯日志??梢哉J(rèn)為當(dāng)delete一條記錄時(shí),undo log中會(huì)記錄一條
對(duì)應(yīng)的insert記錄,反之亦然,當(dāng)update一條記錄時(shí),它記錄一條對(duì)應(yīng)相反的update記錄。
undo log是采用段(segment)的方式來(lái)記錄的,每個(gè)undo操作在記錄的時(shí)候占用一個(gè)undo log segment。
另外,undo log也會(huì)產(chǎn)生redo log,因?yàn)閡ndo log也要實(shí)現(xiàn)持久性保護(hù)。
行鎖
1.兩階段鎖:行鎖是在更新操作下才會(huì)加,但是需要持續(xù)到事務(wù)結(jié)束
2.應(yīng)該盡量減少鎖的粒度,減少對(duì)鎖的持有時(shí)間
3.應(yīng)該提防事務(wù)大規(guī)模數(shù)據(jù)更新,容易形成死鎖
4.大規(guī)模事務(wù)更新的時(shí)候應(yīng)該將粒度拆分
可重復(fù)讀
可重復(fù)讀:在事務(wù)開啟時(shí)候,讀取所有的數(shù)據(jù)在整個(gè)事務(wù)期間是相同的,
視圖是在事務(wù)提交后才失效
基于上面兩個(gè)標(biāo)題提出一個(gè)疑問(wèn)
在rr 模式下,一致性讀是否還有效果。如果同時(shí)兩個(gè)事務(wù)修改一條數(shù)據(jù)一致性讀很明顯會(huì)讓數(shù)據(jù)錯(cuò)亂?
解答:
首先,需要確認(rèn)一點(diǎn),上面問(wèn)題具有迷惑性。一致性視圖是用于讀的,這里存在好幾個(gè)概念:
1.可重復(fù)讀
2.行鎖
3,當(dāng)前讀
事務(wù)的可見性:
前面講過(guò):在事務(wù)開啟的時(shí)候,讀取所有數(shù)據(jù)再事務(wù)期間都是相同的,除非事務(wù)本身修改的內(nèi)容,這就是
事務(wù)的可重復(fù)讀。這是數(shù)據(jù)可見性的前提。
版本:
那么回歸到我們的版本:innodb 的每條數(shù)據(jù)可以共存多個(gè)版本,是由row trx_id 這個(gè)確定的,這個(gè)對(duì)我們
來(lái)說(shuō)是不可見的。一個(gè)trxid 對(duì)應(yīng)的就是一個(gè)版本。 當(dāng)多事務(wù)共同操作相同的數(shù)據(jù)的時(shí)候,可出現(xiàn)多個(gè)trxid,
每個(gè)trxid 之間存在一些聯(lián)系 ,這就是undolog。假設(shè)一個(gè)賬戶數(shù)據(jù) 在多個(gè)事務(wù)運(yùn)行同時(shí)存在:v1、v2、v3,
那么最新的版本數(shù)據(jù)就是v3,但是存在某個(gè)視圖讀取的肯定是v1,那么在一致性視圖層面來(lái)講 v1 =v3回滾+v2
回滾計(jì)算出來(lái)的,因?yàn)閿?shù)據(jù)庫(kù)不可能同時(shí)存在v1,v2,v3 三個(gè)版本的數(shù)據(jù)。v1,v2只能是臨時(shí)及算出來(lái)的。
水位:
當(dāng)一個(gè)事務(wù)啟動(dòng)的時(shí)候,會(huì)同時(shí)記錄當(dāng)前正在進(jìn)行的所有事務(wù),組成一個(gè)數(shù)組[ t1,t2,t3],
按照事務(wù)id順序排列。數(shù)組里面事務(wù) ID 的最小值記為低水位,當(dāng)前系統(tǒng)里面已經(jīng)創(chuàng)建過(guò)
的事務(wù) ID 的最大值加 1 記為高水位。程序更多的是通過(guò)高低水位來(lái)判斷某行應(yīng)該取那
個(gè)時(shí)間段的數(shù)據(jù)。因?yàn)樗皇遣粫?huì)變的,所以事務(wù)整體的視圖是不會(huì)變的。
總結(jié):
事務(wù)可見不可見可以通過(guò):
版本未提交,不可見;
版本已提交,但是是在視圖創(chuàng)建后提交的,不可見;
版本已提交,而且是在視圖創(chuàng)建前提交的,可見。
總歸回來(lái)好似講了一番廢話,但是卻表述了一致性視圖的原理
事務(wù)中的update:
案例:
某用戶,同時(shí)收到3筆轉(zhuǎn)賬,第一筆+100,第二
筆+200,第三筆+300,如果按照數(shù)據(jù)修改是基于一致性視圖來(lái)的,那么用戶的錢只能
是最后一筆提交的。
反證法:
其實(shí)這里又要結(jié)合行鎖來(lái)講解了。反證法:假設(shè)事務(wù)中的修改基于一致性視圖,如果多
個(gè)事務(wù)修改某一行數(shù)據(jù),假設(shè)行數(shù)據(jù)讀取的是一致性視圖,那么數(shù)據(jù)將總是以最后修改
的數(shù)據(jù)為準(zhǔn)。但是事實(shí)卻不是這樣的。
當(dāng)前讀:
事實(shí)上通過(guò)這個(gè)例子,我們就可以知道update 不是通過(guò)一致性視圖來(lái)操作的。
“更新數(shù)據(jù)都是先讀后寫的,而這個(gè)讀,只能讀當(dāng)前的值,稱為“當(dāng)前讀”(current read)”。
其實(shí)select 也可以使用這個(gè): 加鎖: share mode 或者 for update 兩種鎖都可以讀取到
當(dāng)前最新的數(shù)據(jù)。
一致性讀+行鎖:
按照案例解析,第一個(gè)進(jìn)行事務(wù)開啟轉(zhuǎn)賬的會(huì)獲取到行鎖,此時(shí)其他數(shù)據(jù)進(jìn)行修改的事
務(wù)就會(huì)被停住,當(dāng)然此時(shí)如果其他兩個(gè)事務(wù)讀的話也讀取不到最新的余額。
舉反例:這個(gè)例子是很多程序員喜歡寫的,但是存在很大安全隱患:
給用戶賬戶加500塊,如果用戶賬戶沒有直接創(chuàng)建用戶賬戶(例子夠看就行)
record ,err:= db.query("select balance from user_account where user_id =10");
if err == db.ErrRecordNotExist {
// 給用戶創(chuàng)建賬戶
....
}else{
new_balance = balance +500
db.query("update user_account set balance =new_balance where user_id= 10")
...
}
其實(shí)這個(gè)很多場(chǎng)景都能遇到,之前公司業(yè)務(wù)沒考慮到,很多邏輯代碼也是這么操作的
之前寫php的時(shí)候更多都是這么操作的。其實(shí)整體邏輯都是有漏洞的,這類邏輯應(yīng)該在
用戶創(chuàng)建的時(shí)候就應(yīng)該使用服務(wù)進(jìn)行相關(guān)數(shù)據(jù)插入。修改數(shù)據(jù)盡可能使用for update,
或者直接在語(yǔ)句上修改:set balance=balance+500。不然容易出現(xiàn)數(shù)據(jù)錯(cuò)亂的bug,還
比較難以排查。