事務(wù)的幾個(gè)相關(guān)概念
什么是事務(wù)溪食?
事務(wù)(Transaction)驯杜,一般是指要做的或所做的事情顶籽。在計(jì)算機(jī)術(shù)語(yǔ)中是指訪問(wèn)并可能更新數(shù)據(jù)庫(kù)中各種數(shù)據(jù)項(xiàng)的一個(gè)程序執(zhí)行單元(unit)厨钻。
為什么需要事務(wù)琐脏?
技術(shù)是服務(wù)于業(yè)務(wù)的,在許多使用數(shù)據(jù)庫(kù)的業(yè)務(wù)場(chǎng)景中吴裤,經(jīng)常需要進(jìn)行一系列操作旧找,這一系列操作要么全都成功,要么全都失敗麦牺,不允許出現(xiàn)成功了一半這樣的中間情況钮蛛。(例如經(jīng)典銀行轉(zhuǎn)賬案例,A扣10塊錢剖膳,B加十塊錢魏颓,要么全部執(zhí)行成功完成轉(zhuǎn)賬,要么A扣的錢回滾回去吱晒,B也不會(huì)加錢)
事務(wù)的ACID特性
基于對(duì)業(yè)務(wù)的總結(jié)甸饱,人們總結(jié)出了事務(wù)應(yīng)該具有如下四個(gè)特性,即:
- Atomic-原子性仑濒,原子在化學(xué)概念上是指化學(xué)反應(yīng)不可再分的基本微粒叹话,顧名思義原子性即要么都成功,要么都失敗躏精。
- Consistency-一致性渣刷,其實(shí)和原子性意思差不多套鹅,事務(wù)必須是使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變到另一個(gè)一致性狀態(tài)防楷。舉例子來(lái)說(shuō):A有100塊錢,B有50塊錢怠噪,A和B總共有150塊錢這是一個(gè)一致性狀態(tài);轉(zhuǎn)賬之后碌嘀,A有90塊錢涣旨,B有60塊錢,A和B還是一共150塊錢股冗。
- Isolation-隔離性霹陡,是指在并發(fā)多個(gè)事務(wù)訪問(wèn)的情況下,并發(fā)的事務(wù)是相互隔離的止状,一個(gè)事務(wù)的執(zhí)行不能夠被其它事務(wù)干擾烹棉。本文要討論的MySQL的四種事務(wù)隔離級(jí)別,就是為了保證并發(fā)度的前提下怯疤,你的事務(wù)到底能接受多大程度的不被干擾
- Duration-持久性浆洗,保證了上述各種特性之后,業(yè)務(wù)上還需要這個(gè)數(shù)據(jù)庫(kù)保證持久性集峦,不能出現(xiàn)我轉(zhuǎn)賬都完成了/我爐石開(kāi)出金色傳說(shuō)了伏社,結(jié)果數(shù)據(jù)庫(kù)斷電了,重啟后回到解放前了塔淤。這種屬于不可接受的線上事故摘昌,一般數(shù)據(jù)庫(kù)都會(huì)有各種手段在盡可能保證性能的同時(shí)持久化寫(xiě)數(shù)據(jù)到磁盤(pán)上。
MVCC
當(dāng)前讀 vs. 快照讀
當(dāng)前讀高蜂,每次都讀取記錄的最新版本聪黎,并且會(huì)對(duì)記錄進(jìn)行加鎖,典型的當(dāng)前讀操作:
- select lock in share mode(共享鎖)
- select for update(排他鎖)
- update(排他鎖)
- insert(排他鎖)
- delete(排他鎖)
快照讀妨马,每次讀取操作讀到的實(shí)際是基于當(dāng)前可見(jiàn)性生成的快照挺举,快照的實(shí)現(xiàn)基于多版本并發(fā)控制(MVCC),我們?nèi)粘J褂玫牟患渔i的select就是一種快照讀(當(dāng)事務(wù)隔離級(jí)別退化為串行時(shí)烘跺,默認(rèn)select就是當(dāng)前讀)。
快照讀是為了解決上文中提到的事務(wù)ACID特性中的Isolation隔離性而誕生的脂崔,有了快照的存在滤淳,會(huì)讓每個(gè)事務(wù)只看到自己應(yīng)該看到,仿佛數(shù)據(jù)庫(kù)系統(tǒng)只有當(dāng)前一個(gè)事務(wù)在執(zhí)行一樣砌左,正是隔離性的體現(xiàn)脖咐。
MVCC實(shí)現(xiàn)原理-ReadView
MySQl實(shí)現(xiàn)快照讀的原理就是MVCC(Multi-Version Concurrency Control,多版本并發(fā)控制)汇歹,而MVCC的核心就是快照ReadView屁擅,根據(jù)設(shè)置的不同的隔離級(jí)別(RC/RR)在不同的時(shí)機(jī)拍一張當(dāng)前能看到記錄的快照,這張照片就是ReadView产弹。
假設(shè)當(dāng)前有一張student表派歌,數(shù)據(jù)內(nèi)容如下:
id | name |
---|---|
1 | Alice |
那么在實(shí)際的數(shù)據(jù)庫(kù)存儲(chǔ)中,mysql會(huì)有隱藏的幾行:
- DB_TRX_ID
該行記錄的最近修改過(guò)的事務(wù)id,就像是文件系統(tǒng)里的最近修改時(shí)間一樣胶果,是生成ReadView時(shí)可見(jiàn)性判斷的重要依據(jù) - DB_ROLL_PTR
回滾指針匾嘱,如果這個(gè)記錄被修改過(guò),那么會(huì)指向上一個(gè)版本早抠,形成了一個(gè)歷史版本的鏈表 - DB_ROW_ID
隱藏主鍵霎烙,當(dāng)我們的表沒(méi)有指定主鍵的時(shí)候,這個(gè)字段就會(huì)作為聚簇索引
所以這個(gè)student表實(shí)際在db中存儲(chǔ)的格式可能為
上圖表明當(dāng)前這個(gè)記錄是 1:Bob蕊连,最近修改的事務(wù)id是4悬垃,隱藏主鍵也是1。
這個(gè)記錄歷史上最早name是Cao(事務(wù)6創(chuàng)建的)甘苍,
之后被事務(wù)1修改為Bob尝蠕,
最后被事務(wù)4修改為Alica
基于ReadView的快照讀
依據(jù)當(dāng)前的事務(wù)隔離級(jí)別,MySQL事務(wù)會(huì)在某個(gè)時(shí)機(jī)下生成一個(gè)ReadView(開(kāi)始拍照片)羊赵,基于上面理解趟佃,我們知道,一行記錄會(huì)有DB_ROLL_PTR(回滾指針)拉起來(lái)的一個(gè)鏈表昧捷,指向改行記錄的歷史版本闲昭。在快照讀的時(shí)候,會(huì)基于DB_TRX_ID判斷 這行記錄是否可見(jiàn)靡挥,從而完成查詢序矩,整個(gè)過(guò)程如下圖。
ReadView可見(jiàn)性算法
如何判斷這行記錄是否可見(jiàn)跋破?ReadView是通過(guò)維護(hù)了這幾個(gè)值來(lái)判斷的:
- trx_list
快照生成時(shí)還在活躍的事務(wù)id(活躍指的是處于begin -> dosomething -> commit
的dosomething階段簸淀,尚未commit的事務(wù)) - up_limit_id
記錄trx_list中最小的事務(wù)id,小于這個(gè)事務(wù)id的事務(wù)必然都已經(jīng)提交過(guò)了毒返,可以理解為是歷史記錄租幕,肯定可見(jiàn) - low_limit_id
記錄ReadView生成時(shí)刻系統(tǒng)尚未分配的下一個(gè)事務(wù)ID,也就是目前已出現(xiàn)過(guò)的事務(wù)ID的最大值+1拧簸,這個(gè)id之后都是快照生成之后操作劲绪,肯定不可見(jiàn)
基于以上三個(gè)屬性,可以將student表中各個(gè)記錄以及其undo log中的回滾版本記錄的DB_TRX_ID歸類為如下盆赤。
基于以上信息贾富,可以得到MVCC判斷某行記錄是否可見(jiàn)的偽代碼如下:
def is_visible(row):
if row.DB_TRX_ID < up_limit_id:
return true # 1. 操作事務(wù)是歷史事務(wù)了,肯定可見(jiàn)
if row.DB_TRX_ID > low_limit_id:
return false # 2. 操作事務(wù)是一個(gè)晚于當(dāng)前ReadView的事務(wù)牺六,肯定看不到
if row.DB_TRX_ID in trx_list:
return false # 3. 在活躍事務(wù)中(說(shuō)明尚未提交)颤枪,不可見(jiàn)
return true
ACID中的隔離性與并發(fā)性的討論
四種隔離級(jí)別總結(jié)
MySQL四種隔離級(jí)別總結(jié)如下:
隔離級(jí)別 | 名稱的含義 | 可能有的問(wèn)題 |
---|---|---|
READ UNCOMMITTED | 這個(gè)模式下,一個(gè)事務(wù)能讀(read)到另一個(gè)事務(wù)還沒(méi)提交的(uncommitted)更改 | 臟讀淑际,另一個(gè)事務(wù)還沒(méi)提交的修改畏纲,這種不完整的數(shù)據(jù)稱為臟數(shù)據(jù) |
READ COMMITTED | 這個(gè)模式下扇住,一個(gè)事務(wù)能讀(read)到另一個(gè)事務(wù)已經(jīng)提交的(committed)更改 | 不可重復(fù)讀,因?yàn)槿绻貜?fù)讀的話就會(huì)看到結(jié)果變了(靈異事件霍骄,害怕.jpg) |
REPEATABLE READ(MySQL默認(rèn)) | 這個(gè)模式下台囱,一個(gè)事務(wù)能重復(fù)讀(read)同樣的記錄保證結(jié)果相同(不管另一個(gè)事務(wù)提交還沒(méi)提交)(可以簡(jiǎn)單理解為做了一個(gè)這個(gè)事務(wù)id為key的緩存) | 幻讀,在一個(gè)事務(wù)中读整,第一次查詢某條記錄簿训,發(fā)現(xiàn)沒(méi)有,但是米间,當(dāng)試圖更新這條不存在的記錄時(shí)强品,竟然能成功,并且屈糊,再次讀取同一條記錄的榛,它就神奇地出現(xiàn)了(害怕.jpg) |
SERIALIZABLE | 完全串行,一個(gè)事務(wù)開(kāi)啟后逻锐,另一個(gè)事務(wù)被掛起 | 無(wú)任何問(wèn)題夫晌,就是沒(méi)法并發(fā)了,性能很差昧诱∠恚幻讀出現(xiàn)的條件很苛刻,而且一般來(lái)說(shuō)事務(wù)中不會(huì)有人更新一個(gè)“不存在的記錄”盏档,所以一般RR隔離級(jí)別足夠 |
READ UNCOMMITTED
基于上文對(duì)ReadView的理解凶掰,我們可以從原理上理解各個(gè)隔離級(jí)別是怎么做到的。RU級(jí)別蜈亩,即沒(méi)有MVCC機(jī)制的情況下的方式懦窘,即每次select都是獲取到某行記錄最新的結(jié)果。
READ COMMITTED
從RC隔離級(jí)別開(kāi)始稚配,我們開(kāi)始使用MVCC機(jī)制了畅涂,在每次讀操作的時(shí)候都會(huì)建立建立一次ReadView,這個(gè)ReadView自然會(huì)隱藏掉本事務(wù)不該見(jiàn)到的記錄道川。
REPEATABLE READ(默認(rèn)隔離級(jí)別)
從上面的分析可以看出毅戈,每次讀操作都建立一次ReadView確實(shí)可以保證 在活躍事務(wù)中(說(shuō)明尚未提交)的記錄不可見(jiàn),但是每次讀都生成新的ReadView展示當(dāng)前最新的可見(jiàn)性愤惰,會(huì)導(dǎo)致看到其它事務(wù)已提交的修改,這又在某些場(chǎng)景下不符合隔離性的 仿佛沒(méi)有其它事務(wù)在執(zhí)行 的要求赘理。因此又有了更嚴(yán)格的RR級(jí)別宦言,這種級(jí)別下 第一次執(zhí)行操作后 會(huì)生成ReadView,并且以后的查詢操作都會(huì)使用這一版本的ReadView商模,保證了可見(jiàn)性始終如一奠旺。
如上圖所示蜘澜,RR級(jí)別在某種意義上來(lái)說(shuō)已經(jīng)是一種非常完美的隔離級(jí)別了,事務(wù)2的整個(gè)執(zhí)行過(guò)程都仿佛完全感知不到事務(wù)1的存在响疚。只是這種模式下有一種產(chǎn)生幻讀的可能鄙信,如果此時(shí)事務(wù)1插入了一條id=2的記錄,這時(shí)事務(wù)2還是看不到的忿晕,但是如果這時(shí)事務(wù)2更新了這條id=2的記錄装诡,那么我們可以發(fā)現(xiàn)MySQl會(huì)將這條被更新的最新結(jié)果加入到事務(wù)2當(dāng)前的ReadView中,從而導(dǎo)致出現(xiàn)了幻讀的現(xiàn)象践盼。
SERIALIZABLE
事務(wù)的最高隔離級(jí)別鸦采,不存在任何的臟讀、不可重復(fù)讀咕幻、幻讀的問(wèn)題渔伯,事務(wù)完全按照順序線性執(zhí)行,性能極大損失肄程,一般不會(huì)使用锣吼。