《從0開(kāi)始打造一個(gè)最小系統(tǒng)的數(shù)據(jù)庫(kù)》要點(diǎn):
本文介紹了從0開(kāi)始打造一個(gè)最小系統(tǒng)的數(shù)據(jù)庫(kù),希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
本篇趟個(gè)雷,把數(shù)據(jù)庫(kù)納入到輪子中,在我看來(lái),數(shù)據(jù)庫(kù)比輪子復(fù)雜多了,是一個(gè)和操作系統(tǒng)差不多復(fù)雜度的東西,所以才能通過(guò)一個(gè)Oracle養(yǎng)活一家全球50強(qiáng)的公司.本文為后端輪子系列的第一篇,我們先來(lái)談?wù)勅绾卧靷€(gè)數(shù)據(jù)庫(kù)吧.
關(guān)系型數(shù)據(jù)庫(kù)(Relational Database)是一個(gè)偉大的發(fā)明,一般的數(shù)據(jù)庫(kù)理論,大概會(huì)分成以下三個(gè)部分.
我們發(fā)現(xiàn),上面三個(gè)大的部分都是數(shù)據(jù)庫(kù)的理論知識(shí),其實(shí)并沒(méi)有人告訴我們?cè)趺磥?lái)用代碼實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù),因?yàn)榭茖W(xué)家們認(rèn)為實(shí)現(xiàn)它并不重要,那是工程師要考慮的事情,too simple,科學(xué)家只負(fù)責(zé)搞出理論,反正我們也不是科學(xué)家,那么我們就來(lái)做做工程師吧.
既然是工程師,首先想到的就是如何來(lái)實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)了,一個(gè)標(biāo)準(zhǔn)的數(shù)據(jù)庫(kù)主要會(huì)包含以下幾個(gè)大的模塊.
雖然看起來(lái)好像挺簡(jiǎn)單,就是這么三層,但是實(shí)際的數(shù)據(jù)庫(kù)是非常非常復(fù)雜的,除了這些以外還有很多其他模塊,比如用戶(hù)權(quán)限管理、緩存模塊、日志模塊、備份模塊等等,大家可以仔細(xì)去看看innoDB的書(shū)籍或者innoDB的代碼,光一個(gè)binlog就特別麻煩.
其實(shí)要保存數(shù)據(jù),搜索系統(tǒng)也能保存數(shù)據(jù),而且檢索起來(lái)更快,并且兩者的底層數(shù)據(jù)結(jié)構(gòu)其實(shí)差別不是很大,但為什么用數(shù)據(jù)庫(kù)呢?因?yàn)閿?shù)據(jù)庫(kù)的核心是可靠,這個(gè)可靠就是考數(shù)據(jù)庫(kù)的引擎層來(lái)保證的,完整的binlog記錄,崩潰后完整的重放機(jī)制,數(shù)據(jù)雙寫(xiě),內(nèi)存數(shù)據(jù)定時(shí)刷新到磁盤(pán),所有的這些都是為了保證數(shù)據(jù)的可靠,不會(huì)丟失數(shù)據(jù).
而上面說(shuō)的每一個(gè)功能,都能單獨(dú)的寫(xiě)一篇長(zhǎng)文,所以說(shuō)要實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)其實(shí)是很麻煩的,因?yàn)闉榱俗龅娇煽?必然會(huì)有很多冗余的數(shù)據(jù)或者冗余的操作來(lái)保證可靠,但作為一個(gè)成熟的產(chǎn)品,還需要考慮到產(chǎn)品的性能,所以,如何既可靠又性能優(yōu)良,就變成了一個(gè)衡量數(shù)據(jù)庫(kù)好壞的標(biāo)準(zhǔn),當(dāng)然,在這兩點(diǎn)上,目前沒(méi)人能干過(guò)Oracle了.
數(shù)據(jù)庫(kù)如此之復(fù)雜,我們?nèi)绾螌?duì)它進(jìn)行瘦身 ,來(lái)實(shí)現(xiàn)一個(gè)最小的數(shù)據(jù)庫(kù)系統(tǒng)呢?我們可以從另外一個(gè)角度想想,就是我們拿數(shù)據(jù)庫(kù)是干什么的?那就是存儲(chǔ)和查詢(xún)數(shù)據(jù),如果這么來(lái)想的話,就能簡(jiǎn)單不少.
首先,我們知道數(shù)據(jù)庫(kù)最重要的功能就是存儲(chǔ)數(shù)據(jù),那么底層的存儲(chǔ)部分是不能少的;其次,存儲(chǔ)的數(shù)據(jù)要提供查詢(xún)功能,不然存了就沒(méi)意義了,這也是不能少的;第三,需要提供一個(gè)對(duì)外的接口可以和用戶(hù)交互,不然就既不能存也不能查了.
所以,一個(gè)最最基本的數(shù)據(jù)庫(kù)至少應(yīng)該包含數(shù)據(jù)層,查詢(xún)層(引擎層)和UI(用戶(hù)接口)層三層,那么我們就用幾個(gè)簡(jiǎn)單的文件來(lái)實(shí)現(xiàn)這三層,完成一個(gè)最小的數(shù)據(jù)庫(kù)吧.
數(shù)據(jù)庫(kù)的基本單位是列,再上一級(jí)的基本單位就是表了,而且我們?cè)诮ū淼臅r(shí)候都會(huì)指定列的名稱(chēng)、類(lèi)型、長(zhǎng)度這三個(gè)最基本的屬性,如果所有列都有這三個(gè)屬性,那么其實(shí)我們是知道每一行數(shù)據(jù)最多有多少字節(jié)的.所以,我們可以設(shè)定沒(méi)一行數(shù)據(jù)的長(zhǎng)度都是定長(zhǎng)的,那么整個(gè)表的長(zhǎng)度也是定長(zhǎng)的了,這樣查詢(xún)的時(shí)候可以根據(jù)行的長(zhǎng)度進(jìn)行快速定位數(shù)據(jù),所以,我們的最底層數(shù)據(jù)就是一個(gè)定長(zhǎng)的表格了,每一列存儲(chǔ)的時(shí)候就像下面這樣,然后有個(gè)meta信息來(lái)存儲(chǔ)列的屬性:
這個(gè)看上去很簡(jiǎn)單吧?也容易實(shí)現(xiàn)吧,其實(shí)很多數(shù)據(jù)庫(kù)也基本上確實(shí)是這么實(shí)現(xiàn)的,并不難理解吧?稍微注意一下的是每一列存儲(chǔ)的時(shí)候,每個(gè)字段的前四個(gè)字節(jié)保存的是這個(gè)字段的實(shí)際長(zhǎng)度,然后才是字段的實(shí)際內(nèi)容,如果長(zhǎng)度小于建表時(shí)的設(shè)定長(zhǎng)度,那么有一部分空間是浪費(fèi)掉的,雖然是浪費(fèi)了,但還是值得的,因?yàn)榭梢宰尣樵?xún)的時(shí)候省不少事.
這么下來(lái),每行記錄就是一個(gè)定長(zhǎng)的,而一個(gè)數(shù)據(jù)庫(kù)的表就是一個(gè)二進(jìn)制文件了,但僅僅是這樣還是不夠的,因?yàn)檫@樣結(jié)構(gòu),無(wú)論什么查詢(xún)都需要掃描全表,依次進(jìn)行判斷,而我們?cè)诮ū淼臅r(shí)候都會(huì)建立索引,為了建立索引,我們還得實(shí)現(xiàn)一個(gè)B+樹(shù)來(lái)存儲(chǔ)索引,而B(niǎo)+樹(shù)基本上是所有數(shù)據(jù)庫(kù)的索引保存的數(shù)據(jù)結(jié)構(gòu),這里我們也有實(shí)現(xiàn).
總之,數(shù)據(jù)底層我們就用了一個(gè)定長(zhǎng)的二進(jìn)制文件和幾棵B+樹(shù),再加上一個(gè)meta信息文件來(lái)實(shí)現(xiàn)了一個(gè)數(shù)據(jù)庫(kù)的底層數(shù)據(jù)層,很簡(jiǎn)單哈,但基本上包括了數(shù)據(jù)庫(kù)真實(shí)的底層,雖然真正的數(shù)據(jù)庫(kù)比這復(fù)雜多了,但也跑不掉這幾個(gè)數(shù)據(jù)結(jié)構(gòu),整個(gè)看下來(lái),數(shù)據(jù)層的數(shù)據(jù)結(jié)構(gòu)大體上長(zhǎng)這樣子.
底層已經(jīng)有了,接下來(lái)就是上面的查詢(xún)層(引擎層)了.這里我沒(méi)用引擎兩個(gè)字,是因?yàn)樽钚?shù)據(jù)庫(kù)的實(shí)現(xiàn)上,實(shí)在算不上一個(gè)引擎系統(tǒng),我們實(shí)現(xiàn)最簡(jiǎn)單的基本查詢(xún)SQL(建表SQL、插入數(shù)據(jù)SQL、單表查詢(xún)SQL)的解析.在實(shí)際中,SQL的解析是一個(gè)異常復(fù)雜的工程,涉及到語(yǔ)法分析、預(yù)處理、優(yōu)化查詢(xún)等幾個(gè)大的部分,因?yàn)镾QL其實(shí)是一門(mén)編程語(yǔ)言,要解析一門(mén)編程語(yǔ)言,那么編譯原理那一套基本上都會(huì)用得到.
這里我們換條路子,因?yàn)橹粚?shí)現(xiàn)三種簡(jiǎn)單的SQL語(yǔ)句,那么我們直接用正則和字符串的匹配來(lái)對(duì)SQL進(jìn)行解析,解析完成以后變成一個(gè)個(gè)數(shù)據(jù)層的對(duì)外接口,建表和插入數(shù)據(jù)都比較簡(jiǎn)單,解析了SQL以后直接調(diào)用上面的第一和第二接口就行了.
數(shù)據(jù)查詢(xún)的時(shí)候,對(duì)查詢(xún)SQL的WHERE之后的部分,用了個(gè)小算法,就是逆波蘭表達(dá)式來(lái)對(duì)WHERE之后的語(yǔ)句進(jìn)行解析,變成一個(gè)棧結(jié)構(gòu)來(lái)存儲(chǔ)查詢(xún)的內(nèi)容,然后通過(guò)彈棧的方式一個(gè)一個(gè)調(diào)用接口三,并且對(duì)結(jié)果進(jìn)行求交和求并的操作,最后得到結(jié)果以后,再依次調(diào)用接口四獲取最后的結(jié)果.如果對(duì)逆波蘭表達(dá)式不了解,那么請(qǐng)自行百度一下,很簡(jiǎn)單的,主要用在對(duì)四則運(yùn)算的優(yōu)先級(jí)的解析中.
查詢(xún)層的輸入輸出很簡(jiǎn)單,他對(duì)外實(shí)際上只提供一個(gè)接口.ExecSqlSentence( Sql ) string,都是字符串,輸入是一條條的SQL語(yǔ)句,輸出是數(shù)據(jù).
對(duì)于用戶(hù)的接口層就更加簡(jiǎn)單了,我們只需要提供一個(gè)TCP服務(wù)就行了,用分號(hào)來(lái)分割每次用戶(hù)的輸入,也就是說(shuō),我們telnet上我們這個(gè)數(shù)據(jù)庫(kù),然后輸入SQL,數(shù)據(jù)庫(kù)就會(huì)返回?cái)?shù)據(jù)了.
我在github上建立了一個(gè)新的工程叫SparrowSys,麻雀工程,意思很明顯,這是一個(gè)后端的麻雀,是最簡(jiǎn)單的后端輪子,目前我也已經(jīng)提交了一部分代碼,數(shù)據(jù)庫(kù)的還沒(méi)有寫(xiě)完,后面會(huì)補(bǔ)上的.
數(shù)據(jù)庫(kù)的部分在src下的SparrowDB里面,很明顯的看到里面有DataLayer,EngineLayer,NetLayer,對(duì)應(yīng)的就是上面的三層,每層里面有一到兩個(gè)文件,都很簡(jiǎn)單.目前DataLayer基本完成了,后面會(huì)把EngineLayer和NetLayer補(bǔ)上,后面的文章會(huì)說(shuō)說(shuō)使用,utils文件夾中是一些公共的東西,后面的其他輪子會(huì)用到的,比如B+樹(shù)就在utils里面.
目前這個(gè)工程里面東西不多,不建議看,后面我補(bǔ)全以后會(huì)說(shuō)明,歡迎大家提交你的實(shí)現(xiàn)來(lái)代替我的.接受任何pull request.
最近比較忙,沒(méi)時(shí)間寫(xiě)代碼,先放出文章,等代碼補(bǔ)充完整了再說(shuō)說(shuō)測(cè)試效果.點(diǎn)擊后面網(wǎng)址,可跳到github代碼庫(kù).https://github.com/wyh267/SparrowSys
來(lái)源:西加加語(yǔ)言 訂閱號(hào)(ID:XJJ267)
作者:吳堅(jiān)
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/4461.html