前言
迎面走來了一個風塵仆仆的身穿格子衫的男子赏僧,手里拿著一個MacBook Pro大猛,看著那稀少的發(fā)量扭倾,和那從容淡定的眼神。
我心里一顫挽绩,我去膛壹,這是架構(gòu)師,架構(gòu)師來面我技術(shù)面唉堪,我心里頓時不淡定了模聋,表面很穩(wěn)實則心里慌得一批。
果然唠亚,他手里拿著我的簡歷链方,快速的掃了一下,然后用眼角余光看了一下我灶搜,上來就開問祟蚀。
Mysql事務(wù)簡介
面試官: 看你簡歷上說精通Mysql優(yōu)化方法,你先來說說你對Mysql的事務(wù)的了解吧割卖。
我心里喜了一下前酿,這個簡單啊,哥我可是北大(背大)的鹏溯,再來面試之前罢维,早就有準備的,二話不說丙挽,上去就是背肺孵。
我: 好的,數(shù)據(jù)庫的事務(wù)是指一組sql語句組成的數(shù)據(jù)庫邏輯處理單元颜阐,在這組的sql操作中悬槽,要么全部執(zhí)行成功,要么全部執(zhí)行失敗瞬浓。
我: 這里的一組sql操作初婆,舉個簡單又經(jīng)典的例子就是轉(zhuǎn)賬了,事務(wù)A中要進行轉(zhuǎn)賬猿棉,那么轉(zhuǎn)出的賬號要扣錢磅叛,轉(zhuǎn)入的賬號要加錢,這兩個操作都必須同時執(zhí)行成功萨赁,為了確保數(shù)據(jù)的一致性弊琴。
面試官: 剛才你提到了數(shù)據(jù)一致性,你知道事務(wù)的特性嗎杖爽?說說你的理解敲董。
ACID簡介
我: 在Mysql中事務(wù)的四大特性主要包含:原子性(Atomicity)紫皇、一致性(Consistent)、隔離性(Isalotion)腋寨、持久性(Durable)聪铺,簡稱為ACID
。
我: 原子性是指事務(wù)的原子性操作萄窜,對數(shù)據(jù)的修改要么全部執(zhí)行成功铃剔,要么全部失敗,實現(xiàn)事務(wù)的原子性查刻,是基于日志的Redo/Undo
機制键兜。
我: 一致性是指執(zhí)行事務(wù)前后的狀態(tài)要一致,可以理解為數(shù)據(jù)一致性穗泵。隔離性側(cè)重指事務(wù)之間相互隔離普气,不受影響,這個與事務(wù)設(shè)置的隔離級別有密切的關(guān)系佃延。
我: 持久性則是指在一個事務(wù)提交后现诀,這個事務(wù)的狀態(tài)會被持久化到數(shù)據(jù)庫中,也就是事務(wù)提交苇侵,對數(shù)據(jù)的新增赶盔、更新將會持久化到數(shù)據(jù)庫中。
我: 在我的理解中榆浓,原子性于未、隔離性、持久性都是為了保障一致性而存在的陡鹃,一致性也是最終的目的方妖。
心里暗自歡喜轩触,背完了县昂,平時背的多庇勃,面試就會說,幸好難不倒我脊阴。
ACID原理
面試官: 剛才你說原子性是基于日志的Redo/Undo
機制握侧,你能說一說Redo/Undo
機制嗎?
啊哈嘿期?我都說了什么品擎,不小心給自己埋了一顆大雷。不慌备徐,哥腦子里還有貨,假裝若有所思的停了幾十秒蜜猾,接著背振诬。
我: Redo/Undo機制比較簡單衍菱,它們將所有對數(shù)據(jù)的更新操作都寫到日志中赶么。
我: Redo log用來記錄某數(shù)據(jù)塊被修改后的值,可以用來恢復未寫入 data file 的已成功事務(wù)更新的數(shù)據(jù)梦碗;Undo log是用來記錄數(shù)據(jù)更新前的值蓖救,保證數(shù)據(jù)更新失敗能夠回滾。
我: 假如數(shù)據(jù)庫在執(zhí)行的過程中循捺,不小心崩了斩例,可以通過該日志的方式,回滾之前已經(jīng)執(zhí)行成功的操作念赶,實現(xiàn)事務(wù)的一致性恰力。
面試官: 可以舉一個場景,說一下具體的實現(xiàn)流程嗎踩萎?
我: 可以的香府,假如某個時刻數(shù)據(jù)庫崩潰董栽,在崩潰之前有事務(wù)A和事務(wù)B在執(zhí)行锭碳,事務(wù)A已經(jīng)提交勿璃,而事務(wù)B還未提交。當數(shù)據(jù)庫重啟進行 crash-recovery 時补疑,就會通過Redo log將已經(jīng)提交事務(wù)的更改寫到數(shù)據(jù)文件癣丧,而還沒有提交的就通過Undo log進行roll back。
事務(wù)隔離級別
面試官: 之前你還提到事務(wù)的隔離級別胁编,你能說一說嗎?
我: 可以的早直,在Mysql中事務(wù)的隔離級別分為四大等級,讀未提交(READ UNCOMMITTED)霞扬、讀提交 (READ COMMITTED)糕韧、可重復讀 (REPEATABLE READ)喻圃、串行化 (SERIALIZABLE)。
我: 讀未提交會讀到另一個事務(wù)的未提交的數(shù)據(jù)雀扶,產(chǎn)生臟讀問題肆汹,讀提交則解決了臟讀的,出現(xiàn)了不可重復讀浪册,即在一個事務(wù)任意時刻讀到的數(shù)據(jù)可能不一樣岗照,可能會受到其它事務(wù)對數(shù)據(jù)修改提交后的影響,一般是對于update的操作煞肾。
我: 可重復讀解決了之前不可重復讀和臟讀的問題嗓袱,但是由帶來了幻讀的問題渠抹,幻讀一般是針對inser操作。
我: 例如:第一個事務(wù)查詢一個User表id=100發(fā)現(xiàn)不存在該數(shù)據(jù)行奇颠,這時第二個事務(wù)又進來了放航,新增了一條id=100的數(shù)據(jù)行并且提交了事務(wù)。
我: 這時第一個事務(wù)新增一條id=100的數(shù)據(jù)行會報主鍵沖突荆几,第一個事務(wù)再select一下,發(fā)現(xiàn)id=100數(shù)據(jù)行已經(jīng)存在行拢,這就是幻讀诞吱。
面試官: 小伙子你能演示一下嗎?我不太會你能教教我嗎沼瘫?我電腦在這里握巢,你演示我看一看松却。
男人的嘴騙人的鬼晓锻,我信你個鬼,你這糟老頭子壞得很砚哆,出來裝X總是要還的躁锁,只能默默含淚把它敲完。
我: 首先創(chuàng)建一個User表搜立,最為一個測試表槐秧,測試表里面有三個字段,并插入兩條測試數(shù)據(jù)颠通。
CREATE TABLE User (
id INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
INSERT INTO `user` VALUES (1, 'zhangsan', 23);
INSERT INTO `user` VALUES (2, 'lisi', 20);
我: 在Mysql中可以先查詢一下他的默認隔離級別膀懈,可以看出Mysql的默認隔離級別是REPEATABLE-READ
。
我: 先來演示一下讀未提交乘客,先把默認的隔離級別修改為READ UNCOMMITTED
淀歇。
我: 他設(shè)置隔離級別的語句中set global transaction isolation level read uncommitted浪默,這里的global也可以換成session,global表示全局的纳决,而session表示當前會話阔加,也就是當前窗口有效。
我: 當設(shè)置完隔離級別后對于之前打開的會話胳喷,是無效的夭织,要重新打開一個窗口設(shè)置隔離級別才生效。
我: 然后是開啟事務(wù)讲竿,Mysql中開啟事務(wù)有兩種方式begin/start transaction
题禀,最后提交事務(wù)執(zhí)行commit膀捷,或者回滾事務(wù)rollback。
我: 在執(zhí)行begin/start transaction
命令江锨,它們并不是一個事務(wù)的起點糕篇,在執(zhí)行完它們后的第一個sql語句,才表示事務(wù)真正的啟動 挑豌。
我: 這里直接打開兩個新的窗口,同時開啟事務(wù)侯勉,在第一個窗口先update一個id=1的數(shù)據(jù)行name改為'非科班的科班'铝阐,執(zhí)行成功徘键。
我: 然后再第二個窗口執(zhí)行兩次的查詢,分別是窗口一update之前的查詢和update之后的查詢螟凭。
我: 第一個session產(chǎn)生的未提交的事務(wù)的狀態(tài)就會直接影響到第二sesison螺男,也就是臟讀纵穿。
我: 對于讀提交也是一樣的,開啟事務(wù)后汪拥,第一個事務(wù)先執(zhí)行查詢數(shù)據(jù)篙耗,然后第二個session執(zhí)行update操作宪赶,但是還沒有commit搂妻,這是第一個session再次select,數(shù)據(jù)并沒有改變邓厕,再第二個session執(zhí)行commit之后扁瓢,第一個session再次select就是改變后的數(shù)據(jù)了。
我: 這樣第一個事務(wù)的查詢結(jié)果就會收到第二事務(wù)的影響昧互,這個也就是產(chǎn)生不可重復讀的問題敞掘。
面試官: 小伙子你能畫一下他執(zhí)行的過程圖嗎?你講的我有點亂更扁,我還沒有徹底明白赫冬。
我心里一萬只什么馬在飛過,欲哭無淚竖哩,這面試官真難伺候脊僚,說時遲那時快,從左屁股兜抽出筆增淹,從右屁股兜拿出紙乌企,開始畫加酵。
我: 這個是讀提交的時間軸圖,讀未提交的時間軸圖冗澈,原理也一樣的陋葡,第二個select的時候數(shù)據(jù)就已經(jīng)改變了腐缤。
這是面試官拿過我的圖看了一點,微微的點了點頭岭粤,嘴角露出思思的笑意绍在,我想你這糟老頭子應(yīng)該不會再刁難我了吧雹有。
面試官: 嗯臼寄,你接著演示你的可重復讀吧吉拳。
我: 嗯,好的煤惩,然后就是可重復讀炼邀,和之前一樣的操作。
我: 將兩個session開啟為REPEATABLE READ
洛退,同時開啟事務(wù)兵怯,在第一個事務(wù)中先select腔剂,然后在第二個事務(wù)里面update數(shù)據(jù)行,可以發(fā)現(xiàn)即使第二個事務(wù)已經(jīng)commit袜漩,第一個事務(wù)再次select數(shù)據(jù)也還是沒有改變登渣,這就解決了不可重復讀的問題胜茧。
我: 這里有個不同的地方就是在Mysql中仇味,默認的不可重復讀個隔離級別也解決了幻讀的問題。
我: 從上面的演示中可以看出第一個事務(wù)中先select一個id=3的數(shù)據(jù)行廊遍,這條數(shù)據(jù)行是不存在的贩挣,返回Empty set,然后第二個事務(wù)中insert一條id=3的數(shù)據(jù)行并且commit卵迂,第一個事務(wù)中再次select的见咒,數(shù)據(jù)也好是沒有id=3的數(shù)據(jù)行。
我: 最后的串行化下翎,樣式步驟也是一樣的宝当,結(jié)果也和Mysql中默認的個可重復讀隔離級別的結(jié)果一樣,串行化的執(zhí)行流程相當于把事務(wù)的執(zhí)行過程變?yōu)轫樞驁?zhí)行俐东,我這邊就不再做演示了犬性。
我: 這四大等級從上到下腾仅,隔離的效果是逐漸增強,但是性能卻是越來越差鹤耍。
Mysql的鎖機制
面試官: 哦验辞?性能越來越差跌造?為什么會性能越來越差?你能說一說原因嗎陵珍?
哎呀违施,我這嘴,少說一句會死啊留潦,這下好了,這個得說底層實現(xiàn)原理了殖卑,從原來的假裝若有所思秆乳,變成了真正得若有所思屹堰。
我: 這個得從Mysq的鎖說起,在Mysql中的鎖可以分為分享鎖/讀鎖(Shared Locks)睦袖、排他鎖/寫鎖(Exclusive Locks) 荣刑、間隙鎖、行鎖(Record Locks)厉亏、表鎖爱只。
我: 在四個隔離級別中加鎖肯定是要消耗性能的,而讀未提交是沒有加任何鎖的窝趣,所以對于它來說也就是沒有隔離的效果训柴,所以它的性能也是最好的幻馁。
我: 對于串行化加的是一把大鎖,讀的時候加共享鎖仗嗦,不能寫儒将,寫的時候对蒲,加的是排它鎖,阻塞其它事務(wù)的寫入和讀取砰逻,若是其它的事務(wù)長時間不能寫入就會直接報超時蝠咆,所以它的性能也是最差的,對于它來就沒有什么并發(fā)性可言闸翅。
我: 對于讀提交和可重復讀菊霜,他們倆的實現(xiàn)是兼顧解決數(shù)據(jù)問題鉴逞,然后又要有一定的并發(fā)行,所以在實現(xiàn)上鎖機制會比串行化優(yōu)化很多液南,提高并發(fā)性勾徽,所以性能也會比較好捂蕴。
事務(wù)底層實現(xiàn)原理
我: 他們倆的底層實現(xiàn)采用的是MVCC(多版本并發(fā)控制)方式進行實現(xiàn)。
面試官: 你能先說一下先這幾個鎖的概念嗎涡匀?我不是很懂溉知,說說你的理解级乍。
我: 哦,好的甚淡,共享鎖是針對同一份數(shù)據(jù)捅厂,多個讀操作可以同時進行,簡單來說即讀加鎖贿堰,不能寫并且可并行讀啡彬;排他鎖針對寫操作,假如當前寫操作沒有完成纵搁,那么它會阻斷其它的寫鎖和讀鎖诡渴,即寫加鎖菲语,其它讀寫都阻塞 。
我: 而行鎖和表鎖眼耀,是從鎖的粒度上進行劃分的哮伟,行鎖鎖定當前數(shù)據(jù)行妄帘,鎖的粒度小,加鎖慢鬼廓,發(fā)生鎖沖突的概率小碎税,并發(fā)度高馏锡,行鎖也是MyISAM和InnoDB的區(qū)別之一杯道,InnoDB支持行鎖并且支持事務(wù) 。
我: 而表鎖則鎖的粒度大虐杯,加鎖快,開銷小,但是鎖沖突的概率大达舒,并發(fā)度低叹侄。
我: 間隙鎖則分為兩種:Gap Locks
和Next-Key Locks
趾代。Gap Locks會鎖住兩個索引之間的區(qū)間,比如select * from User where id>3 and id<5 for update禽捆,就會在區(qū)間(3飘哨,5)之間加上Gap Locks芽隆。
我: Next-Key Locks是Gap Locks+Record Locks形成閉區(qū)間鎖select * from User where id>=3 and id=<5 for update胚吁,就會在區(qū)間[3,5]之間加上Next-Key Locks。
面試官: 那Mysql中什么時候會加鎖呢孽拷?
我: 在數(shù)據(jù)庫的增蕉毯、刪乓搬、改、查中代虾,只有增进肯、刪、改才會加上排它鎖棉磨,而只是查詢并不會加鎖江掩,只能通過在select語句后顯式加lock in share mode或者for update來加共享鎖或者排它鎖。
面試官: 你在上面提到MVCC(多版本并發(fā)控制),你能說一說原理嗎环形?
我: 在實現(xiàn)MVCC時用到了一致性視圖策泣,用于支持讀提交和可重復讀的實現(xiàn)抬吟。
我: 在實現(xiàn)可重復讀的隔離級別萨咕,只需要在事務(wù)開始的時候創(chuàng)建一致性視圖,也叫做快照火本,之后的查詢里都共用這個一致性視圖危队,后續(xù)的事務(wù)對數(shù)據(jù)的更改是對當前事務(wù)是不可見的,這樣就實現(xiàn)了可重復讀钙畔。
我: 而讀提交茫陆,每一個語句執(zhí)行前都會重新計算出一個新的視圖,這個也是可重復讀和讀提交在MVCC實現(xiàn)層面上的區(qū)別擎析。
面試官: 那你知道快照(視圖)在MVCC底層是怎么工作的嗎簿盅?
我: 在InnoDB 中每一個事務(wù)都有一個自己的事務(wù)id,并且是唯一的揍魂,遞增的 桨醋。
我: 對于Mysql中的每一個數(shù)據(jù)行都有可能存在多個版本,在每次事務(wù)更新數(shù)據(jù)的時候愉烙,都會生成一個新的數(shù)據(jù)版本讨盒,并且把自己的數(shù)據(jù)id賦值給當前版本的row trx_id。
面試官: 小伙子你可以畫個圖我看看嗎步责?我不是很明白返顺。
我有什么辦法呢?完全沒辦法蔓肯,只能又從屁股兜里拿出筆和紙遂鹊,迅速的畫了起來,要是這次面試不過就血虧啊蔗包,浪費了我兩張紙和筆水秉扑,現(xiàn)在的筆和紙多貴啊,只能豁出去了调限。
我: 如圖中所示舟陆,假如三個事務(wù)更新了同一行數(shù)據(jù),那么就會有對應(yīng)的三個數(shù)據(jù)版本耻矮。
我: 實際上版本1秦躯、版本2并非實際物理存在的,而圖中的U1和U2實際就是undo log裆装,這v1和v2版本是根據(jù)當前v3和undo log計算出來的踱承。
面試官: 那對于一個快照來說倡缠,你知道它要遵循什么規(guī)則嗎?
我: 嗯茎活,對于一個事務(wù)視圖來說除了對自己更新的總是可見昙沦,另外還有三種情況:版本未提交的,都是不可見的载荔;版本已經(jīng)提交盾饮,但是是在創(chuàng)建視圖之后提交的也是不可見的;版本已經(jīng)提交身辨,若是在創(chuàng)建視圖之前提交的是可見的丐谋。
面試官: 假如兩個事務(wù)執(zhí)行寫操作,又怎么保證并發(fā)呢煌珊?
我: 假如事務(wù)1和事務(wù)2都要執(zhí)行update操作,事務(wù)1先update數(shù)據(jù)行的時候泌豆,先回獲取行鎖定庵,鎖定數(shù)據(jù),當事務(wù)2要進行update操作的時候踪危,也會取獲取該數(shù)據(jù)行的行鎖蔬浙,但是已經(jīng)被事務(wù)1占有,事務(wù)2只能wait贞远。
我: 若是事務(wù)1長時間沒有釋放鎖畴博,事務(wù)2就會出現(xiàn)超時異常 。
面試官: 這個是在update的where后的條件是在有索引的情況下吧蓝仲?
我: 嗯俱病,是的 。
面試官: 那沒有索引的條件下呢袱结?沒辦法快速定位到數(shù)據(jù)行呢亮隙?
我: 若是沒有索引的條件下,就獲取所有行垢夹,都加上行鎖溢吻,然后Mysql會再次過濾符合條件的的行并釋放鎖,只有符合條件的行才會繼續(xù)持有鎖果元。
我: 這樣的性能消耗也會比較大促王。
面試官: 嗯嗯
此時面試官看看手表一個多鐘已經(jīng)過去了,也已經(jīng)到了飯點時刻而晒,我想他應(yīng)該是肚子餓了蝇狼,不會繼續(xù)追問吧,兩人持續(xù)僵了三十秒欣硼,他終于開口了题翰。
面試官: 小伙子恶阴,現(xiàn)在時間也已經(jīng)到了飯點了,今天的面試就到此結(jié)束吧豹障,你回去等通知吧冯事。
我: 。血公。昵仅。。累魔。摔笤。。垦写。吕世。。