對于后端開發(fā)來說穴豫,數(shù)據(jù)庫是我們?nèi)粘i_發(fā)中幾乎都會使用到的渤滞。而且對于許多大型應用來說渔肩,往往數(shù)據(jù)庫就是限制其性能的瓶頸所在因俐。在以前的大多數(shù)時間里面,對數(shù)據(jù)庫的認知周偎,始終停留在一個比較淺的層面里抹剩。遂決定翻閱相關的書籍、博客和官方文檔蓉坎,讓自己對數(shù)據(jù)庫有一個全面的了解澳眷。
MySQL架構
以下是MySQL大體的組件結構
如果只關心和聚焦于客戶端向MySQL發(fā)送一條sql語句大致主要會涉及到的Server層的組件,可以查看下圖:
上面這張圖的箭頭標識和注釋也說得比較清楚了蛉艾。如果想要更加詳細的說明钳踊,可以自行查閱相關文獻資料。需要說明的是在MySQL8.0中勿侯,已經(jīng)將查詢緩存整個去掉了拓瞪。
MySQL內(nèi)置存儲引擎介紹
上圖介紹的MySQL架構大致可以分成Server層和存儲引擎層的。MySQL提供的基于插件式的存儲引擎助琐,使得我們可以根據(jù)不同的需要選擇不同的引擎祭埂,甚至是在同一個schema中的不同表,也可以使用不同的存儲引擎兵钮。而實際的數(shù)據(jù)蛆橡,也是存儲在存儲引擎中的。不同的存儲引擎的架構和對數(shù)據(jù)的組織方式也有所不同矢空。正是這些不同航罗,決定了這些存儲引擎提供了不同的特性和功能。
內(nèi)置存儲引擎
我們可以使用show engines來查看當前mysql內(nèi)置了哪些存儲引擎
三種常見的存儲引擎區(qū)別及介紹
InnoDB
InnoDB從5.1版本以后屁药,已經(jīng)取代了MyISAM粥血,成為MySQL默認的存儲引擎了柏锄。可以從上表上看到复亏,InnoDB是唯一支持事務趾娃、XA和Savepoints的內(nèi)置存儲引擎。同時缔御,它還支持行鎖抬闷、外鍵約束等。InnoDB表基于聚簇索引建立耕突,并且采用MVCC來支持高并發(fā)笤成,同時實現(xiàn)了ANSI SQL92定義的四種隔離級別,并在引擎內(nèi)部實現(xiàn)了redo log和undo log眷茁。這些特性組合在一起炕泳,使得InnoDB成為了一個適合處理大量數(shù)據(jù)的高性能事務引擎。對于DBA來說上祈,結合server層的bin log組成的日志系統(tǒng)機制培遵,使得使用InnoDB作為數(shù)據(jù)存儲引擎的數(shù)據(jù)庫具備安全的崩潰恢復能力和快速穩(wěn)定的復制性能,這些都是其它存儲引擎所不具備的登刺。所以在Oracle收購MySQL以后籽腕,沒有了版權問題,InnoDB就毫無爭議地成為了MySQL的默認存儲引擎了纸俭。
MyISAM
在MySQL5.1及之前的版本皇耗,MyISAM都是默認的存儲引擎。雖然不支持事務掉蔬、不支持行級鎖廊宪,崩潰后無法安全恢復。但是還MyISAM還是提供了許多其它的特性女轿,包括全文索引箭启、壓縮、空間函數(shù)(GIS)等蛉迹。這些特性在某些場景下的性能是高于其它存儲引擎的傅寡,比如需要存儲和批量查詢歸檔日志數(shù)據(jù),MyISAM引擎能提供較高的處理效率北救。
Memory
Memory存儲引擎將表中的數(shù)據(jù)存儲到內(nèi)存中荐操,為查詢和引用其他表數(shù)據(jù)提供快速訪問。因為是存在內(nèi)存中珍策,所以數(shù)據(jù)訪問的速度一般也要快于其它存儲引擎托启,同時Memory支持Hash索引,因此在單值查找的速度非橙林妫快屯耸。同時拐迁,MySQL在執(zhí)行查詢的過程中需要使用臨時表來保存中間結果,內(nèi)部使用的臨時表就是Memory表(如果結果集大小超出Memory表的限制疗绣,則會轉換成MyISAM表)线召。
使用哪一種引擎需要靈活選擇,一個數(shù)據(jù)庫中多個表可以使用不同引擎以滿足各種性能和實際需求多矮,使用合適的存儲引擎缓淹,將會提高整個數(shù)據(jù)庫的性能
功 能 | MYISAM | Memory | InnoDB |
---|---|---|---|
存儲限制 | 256TB | RAM | 64TB |
支持哈希索引 | No | Yes | No |
支持全文索引 | Yes | No | No |
支持數(shù)索引 | Yes | Yes | Yes |
支持數(shù)據(jù)緩存 | No | N/A | Yes |
支持外鍵 | No | No | Yes |
InnoDB內(nèi)存/磁盤結構及存儲邏輯結構
InnoDB總體架構
上面這張圖是InnoDB存儲引擎在內(nèi)存和磁盤上的對應結構。
這里分別取兩個最常操作的update和select操作大致描述一下內(nèi)部的流轉機制(在默認的可重復讀級別下, 同時略去了所有有關加鎖釋放鎖的操作):
1.select * from xxx where id=1 語句:
(1)引擎接收到執(zhí)行計劃塔逃,會創(chuàng)建一個trx_id讯壶。
(2)查詢是否在這個內(nèi)存中,如果在患雏,則返回行鹏溯。如果在change buffer中或者不在內(nèi)存中,則從磁盤中讀入內(nèi)存(在change buffer中還要涉及merge操作更新內(nèi)存中的數(shù)據(jù))淹仑。引擎層拿到數(shù)據(jù),返回給到server層的執(zhí)行器肺孵。
(3)如果Innodb發(fā)現(xiàn)某二級索引被頻繁訪問匀借,會對該索引上創(chuàng)建一個哈希索引。下次同樣的查詢過來平窘,會直接走這個自適應哈希索引吓肋。
2.update xxx set xxx where id=1 語句:
(1)引擎接收到執(zhí)行計劃,會為該事務創(chuàng)建一個trx_id瑰艘。
(2)查詢是否在這個內(nèi)存中是鬼,如果在,則返回行紫新。如果在change buffer中或者不在內(nèi)存中均蜜,則從磁盤中讀入內(nèi)存(在change buffer中還要涉及merge操作更新內(nèi)存中的數(shù)據(jù))。引擎層拿到數(shù)據(jù)芒率,返回給到server層的執(zhí)行器囤耳。
(3)執(zhí)行器更新這行的相關列數(shù)據(jù),再通過API接口調(diào)用引擎層偶芍,更新修改后的行數(shù)據(jù)更新到內(nèi)存中充择。同時寫入redo log,此時處于prepare階段匪蟀。
(4)引擎層告知執(zhí)行器已經(jīng)已經(jīng)執(zhí)行完成椎麦,隨時可以提交事務。
(5)server層的執(zhí)行器將該操作寫入bin log
(6)執(zhí)行期通過API接口告知引擎提交事務材彪,引擎把剛剛寫入的redo log標記為commit狀態(tài)观挎,更新完成琴儿。(兩階段提交)
上面就是5.5版本下的文件組織,可以看到键兜,每個schema都有一個文件夾凤类,文件夾里面有 db.opt和*.frm格式的文件。db.opt存放的是字符集和字符集排序規(guī)則信息(字符文件普气,可以打開)谜疤,frm文件存的是表結構等信息,官方同時也給出說明现诀,frm里的信息和InnoDB數(shù)據(jù)字典有所重疊夷磕,是出于歷史遺留原因(參見)。另外如果你沒有設置innodb_file_per_tale=ON那么所有的數(shù)據(jù)文件都將存儲在ibdata1文件中仔沿。所以這個文件會隨著數(shù)據(jù)的增長而增長坐桩。同時我們也可以看到,InnoDB結構圖中5.7相對與5.5最大的變化封锉,就是對于ibdata1的拆分绵跷,它把原本共享表空間,undo表空間和臨時表空間從ibdata1中分了出來成福。好處當然就是結構更加清晰碾局,更加方便獨立管理,不會出現(xiàn)之前臨時表空間不再使用釋放了奴艾,ibdata1文件還是那么大等情況净当。
InnoDB的數(shù)據(jù)邏輯結構
從上面InnoDB的架構圖里面的右半部分可以知道,無論是索引還是數(shù)據(jù)蕴潦,InnoDB都把它們存在.idb后綴(或者ibdata1)的文件中像啼。而在MyISAM中,索引和數(shù)據(jù)是分別存儲在MYI和MYD文件中的潭苞。
下圖是InnoDB的數(shù)據(jù)組織形式
從圖中可以看出忽冻,InnoDB數(shù)據(jù)其實就是保存在聚簇索引的葉子節(jié)點中的,并且按照主鍵列順序存儲在數(shù)據(jù)文件中的萄传。
相比之下甚颂,MyISAM的數(shù)據(jù)則是按照插入的順序存儲在磁盤上的,其索引的葉子節(jié)點存儲的是可以定位到實際數(shù)據(jù)的行號(或者是可以找到其物理位置的地址秀菱,這里隱藏了頁的物理細節(jié))振诬。
這兩種數(shù)據(jù)組織形式,使得下面兩種引擎有如下區(qū)別:
1.由于使用聚簇索引衍菱,所以無法同時把數(shù)據(jù)行存放在兩個地方赶么,所以一個表只能有一個聚簇索引。而InnoDB的二級索引的葉子節(jié)點也只能存儲聚簇索引上的主鍵值脊串,從而導致二級索引(除覆蓋索引以外)在查詢數(shù)據(jù)的時候需要回表辫呻。
2.也由于MyISAM的索引不存在聚簇索引清钥,葉子節(jié)點存儲的是實際數(shù)據(jù)的行號,所以對MyISAM而言放闺,主鍵索引和其它索引一樣祟昭,不存在定位實際數(shù)據(jù)塊上的性能差異。
這里有一個有意思的問題怖侦,如果InnoDB的二級索引的葉子節(jié)點和MyISAM一樣篡悟,存儲的是可以直接找到實際數(shù)據(jù)的行號,那豈不是可以避免了回表的問題匾寝。我個人感覺確實是這樣的搬葬。但是那樣會存在一個問題,那就是行鎖和間隙鎖的加鎖問題艳悔。我們知道急凰,InnoDB的行鎖其實質(zhì)就是加在索引上面加的鎖,只要訪問到該索引猜年,就會在對應的索引上面加鎖(包含回表的加鎖)如果不回表的話抡锈,那么不同索引上面加鎖并且相互獨立,那么行鎖和間隙鎖就毫無意義了乔外。反過來說企孩,InnoDB為什么會做一個回表這樣的邏輯,其實是在犧牲部分二級索引定位數(shù)據(jù)頁的性能袁稽,來換取更細粒度的鎖帶來的顯著性能提升。另外擒抛,如果在二級索引上存儲的是實際數(shù)據(jù)的行號推汽,那在數(shù)據(jù)頁調(diào)整的時候,也要對這些二級索引進行更新歧沪,這同時也會導致寫性能的下降歹撒。
InnoDB的行鎖和間隙鎖
前面有提到,InnoDB的鎖是加在索引上的诊胞,行鎖的引入減少了鎖競爭的情況暖夭,從而提高了并發(fā)度。在不同隔離級別的不同語句下撵孤,加鎖情況也是不一樣的迈着。從上圖可以知道,有一些查詢甚至不需要加鎖邪码,通過基于MVCC實現(xiàn)的一致性讀就可以達到對應的隔離級別裕菠,這里又進一步提高了并發(fā)度。
總結
其實當我們大概了解了InnoDB架構組件中各個組件的作用闭专,以及其數(shù)據(jù)存儲的邏輯結構奴潘。也就大概明白了為什么InnoDB提供了這么多其它存儲引擎不能提供的相關特性旧烧。例如:
1.redo log及對應的兩階段提交協(xié)議的引入,使得引擎可以提供在系統(tǒng)崩潰的時候提供安全崩潰恢復機制画髓。
2.undo log機制的引入掘剪,每一事務都有一個單調(diào)遞增的trx_id,使得Innodb可以基于MVCC對相關的事務做一致性讀(或者稱為快照讀)。
3.InnoDB基于聚簇索引的數(shù)據(jù)組織形式奈虾,多數(shù)情況下通過行鎖夺谁,間隙鎖和快照讀實現(xiàn)了四種隔離級別。這種方式相比與直接加表鎖愚墓,性能更高予权,更加適合高并發(fā)場景。
通過根據(jù)其作用推出它能提供的特性浪册,反過來也加深我們對各個組件的作用的理解扫腺。同時,通過思考這些組件的設計思想來實現(xiàn)對應的特性村象,這本身就是一個很有趣的過程笆环。