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