1、前言
mvcc 即多版本并發(fā)控制,即通過(guò)多版本的方式實(shí)現(xiàn)讀寫(xiě)數(shù)據(jù)的高并發(fā)贝奇,主要是通過(guò)多版本和鎖來(lái)實(shí)現(xiàn)的。多版本是使用版本鏈 + undo log靠胜,鎖是使用間隙鎖掉瞳。
版本鏈?zhǔn)窃趺磳?shí)現(xiàn)的呢?
在 innerdb 數(shù)據(jù)的每一行浪漠,除了存儲(chǔ)的數(shù)據(jù)之外陕习,還存儲(chǔ)了隱藏的兩列,分別為 trx_id和db_roll_ptr址愿。trx_id表示最近修改的事務(wù)的 id(即當(dāng)前事務(wù)啟動(dòng)時(shí)的分配的事務(wù) id)该镣,db_roll_ptr 指向 undo segment 中的 undo log(即指向老版本),因?yàn)?strong>每次對(duì)某條聚簇索引記錄進(jìn)行改動(dòng)時(shí)必盖,都會(huì)把舊的版本寫(xiě)入到 undo日志中拌牲。我們知道,undo log 主要記錄了數(shù)據(jù)之前的數(shù)據(jù)信息歌粥,通過(guò)這些信息可以還原到之前版本的狀態(tài)塌忽,這玩意在事務(wù)中非常有用,而且在 mvcc 中失驶,我們也要用土居。
一個(gè)版本鏈類(lèi)似于這樣:
那么對(duì)于可重復(fù)讀以及讀已提交,我們?cè)趺赐ㄟ^(guò)上面所說(shuō)的東西是實(shí)現(xiàn)事務(wù)呢?這里就要引入 readview 了擦耀,這個(gè) ReadView 中主要包含4個(gè)比較重要的內(nèi)容:
- m_ids :表示在生成 ReadView 時(shí)當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)的 事務(wù)id 列表棉圈。
- min_trx_id :表示在生成 ReadView 時(shí)當(dāng)前系統(tǒng)中活躍的讀寫(xiě)事務(wù)中最小的 事務(wù)id ,也就是 m_ids 中的最小值眷蜓。
- max_trx_id :表示生成 ReadView 時(shí)系統(tǒng)中應(yīng)該分配給下一個(gè)事務(wù)的 id 值(注意:max_trx_id 并不是 m_ids 中的最大值分瘾,事務(wù) id 是遞增分配的。比方說(shuō)現(xiàn)在有id為1吁系,2德召,3這三個(gè)事務(wù),之后 id 為3的事務(wù)提交了汽纤。那么一個(gè)新的讀事務(wù)在生成 ReadView 時(shí)上岗,m_ids 就包括1和2,min_trx_id 的值就是1蕴坪,max_trx_id 的值就是4肴掷。
- creator_trx_id :表示生成該 ReadView 的事務(wù)的 事務(wù)id (只有在對(duì)表中的記錄做改動(dòng)時(shí)(執(zhí)行INSERT、DELETE背传、UPDATE這些語(yǔ)句時(shí))才會(huì)為事務(wù)分配事務(wù)id呆瞻,否則在一個(gè)只讀事務(wù)中的事務(wù)id值都默認(rèn)為0)
對(duì)于查詢(xún)時(shí)的版本鏈數(shù)據(jù)是否看見(jiàn)的判斷邏輯:
- 如果被訪(fǎng)問(wèn)版本的 trx_id 屬性值與 ReadView 中的 creator_trx_id 值相同,意味著當(dāng)前事務(wù)在訪(fǎng)問(wèn)它自己修改過(guò)的記錄续室,所以該版本可以被當(dāng)前事務(wù)訪(fǎng)問(wèn)栋烤。
- 如果被訪(fǎng)問(wèn)版本的 trx_id 屬性值小于 ReadView 中的 min_trx_id 值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成 ReadView 前已經(jīng)提交挺狰,所以該版本可以被當(dāng)前事務(wù)訪(fǎng)問(wèn)明郭。
- 如果被訪(fǎng)問(wèn)版本的 trx_id 屬性值大于 ReadView 中的 max_trx_id 值,表明生成該版本的事務(wù)在當(dāng)前事務(wù)生成 ReadView 后才開(kāi)啟丰泊,所以該版本不可以被當(dāng)前事務(wù)訪(fǎng)問(wèn)薯定。
- 如果被訪(fǎng)問(wèn)版本的 trx_id 屬性值在 ReadView 的 min_trx_id 和 max_trx_id 之間,那就需要判斷一下 trx_id 屬性值是不是在 m_ids 列表中瞳购,如果在话侄,說(shuō)明創(chuàng)建 ReadView 時(shí)生成該版本的事務(wù)還是活躍的,該版本不可以被訪(fǎng)問(wèn)学赛;如果不在年堆,說(shuō)明創(chuàng)建 ReadView 時(shí)生成該版本的事務(wù)已經(jīng)被提交,該版本可以被訪(fǎng)問(wèn)盏浇。
2变丧、例子
舉一個(gè)例子,比如我們有一個(gè)表 person绢掰,屬性為 (id, name)痒蓬,剛開(kāi)始有一條數(shù)據(jù)(它是事務(wù)10創(chuàng)建的)童擎,數(shù)據(jù)格式為:
然后有一個(gè)事務(wù)11,它更新了這條數(shù)據(jù)攻晒,將名字變成 LiLi顾复,然后事務(wù)提交,那么版本鏈如下所示:
在事務(wù)11后鲁捏,又有一個(gè)事務(wù)12芯砸,它也更新了這條數(shù)據(jù),將名字變成了 Joke给梅,但是沒(méi)有提交乙嘀,版本鏈如下所示:
在讀以提交的情況下:
假設(shè)在事務(wù)11更新后事務(wù)12更新前 select(讀取不分配事務(wù)id,所以你 select 不會(huì)有新的版本數(shù)據(jù))破喻,那么對(duì)于 select 來(lái)說(shuō),min_trx_id = 12盟榴,max_trx_id = 12 + 1 = 13曹质,m_ids = [12],m那么根據(jù)上述規(guī)則擎场,從版本鏈的最新版本開(kāi)始讀羽德,發(fā)現(xiàn) 12 在 m_ids 中,則往下一個(gè)迅办。然后一直找發(fā)現(xiàn) 11 < 12宅静,則說(shuō)明 select 此時(shí)只能讀到版本為11的數(shù)據(jù),即名字為 LiLi站欺。
然后事務(wù)13又到12更新后并提交事務(wù)后再 select姨夹,此時(shí) m_ids = [],根據(jù)規(guī)則可以讀到版本12矾策,即 Joke磷账。
在可重復(fù)讀的情況下:
事務(wù)的 readview 是在事務(wù)開(kāi)始的時(shí)候生成的,所以分析更為簡(jiǎn)單贾虽,事務(wù)開(kāi)始的時(shí)候分析哪些活躍的事務(wù)逃糟,版本鏈為什么,后續(xù) readview 都使用這個(gè)蓬豁。比如事務(wù)按照上述順序绰咽,select 開(kāi)始讀到的版本為10,如果 select 事務(wù)沒(méi)結(jié)束地粪,就算事務(wù)13提交了取募,讀到的數(shù)據(jù)都是10不變。
3驶忌、update
對(duì)于 select 來(lái)說(shuō)矛辕,是版本讀笑跛。那么對(duì)于 update 來(lái)說(shuō),卻是當(dāng)前讀聊品,即 update 的時(shí)候總是讀當(dāng)前最新版本的數(shù)據(jù)然后再進(jìn)行更新飞蹂,所以經(jīng)常會(huì)有可重復(fù)讀的情況下,update 沖突的情況翻屈。
4陈哑、后記
1、事務(wù)不是在 begin transition 就開(kāi)始的伸眶,而是在第一個(gè) sql 語(yǔ)句開(kāi)始的惊窖。
2、在可重復(fù)讀的情況下厘贼,readview 實(shí)在事務(wù)開(kāi)始的時(shí)候(結(jié)合1)創(chuàng)建的界酒;而在讀已提交的情況下,readview 是在每次 select 查詢(xún)的時(shí)候重新生成的嘴秸。
3毁欣、在 innerdb 事務(wù)中,行鎖是在更新行的時(shí)候加上岳掐,但并不是馬上釋放凭疮,而是等事務(wù)結(jié)束的時(shí)候才釋放,這就是兩階段鎖協(xié)議串述。
4执解、至于 mvcc 能解決幻讀問(wèn)題,為啥還用鎖(快照讀用 mvcc纲酗,當(dāng)前讀用間隙鎖)衰腌,看這篇回答:https://www.zhihu.com/question/372905832