MySQL它不香嗎,為什么還要NoSQL?
NoSQL 現(xiàn)在非?;?,我看過(guò)的簡(jiǎn)歷里面十個(gè)有九個(gè)都寫(xiě)了熟悉 NoSQL,但是對(duì)于 NoSQL 背后的細(xì)節(jié)卻很少有人能講清楚,甚至連 NoSQL 里面的這個(gè) No 是什么意思都很多人搞錯(cuò)。
圖片來(lái)自 Pexels
這個(gè) No 并不是 Not 的意思,而是 Not Only 的縮寫(xiě)。不得不說(shuō)這個(gè)縮寫(xiě)實(shí)在是很坑爹,單從字面上應(yīng)該沒(méi)人能猜出來(lái)它是這個(gè)意思。
而且即使解讀成 Not Only SQL,還是有點(diǎn)云里霧里,不是很能精準(zhǔn)地 get 到它的點(diǎn)。
因?yàn)?SQL 的英文全寫(xiě)是 Structured Query Language,也就是結(jié)構(gòu)化查詢語(yǔ)言的意思。它可以認(rèn)為是一門(mén)特殊的編程語(yǔ)言,但“不僅僅是 SQL”是啥意思?
的確令人費(fèi)解,所以我們從字面意思上去理解是不行的,我們需要從實(shí)際應(yīng)用場(chǎng)景去理解。
SQL 的應(yīng)用場(chǎng)景是關(guān)系型數(shù)據(jù)庫(kù),比如我們常用的 Oracle、MySQL,這些就是關(guān)系型數(shù)據(jù)庫(kù)。
我們理解數(shù)據(jù)庫(kù)的時(shí)候,往往會(huì)從表的結(jié)構(gòu)入手去理解。數(shù)據(jù)庫(kù)當(dāng)中存儲(chǔ)的是一張張的表,表呢是一行行數(shù)據(jù)組成的,而每一行數(shù)據(jù)都有固定的字段。
我想這點(diǎn)大家應(yīng)該非常熟悉,即使沒(méi)有學(xué)過(guò)數(shù)據(jù)庫(kù)或者是像我這樣已經(jīng)還給老師的,應(yīng)該或多或少都有印象。
但是為什么它會(huì)被叫做關(guān)系型數(shù)據(jù)庫(kù),而不是表結(jié)構(gòu)數(shù)據(jù)庫(kù)呢?
因?yàn)樵跀?shù)據(jù)庫(kù)當(dāng)中,關(guān)系要比表結(jié)構(gòu)更重要。表結(jié)構(gòu)只是一種形式,而數(shù)據(jù)庫(kù)當(dāng)中核心的設(shè)計(jì)理念其實(shí)是關(guān)系。
這也是為什么我們學(xué)習(xí)數(shù)據(jù)庫(kù)的時(shí)候都需要從 ER 圖開(kāi)始,而不是上來(lái)就講數(shù)據(jù)庫(kù)使用的方法,或者是 SQL 語(yǔ)言的細(xì)節(jié)。
如果你想不明白這句話的含義,也沒(méi)有關(guān)系,我們先放一放,最后再回到這個(gè)話題來(lái)。
問(wèn)題來(lái)了,我們知道了常用的 SQL 數(shù)據(jù)庫(kù)是關(guān)系型數(shù)據(jù)庫(kù),那么 NoSQL 代表的數(shù)據(jù)庫(kù)又是什么呢?
關(guān)于 NoSQL 概念我至少看到了兩種說(shuō)法:
- 非關(guān)系型數(shù)據(jù)庫(kù)
- 文檔型數(shù)據(jù)庫(kù)
我個(gè)人在理解的時(shí)候覺(jué)得這兩種說(shuō)法都不是非常完美,但相比之下顯然是第二種更好,因?yàn)榈谝环N說(shuō)法完全沒(méi)有給我們提供任何信息。
文檔型數(shù)據(jù)庫(kù)這里的文檔,并不是我們常規(guī)理解的一篇文檔的含義,而是指的數(shù)據(jù)存儲(chǔ)的結(jié)構(gòu)和核心邏輯。
一個(gè)生動(dòng)的例子
和大多數(shù)技術(shù)上的概念一樣,NoSQL 也比較晦澀,很難單純用語(yǔ)言將它描述清楚。
所以我決定舉一個(gè)生動(dòng)活潑,大家都耳熟能詳?shù)睦?mdash;—就是萬(wàn)能的 X 寶。
下面是一張 X 寶當(dāng)中的商品詳情頁(yè)的圖(隨便選取,并非廣告,如有巧合,請(qǐng)付推廣費(fèi)):
這張圖大家應(yīng)該都很熟悉了,在我們平時(shí)的網(wǎng)上購(gòu)物的活動(dòng)當(dāng)中,一定見(jiàn)過(guò)了許多次。
它看起來(lái)有些眼花繚亂,我們把上面的內(nèi)容做個(gè)抽象和精簡(jiǎn),畫(huà)成一張草圖,它大概是這樣的(的確有些草率):
也就是說(shuō)我們把一個(gè)商品詳情頁(yè)展示的內(nèi)容大概分成了三個(gè)部分:
- 商品圖
- 商品的一些介紹說(shuō)明
- 用戶的評(píng)論
各大電商公司商品詳情頁(yè)的設(shè)計(jì)大同小異,也許有些細(xì)節(jié)不太一樣,但是整體上的模塊都相差不大。
為了簡(jiǎn)化問(wèn)題,我們就假設(shè)商品詳情頁(yè)需要關(guān)聯(lián)圖片信息、文字說(shuō)明和用戶評(píng)論這三張表。
其實(shí)這樣劃分不太科學(xué),比如文字介紹和商品圖可以都存在商品詳情頁(yè)的表中。
比如除了這些信息之外,還有商品的售賣(mài)信息,比如庫(kù)存、價(jià)格、當(dāng)前的優(yōu)惠、活動(dòng)等等,但是這些和我們最后的結(jié)論關(guān)系不大,可以簡(jiǎn)單這么理解。
根據(jù)上面的劃分方式,我們應(yīng)該根據(jù) itemID 去查詢商品的圖片、文字以及評(píng)論信息,這從表面上看當(dāng)然沒(méi)有問(wèn)題。
但實(shí)際上這是有問(wèn)題的,問(wèn)題在于這些數(shù)據(jù)都不是一對(duì)一的關(guān)系,而是一對(duì)多的關(guān)系。
比如頭部展示的圖片往往不止一張,文字說(shuō)明可能也不止一段,同樣用戶的評(píng)論可能也不止一條。這個(gè)問(wèn)題怎么解決呢?
你可能會(huì)想出辦法來(lái),這不難啊,我們?cè)?img 和 text 以及 comment 的表里都加入 itemID 這個(gè)字段,在我們查詢的時(shí)候通過(guò) itemID 進(jìn)行關(guān)聯(lián),不就 OK 了么?
這樣做當(dāng)然可以,假設(shè)你是負(fù)責(zé)這個(gè)項(xiàng)目的程序員,你做出了這個(gè)更新,成功上線了之后,產(chǎn)品又給你提了一個(gè)新的需求。
她說(shuō)我想要在文字介紹和用戶評(píng)論里面都展示圖片,雖然系統(tǒng)一開(kāi)始不是這么設(shè)計(jì)的,但是我不管,我就是需要,立刻馬上。
你翻了好一會(huì)白眼,冷靜了許久,想了想,終于想到了兩種方案:
- 第一個(gè)方案是在目前的圖片表上加上字段,用來(lái)判斷圖片的用途是詳情頁(yè)展示還是評(píng)論頁(yè)展示,把之后要加的文本介紹和評(píng)論頁(yè)中的圖片依然存在這張表上。
- 第二個(gè)方案是重新建新的表,專表專用,專門(mén)負(fù)責(zé)存放評(píng)論頁(yè)和說(shuō)明頁(yè)的圖片。
第一個(gè)方案的好處是我們不用建新的表,避免了表的冗余,如果每一個(gè)需求都需要建新的表,顯然對(duì)于后續(xù)的維護(hù)是一個(gè)巨大的負(fù)擔(dān)。
但是它的缺點(diǎn)是我們需要批量修正之前所有的數(shù)據(jù),因?yàn)橹暗臄?shù)據(jù)里沒(méi)有來(lái)源這個(gè)字段。
當(dāng)然你也可以不加這個(gè)字段,直接用 ID 區(qū)分,但是這是不符合規(guī)范的,而且必然會(huì)留下安全隱患。
第二個(gè)方案的優(yōu)點(diǎn)是操作簡(jiǎn)單,不需要變更之前的數(shù)據(jù),安全風(fēng)險(xiǎn)較小,但問(wèn)題是需要占用新的資源,利用率低。
因?yàn)橛行┰斍轫?yè)的圖片和頂部的圖片是可以共用的,這樣分開(kāi)存儲(chǔ)的話就需要存儲(chǔ)多份。
這兩個(gè)方案各有優(yōu)缺點(diǎn),似乎都還不錯(cuò),但坑爹的是它們都有一個(gè)共同的缺點(diǎn),就是都會(huì)增加目前系統(tǒng)和查詢的復(fù)雜度。
一個(gè)是要增加查詢時(shí)候傳入的字段,一個(gè)是要發(fā)起額外的查詢,不論選擇哪一個(gè),都會(huì)讓系統(tǒng)越來(lái)越復(fù)雜。
到了后來(lái),一個(gè)用戶請(qǐng)求傳來(lái),會(huì)帶動(dòng)數(shù)十個(gè)聯(lián)動(dòng)請(qǐng)求,才能拼裝出完整的數(shù)據(jù)。
現(xiàn)在最新版本的 X 寶的詳情頁(yè)商品介紹的部分一律用圖片展示,沒(méi)有了文字,也許背后就是受到這個(gè)問(wèn)題的驅(qū)動(dòng)。
我們回顧一下這個(gè)例子,為什么我們的查詢會(huì)很復(fù)雜,其實(shí)就和數(shù)據(jù)庫(kù)的核心理念有關(guān)。
關(guān)系型數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)是關(guān)系,在這個(gè)問(wèn)題當(dāng)中,我們一個(gè)詳情頁(yè)的查詢,需要查詢商品和圖片的關(guān)系,商品和說(shuō)明的關(guān)系,商品和評(píng)論的關(guān)系,評(píng)論和圖片的關(guān)系等等。
也就是說(shuō)我們最終看到的頁(yè)面,其實(shí)是這一系列關(guān)系擰在一起的結(jié)果,每一次查詢的背后都是一個(gè)關(guān)系分解再合并的過(guò)程,因此會(huì)非常復(fù)雜。
文檔型數(shù)據(jù)庫(kù)
我們剛才看了關(guān)系型數(shù)據(jù)庫(kù)在電商場(chǎng)景下的問(wèn)題,我們?cè)賮?lái)看下文檔型數(shù)據(jù)庫(kù)在同樣場(chǎng)景下的表現(xiàn)。
和關(guān)系型數(shù)據(jù)庫(kù)不同,文檔型數(shù)據(jù)庫(kù)存儲(chǔ)的核心是文檔。當(dāng)然這里的文檔指的不是我們通常意義上的文檔,而是 Json 或者是 XML 格式的數(shù)據(jù)。在目前的 NoSQL 數(shù)據(jù)庫(kù)當(dāng)中,Json 類型的數(shù)據(jù)更加常用一些。
我們還用剛才詳情頁(yè)的例子來(lái)看下在 NoSQL 數(shù)據(jù)庫(kù)當(dāng)中,這份數(shù)據(jù)是如何存儲(chǔ)的:
- {
- "itemID": 123,
- "itemName": "iPad Pro",
- "topImgs": ["imgs1.path", "imgs2.path"],
- "desc": [{"text": "iPad Pro", "img": ""}, {"text": "2020 new version", "img": "imgs1.path"}],
- "comments": [{"userName": "chengzhi", "comment": "good product", "imgs": ["imgs3.path", "img4.path"]}]
- }
你看,在文檔型數(shù)據(jù)庫(kù)當(dāng)中剛才復(fù)雜的、需要經(jīng)過(guò)多次查詢經(jīng)過(guò)一系列處理才可以擰到一起的數(shù)據(jù),我們通過(guò) itemID 一次查詢就可以獲取到了。
單純從使用上來(lái)說(shuō),它比關(guān)系型數(shù)據(jù)庫(kù)要方便了許多,但是它也并不是沒(méi)有缺點(diǎn)的。
這其中一個(gè)很大的問(wèn)題是,我們把所有數(shù)據(jù)都直接存儲(chǔ)在了文檔當(dāng)中,這一方面造成了數(shù)據(jù)的冗余,另一方面也限制了拓展性。
比如說(shuō),同一個(gè)商家下類似的商品不能共享圖片,而必須存儲(chǔ)多份,這造成了空間的浪費(fèi)。
再比如,假設(shè)我們希望支持用戶修改之前過(guò)去的評(píng)論會(huì)非常麻煩,因?yàn)槲覀儽仨氁业剿写鎯?chǔ)了用戶評(píng)論的文檔進(jìn)行修改(假設(shè)在其他場(chǎng)景下也用到了用戶評(píng)論),這往往是跨系統(tǒng)并且非常不方便的。
這個(gè)問(wèn)題也并不是不可解的,比如我們可以把文檔當(dāng)中存儲(chǔ)的具體數(shù)據(jù)換成一個(gè) ID。
比如 Comment 當(dāng)中不再存儲(chǔ)具體的圖片和評(píng)論信息,而存儲(chǔ)一個(gè)評(píng)論的 ID,在使用的時(shí)候再去關(guān)聯(lián)。
由于文檔型數(shù)據(jù)庫(kù)由于架構(gòu)的原因,對(duì)于關(guān)聯(lián)的支持并不好,往往需要我們手動(dòng)根據(jù) ID 再去查詢來(lái)模擬連接,這也會(huì)帶來(lái)額外的開(kāi)銷。
另外一個(gè)小瑕疵是在文檔型數(shù)據(jù)庫(kù)當(dāng)中我們?cè)L問(wèn)數(shù)據(jù)的路徑變長(zhǎng)了,舉個(gè)例子,加入我們要獲取商品評(píng)論當(dāng)中的第二條中的第一張圖片。
我們需要寫(xiě)成 item['comments'][1]['imgs'][0],而在關(guān)系型數(shù)據(jù)庫(kù)當(dāng)中,由于圖片是通過(guò)關(guān)系直接查詢得到的,因此要方便一些。
除了這些之外,NoSQL 數(shù)據(jù)庫(kù)發(fā)展的年限和 MySQL 這些較成熟的關(guān)系型數(shù)據(jù)庫(kù)相比要短得多,因此支持的特性相對(duì)比較少。
總結(jié)
通過(guò)一個(gè)例子,我們很生動(dòng)地對(duì)比了關(guān)系型數(shù)據(jù)庫(kù)和 NoSQL 數(shù)據(jù)庫(kù)之間的差別。
為什么我們?cè)趯W(xué)習(xí)數(shù)據(jù)庫(kù)的時(shí)候需要先從 ER 圖開(kāi)始,而不是直接學(xué)習(xí)數(shù)據(jù)庫(kù)的原理和它的使用方法呢?我想理解了上面的例子之后,再來(lái)看這個(gè)問(wèn)題應(yīng)該會(huì)簡(jiǎn)單許多。
因?yàn)殛P(guān)系型數(shù)據(jù)庫(kù)的核心邏輯就是存儲(chǔ)關(guān)系,使用規(guī)范、各種技巧和特性,本質(zhì)上都是圍繞這個(gè)核心展開(kāi)的。
如果我們沒(méi)有 Get 到這一層就來(lái)使用數(shù)據(jù)庫(kù)很容易走偏,很多匪夷所思的操作就是這么來(lái)的。
比如有人在數(shù)據(jù)庫(kù)當(dāng)中存儲(chǔ)前端頁(yè)面的代碼,比如把 ID 拼接成一個(gè)字符串來(lái)實(shí)現(xiàn)存儲(chǔ)多個(gè)值等等。
這也說(shuō)明了經(jīng)典教材上的內(nèi)容沒(méi)有廢話,每一個(gè)章節(jié)都有它預(yù)期的作用,因此當(dāng)我們覺(jué)得某些內(nèi)容沒(méi)有用的時(shí)候,可能并不是教材錯(cuò)了,只是我們沒(méi)有理解到位。
作者:梁唐
編輯:陶家龍
出處:轉(zhuǎn)載自微信公眾號(hào) TechFlow(ID:techflow2019)