?我當(dāng)面試官,面你 MySQL 原理
時(shí)間:2021-11-08 17:01:46
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]言歸正傳,之前MySQL基礎(chǔ)篇的知識(shí)點(diǎn),小伙伴們有熟練掌握嗎?沒有的話趕緊來復(fù)習(xí)一下~我當(dāng)面試官,面你MySQL基礎(chǔ)覺得已經(jīng)沒有問題,我們就一起來進(jìn)行第二部分的學(xué)習(xí)吧——MySQL原理篇?;A(chǔ)篇主要是側(cè)重基礎(chǔ)知識(shí),原理篇是有一定基礎(chǔ)后的遞進(jìn),通過學(xué)習(xí)本篇,不僅可以進(jìn)一步了解MyS...
言歸正傳,之前MySQL基礎(chǔ)篇的知識(shí)點(diǎn),小伙伴們有熟練掌握嗎?沒有的話趕緊來復(fù)習(xí)一下~
- 我當(dāng)面試官,面你 MySQL 基礎(chǔ)
基礎(chǔ)篇主要是側(cè)重基礎(chǔ)知識(shí),原理篇是有一定基礎(chǔ)后的遞進(jìn),通過學(xué)習(xí)本篇,不僅可以進(jìn)一步了解MySQL的各項(xiàng)特性,還能為接下來的容災(zāi)調(diào)優(yōu)打下堅(jiān)實(shí)的基礎(chǔ)。
現(xiàn)在,就讓我們繼續(xù)跟隨阿柴進(jìn)行這場(chǎng)沉浸式面試吧。
ACID與隔離級(jí)別
那你先來說說MySQL的四種隔離級(jí)別吧。SQL標(biāo)準(zhǔn)定義了4類隔離級(jí)別,包括一些具體規(guī)則,用來限定事務(wù)之間的隔離性。
這四種級(jí)別分別是讀未提交、讀已提交、可重復(fù)讀、串型化。
讀未提交,顧名思義,就是可以讀到還沒有提交的數(shù)據(jù);讀已提交會(huì)讀到其它事務(wù)已經(jīng)提交的數(shù)據(jù);可重復(fù)讀確保了同一事務(wù)中,讀取同一條數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行;串型化通過強(qiáng)制事務(wù)排序,使其不可能相互沖突。
重點(diǎn)介紹下Repeatable Read吧。Repeatable Read就是可重復(fù)讀。它確保了在同一事務(wù)中,讀取同一條數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行。
它也是MyQL的默認(rèn)事務(wù)隔離級(jí)別,這種級(jí)別事務(wù)之間影響很小,通常已經(jīng)能夠滿足日常需要了。
說出四種隔離級(jí)別只是最低要求,能每一項(xiàng)具體去闡述特性就算過關(guān)。如果還能指出存在的問題、依賴的技術(shù),那么就是妥妥的加分了!
下面我們來聊聊InnoDB中ACID的實(shí)現(xiàn)吧,先說一下原子性是怎么實(shí)現(xiàn)的。事務(wù)要么失敗,要么成功,不能做一半。聰明的InnoDB,在干活兒之前,先將要做的事情記錄到一個(gè)叫undo log的日志文件中,如果失敗了或者主動(dòng)rollback,就可以通過undo log的內(nèi)容,將事務(wù)回滾。
那undo log里面具體記錄了什么信息呢?undo log屬于邏輯日志,它記錄的是sql執(zhí)行相關(guān)的信息。當(dāng)發(fā)生回滾時(shí),InnoDB會(huì)根據(jù)undo log的內(nèi)容做與之前相反的工作,使數(shù)據(jù)回到之前的狀態(tài)。。。
那持久性又是怎么實(shí)現(xiàn)的?持久性是用來保證一旦給客戶返回成功,數(shù)據(jù)就不會(huì)消失,持久存在。最簡(jiǎn)單的做法,是每次寫完磁盤落地之后,再給客戶返回成功。但如果每次讀寫數(shù)據(jù)都需要磁盤IO,效率就會(huì)很低。
為此,追求極致的InnoDB提供了緩沖。當(dāng)向數(shù)據(jù)庫(kù)寫入數(shù)據(jù)時(shí),會(huì)首先寫入緩沖池,緩沖池中修改的數(shù)據(jù)會(huì)定期刷新到磁盤中,這一過程稱為刷臟。
如果MySQL宕機(jī),那此時(shí)Buffer Pool中修改的數(shù)據(jù)不是丟失了嗎?Innodb引入了redo log來解決這個(gè)問題。當(dāng)數(shù)據(jù)修改時(shí),會(huì)先在redo log記錄這次操作,然后再修改緩沖池中的數(shù)據(jù),當(dāng)事務(wù)提交時(shí),會(huì)調(diào)用fsync接口對(duì)redo log進(jìn)行刷盤。
如果MySQL宕機(jī),重啟時(shí)可以讀取redo log中的數(shù)據(jù),對(duì)數(shù)據(jù)庫(kù)進(jìn)行恢復(fù)。由于redo log是WAL日志,也就是預(yù)寫式日志,所有修改先寫入日志,所以保證了數(shù)據(jù)不會(huì)因MySQL宕機(jī)而丟失,從而滿足了持久性要求。
按你所說,redo log 也需要寫磁盤,為什么不直接將數(shù)據(jù)寫磁盤呢?嗯。。。主要是有以下兩方面的原因:
1.對(duì)Buffer Pool進(jìn)行刷臟是隨機(jī)IO,因?yàn)槊看涡薷牡臄?shù)據(jù)位置隨機(jī),但寫redo log是追加操作,屬于順序IO;
2.刷臟是以數(shù)據(jù)頁(yè)為單位,MySQL默認(rèn)頁(yè)大小是16KB,一個(gè)Page上一個(gè)小修改都要整頁(yè)寫入,所以積累一些數(shù)據(jù)一并寫入會(huì)大大提升性能;而redo log中只包含真正需要寫入的部分,無效IO比較少。
redo log是持久性的核心,WAL的思路也是持久化的常見解決方式,只有先落地了,才能應(yīng)對(duì)后續(xù)的各種異常。
那隔離性怎么實(shí)現(xiàn)呢?MySQL能支持Repeatable Read這種高隔離級(jí)別,主要是鎖和MVCC一起努力的結(jié)果。
我先說鎖吧。事務(wù)在讀取某數(shù)據(jù)的瞬間,必須先對(duì)其加行級(jí)共享鎖,直到事務(wù)結(jié)束才釋放;事務(wù)在更新某數(shù)據(jù)的瞬間,必須先對(duì)其加行級(jí)排他鎖,直到事務(wù)結(jié)束才釋放;
為了防止幻讀,還會(huì)有間隙鎖進(jìn)行區(qū)間排它鎖定。
然后是MVCC,多版本并發(fā)控制,主要是為了實(shí)現(xiàn)可重復(fù)讀,雖然鎖也可以,但是為了更高性能考慮,使用了這種多版本快照的方式。
因?yàn)槭强煺眨砸粋€(gè)事務(wù)針對(duì)同一條Sql查詢語句的結(jié)果,不會(huì)受其它事務(wù)影響。
索引原理
索引的底層實(shí)現(xiàn)是什么?用的B 樹,它是一個(gè)N叉排序樹,每個(gè)節(jié)點(diǎn)通常有多個(gè)子節(jié)點(diǎn)。節(jié)點(diǎn)種類有普通節(jié)點(diǎn)和葉子節(jié)點(diǎn)。根節(jié)點(diǎn)可能是一個(gè)葉子節(jié)點(diǎn), 也可能是個(gè)普通節(jié)點(diǎn)。
B 樹
那MySQL為什么用樹做索引?一般而言,能做索引的,要么Hash,要么樹,要么就是比較特殊的跳表。Hash不支持范圍查詢,跳表不適合這種磁盤場(chǎng)景,而樹支持范圍查詢,且多種多樣,很多樹適合磁盤存儲(chǔ)。所以MySQL選擇了樹來做索引。
那你能說說為什么是B 樹,而不是平衡二叉樹、紅黑樹或者B-樹嗎?平衡二叉樹追求絕對(duì)平衡,條件比較苛刻,實(shí)現(xiàn)起來比較麻煩,每次插入新節(jié)點(diǎn)之后需要旋轉(zhuǎn)的次數(shù)不能預(yù)知。
同時(shí),B 樹優(yōu)勢(shì)在于每個(gè)節(jié)點(diǎn)能存儲(chǔ)多個(gè)信息,這樣深度比平衡二叉樹會(huì)淺很多,減少數(shù)據(jù)查找的次數(shù)。
平衡二叉樹
紅黑樹放棄了追求完全平衡,只追求大致平衡,在與平衡二叉樹的時(shí)間復(fù)雜度相差不大的情況下,保證每次插入最多只需要三次旋轉(zhuǎn)就能達(dá)到平衡,實(shí)現(xiàn)起來也更為簡(jiǎn)單。
但是紅黑樹多用于內(nèi)部排序,即全放在內(nèi)存中,而B 樹多用于外存上時(shí),B 也被稱為一個(gè)磁盤友好的數(shù)據(jù)結(jié)構(gòu)。
同時(shí),紅黑樹和平衡二叉樹有相同缺點(diǎn),即每個(gè)節(jié)點(diǎn)存儲(chǔ)一個(gè)關(guān)鍵詞,數(shù)據(jù)量大時(shí),導(dǎo)致它們的深度很深,MySQL每次讀取時(shí)都會(huì)消耗大量IO。
紅黑樹
那B 樹相比B-樹有什么優(yōu)點(diǎn)呢?哈哈,我覺得這就屬于同門師兄較勁兒了。B 樹非葉子節(jié)點(diǎn)只存儲(chǔ)key值,而B-樹存儲(chǔ)key值和data值,這樣B 樹的層級(jí)更少,查詢效率更高;
MySQL進(jìn)行區(qū)間訪問時(shí),由于B 樹葉子節(jié)點(diǎn)之間用指針相連,只需要遍歷所有的葉子節(jié)點(diǎn)即可,而B-樹則需要中序遍歷一遍。
B-樹
這類選型問題其實(shí)很深,要深刻理解為什么要用B 樹、B 樹有哪些競(jìng)爭(zhēng)對(duì)手。換句話說,也就是要了解,哪些數(shù)據(jù)結(jié)構(gòu)能做索引。如果能答出哈希表、樹、跳表這三大類,就說明確實(shí)有自己的深入思考,這部分知識(shí)點(diǎn)學(xué)透了,也是加分項(xiàng)。
接下來講講聚簇索引和二級(jí)索引吧。聚簇索引是主鍵上的索引,二級(jí)索引是非主鍵字段的索引。這兩者相同點(diǎn)是都是基于B 樹實(shí)現(xiàn)。
區(qū)別在于,二級(jí)索引的葉子結(jié)點(diǎn)只存儲(chǔ)索引本身內(nèi)容,以及主鍵ID,聚簇索引的葉子結(jié)點(diǎn),會(huì)存儲(chǔ)完整的行數(shù)據(jù)。在一定程度上,可以說二級(jí)索引就是主鍵索引的索引。
一般來說,面試官讓介紹兩個(gè)名詞或者概念,潛臺(tái)詞就是要我們說清楚兩者的相同點(diǎn)、不同點(diǎn),說清楚了就過關(guān)。如果有些自己的總結(jié)性思考,比如在上面的對(duì)話中,阿柴回答出二級(jí)索引是主鍵索引的索引,這樣就會(huì)讓面試官眼前一亮。
鎖
下面講講MySQL鎖的分類吧。MySQL從鎖粒度粒度上講,有表級(jí)鎖、行級(jí)鎖。從強(qiáng)度上講,又分為意向共享鎖、共享鎖、意向排它鎖和排它鎖。
鎖模式的兼容情況
那select操作會(huì)加鎖嗎?對(duì)于普通select語句,InnoDB 不會(huì)加任何鎖。但是select語句,也可以顯示指定加鎖。有兩種模式,一種是LOCK IN SHARE MODE是加共享鎖,還有Select ... for updates是加排它鎖。
什么情況下會(huì)發(fā)生死鎖?嗯。。。比如事務(wù)A鎖住了資源1,然后去申請(qǐng)資源2,但事務(wù)B已經(jīng)占據(jù)了資源2,需要資源1,誰都不退讓,就死鎖了。對(duì)于MySQL,最常見的情況,就是資源1、資源2分別對(duì)應(yīng)一個(gè)排它鎖。
那間隙鎖你有了解么?間隙鎖就是對(duì)索引行進(jìn)行加鎖操作,不僅鎖住其本身,還會(huì)鎖住周圍鄰近的范圍區(qū)間。間隙鎖的目的是為了解決幻影讀,但也因此帶來了更大的死鎖隱患。
比如,一個(gè)任務(wù)表里面有個(gè)狀態(tài)字段,是一個(gè)非唯一索引,有一個(gè)任務(wù)id,是唯一索引。
一個(gè)sql將狀態(tài)處于執(zhí)行中的任務(wù)設(shè)置為等待中,另一個(gè)sql正好通過任務(wù)id更新在范圍內(nèi)的一條任務(wù)信息。那么因?yàn)槭窃诓煌饕渔i的,所以都能成功。但是最后去更新主鍵數(shù)據(jù)的時(shí)候,就會(huì)死鎖。
介于篇幅,其中的一些知識(shí)點(diǎn),比如MVCC,并未擴(kuò)展出來深度闡述,建議大家可以看看我往期的數(shù)據(jù)庫(kù)文章。
- 索引為什么能提高查詢效率
- 事務(wù)、事務(wù)隔離級(jí)別和MVCC
- MySQL 全局鎖、表級(jí)鎖、行級(jí)鎖
- MySQL 到底是怎么加行鎖的?
- 林哥,幻讀是怎么被解決的?
- 完蛋,公司被一條 update 語句干趴了!