id | user_id | blog_id |
0 | 0 | 0 |
1 | 0 | 1 |
壞處:
破壞數(shù)據(jù)完備性。由于ID是主鍵,在數(shù)據(jù)模型上沒(méi)有約束來(lái)保證不出現(xiàn)重復(fù)的user_id,blog_id對(duì)。一旦數(shù)據(jù)出現(xiàn)重復(fù),更新刪除都是問(wèn)題。索引過(guò)多。由于是關(guān)系表,必須在user_id和blog_id上面分別建一個(gè)索引。影響性能。
解決方案:使用文檔數(shù)據(jù)庫(kù)典型的處理多對(duì)多的辦法。不是建立一張關(guān)系表,而是在其中一個(gè)文檔(如User)中,加入一個(gè)List字段。
user_id | user_name | blog_id[] | …… |
0 | Jake | 0,1 | …… |
1 | Rose | 1,2 | …… |
問(wèn)題二:沒(méi)有區(qū)分"一對(duì)多關(guān)系"和“多對(duì)一關(guān)系”癥狀:關(guān)系模型不區(qū)分“一對(duì)多”和“多對(duì)一”,對(duì)于文檔數(shù)據(jù)庫(kù)來(lái)講,關(guān)系模型只有“多對(duì)一”。就像這張Comment表:
comment_id | user_id | content | …… |
0 | 0 | “NoSQL反模式是好文章” | …… |
1 | 0 | “是啊” | …… |
如果整個(gè)模型都是這樣的“多對(duì)一”關(guān)系就需要反思了。
壞處:
額外索引。如果客戶端已知user_id,需要獲得User信息和Comment信息,需要執(zhí)行兩次查詢。其中一次查詢需要使用索引。并且要在客戶端自己Join。這樣可能有潛在性能問(wèn)題。
解決方案:?jiǎn)栴}的核心在于是已知user_id查詢兩張表,還是已知comment_id查詢兩張表。如果是已知comment_id這樣的設(shè)計(jì)就是合理的,但是如果是已知user_id來(lái)查詢,把關(guān)系放在user表里的設(shè)計(jì)更合理一些。
user_id | user_name | comment_id[] | …… |
0 | Jake | 0,1 | …… |
1 | Rose | 1,2 | …… |
這樣的設(shè)計(jì),就可以避免一個(gè)索引。同理,對(duì)于多對(duì)多也是一樣的,通過(guò)合理的安排字段的位置可以避免索引。
正確使用的場(chǎng)合:
關(guān)系型模型是非常成功的數(shù)據(jù)模型,合理的沿用是非常好的。但是由于文檔數(shù)據(jù)庫(kù)的特點(diǎn),需要適當(dāng)?shù)恼{(diào)整,這樣得出的數(shù)據(jù)模型,盡管性能不是最優(yōu),但是有最好的靈活性。并且也有利于和關(guān)系數(shù)據(jù)庫(kù)轉(zhuǎn)換。
反模式二:處處引用客戶端Join
癥狀:數(shù)據(jù)庫(kù)設(shè)計(jì)中充滿了xx_id的字端,在查詢的時(shí)候需要大量的手動(dòng)Join操作。就涉及到了這個(gè)反模式。正如上面提到的博客的關(guān)系模型,如果已知blog_id查詢comments,需要至少執(zhí)行3次查詢,并且手動(dòng)Join。
壞處:
手動(dòng)Join,麻煩且易出錯(cuò)。文檔數(shù)據(jù)庫(kù)不支持Join且沒(méi)有外鍵保證。因此需要在客戶端Join,這樣的操作對(duì)于軟件開發(fā)來(lái)講是比較繁瑣的。由于沒(méi)有外鍵保證,因此不能保證取得的ID在數(shù)據(jù)庫(kù)里面是有數(shù)據(jù)的。在處理的時(shí)候需要不斷判斷,容易出錯(cuò)。多次查詢。如果引用過(guò)多,查詢的時(shí)候需要多次查詢才能查到足夠的數(shù)據(jù)。本來(lái)文檔數(shù)據(jù)庫(kù)是很快的,但是由于多次查詢,給數(shù)據(jù)庫(kù)增加了壓力,獲取全部數(shù)據(jù)的時(shí)間也會(huì)增加。事務(wù)處理繁瑣。文檔數(shù)據(jù)庫(kù)一般不支持一般意義上事務(wù),只支持行鎖。如果文檔數(shù)據(jù)庫(kù)有給多個(gè)連接。在插入的時(shí)候,事務(wù)的處理就是噩夢(mèng)。在文檔數(shù)據(jù)庫(kù)中使用事務(wù),需要使用行鎖,在進(jìn)行大量的處理。太過(guò)繁瑣,感興趣的讀者可以搜一下。
解決方案:適當(dāng)使用內(nèi)聯(lián)數(shù)據(jù)結(jié)構(gòu)。由于文檔數(shù)據(jù)庫(kù)支持更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)可以將引用轉(zhuǎn)換為內(nèi)聯(lián)的數(shù)據(jù),而不用新建一張表。這樣做可以解決上面的一些問(wèn)題,是一個(gè)推薦的方案。就像上面博客的例子一樣。將五張表簡(jiǎn)化成了兩張表。那什么時(shí)候使用內(nèi)聯(lián)呢?一般認(rèn)為
使用內(nèi)聯(lián)可以解決讀性能問(wèn)題,明顯減少Q(mào)uery的次數(shù)的時(shí)候??梢院?jiǎn)化數(shù)據(jù)模型,化簡(jiǎn)表之間的關(guān)系,而同時(shí)不會(huì)影響靈活性的時(shí)候。事務(wù)可以得到簡(jiǎn)化為單行事務(wù)的時(shí)候正確使用的場(chǎng)合:
范式化的使用場(chǎng)景,文檔數(shù)據(jù)庫(kù)會(huì)被多個(gè)應(yīng)用使用。由于數(shù)據(jù)庫(kù)設(shè)計(jì)無(wú)法估計(jì)多個(gè)應(yīng)用現(xiàn)在及將來(lái)的查詢情況,需要極大的靈活性。在這個(gè)時(shí)候,使用引用比內(nèi)聯(lián)靠譜。
反模式三 濫用內(nèi)聯(lián)后患無(wú)窮
問(wèn)題一 妨礙到查詢的內(nèi)聯(lián)癥狀:頻繁查詢一些內(nèi)聯(lián)字段,丟棄其他字段。
壞處:
無(wú)ID約束:使用內(nèi)聯(lián)字段和引用不同,是沒(méi)有ID約束的。因此不能通過(guò)ID(主鍵)來(lái)管理,如果經(jīng)常需要單獨(dú)操作內(nèi)聯(lián)對(duì)象會(huì)非常不便。索引泛濫:如果以內(nèi)聯(lián)字段為條件進(jìn)行查詢,需要建立索引。有可能造成索引泛濫。性能浪費(fèi):大部分文檔數(shù)據(jù)庫(kù)的實(shí)現(xiàn)是按行存儲(chǔ)的,也就意味著,盡管只查詢一個(gè)字段,但是DB需要將整行從磁盤中取出。如果字段夠小,文檔夠大,是很不合算的。
解決方案:如果出現(xiàn)以上的癥結(jié),就可以考慮使用引用代替內(nèi)聯(lián)了。內(nèi)聯(lián)特性主要的用途在于提高性能,如果出現(xiàn)性能不升反降,那就沒(méi)有意義了。如果對(duì)性能有很強(qiáng)烈的要求,可以考慮使用重復(fù)數(shù)據(jù),同樣的數(shù)據(jù)即在內(nèi)聯(lián)字段中也在引用的表里面。這樣可以結(jié)合內(nèi)聯(lián)和引用的性能優(yōu)勢(shì)。缺點(diǎn)是數(shù)據(jù)出現(xiàn)重復(fù),維護(hù)會(huì)比較麻煩。
問(wèn)題二 無(wú)限膨脹的內(nèi)聯(lián)癥狀:List,Map類型的內(nèi)聯(lián)字段不斷膨脹,而且沒(méi)有限制。就像前面提到的Blog的內(nèi)聯(lián)字段Comment。如果對(duì)每一篇Blog的Comment數(shù)量沒(méi)有限制的話,Comment會(huì)無(wú)限膨脹。輕則影響性能,重則插入失敗。
Blog_id | content | Comment[] | …… |
0 | “…” | “NoSQL反模式是好文章”, “是啊”,”無(wú)限增長(zhǎng)中”… | …… |
插入失敗。文檔數(shù)據(jù)庫(kù)的每條記錄都有最大大小,并且也有推薦最佳的大小。一般不會(huì)超過(guò)4M。就像剛剛提到的例子,如果是篇熱門的博文的話,評(píng)論的大小很容易就超過(guò)4M。屆時(shí)文檔將無(wú)法更新,新的評(píng)論無(wú)法插入。性能拖油瓶。由于內(nèi)聯(lián)字段膨脹,其大小將遠(yuǎn)遠(yuǎn)超過(guò)其他部分,影響其他部分的性能表現(xiàn)。并且因此導(dǎo)致該記錄大小頻繁變化,對(duì)檔數(shù)據(jù)庫(kù)的數(shù)據(jù)文件內(nèi)部可能因此產(chǎn)生很多碎片。
解決方案:設(shè)定最大數(shù)目或者使用引用。還是Blog和Comment的例子,可以將Comment從Blog中剝離出成一張表。如果考慮到性能,可以在Blog表中新建一個(gè)字段如最近的評(píng)論。這樣既保證了性能,又能夠預(yù)防膨脹。
Blog_id | content | last_five_comment[] | …… |
0 | “…” | “NoSQL反模式是好文章”, “是啊”,”最多5條”… | …… |
壞處:
權(quán)限管理難。數(shù)據(jù)庫(kù)的權(quán)限管理的最小粒度是表。如果使用內(nèi)聯(lián)技術(shù),就意味著內(nèi)聯(lián)部分必須和其他字段用同一個(gè)權(quán)限來(lái)管理。沒(méi)有辦法在DB級(jí)別隱藏。切表難。如果發(fā)現(xiàn)一張表的龐大需要切表。這個(gè)時(shí)候就比較糾結(jié)了。如果一刀切,partion Key的選擇;索引的失效都會(huì)成為問(wèn)題。如果覺(jué)得拆為兩張表,就會(huì)很好操作的話,就是內(nèi)聯(lián)的過(guò)度使用了 。備份難。關(guān)系數(shù)據(jù)庫(kù)中每張表可以有不同的備份策略。但是如果內(nèi)聯(lián)起來(lái),這樣的備份就做不到了。解決辦法:設(shè)計(jì)數(shù)據(jù)庫(kù)模型的時(shí)候需要考量之后的維護(hù)操作,尤其是內(nèi)聯(lián)的字段需不需要單獨(dú)的維護(hù)。需要和運(yùn)維商量。如果對(duì)內(nèi)聯(lián)的字段有單獨(dú)維護(hù)的要求,可以拆分出來(lái)作為引用。
問(wèn)題四 盯死應(yīng)用的內(nèi)聯(lián)癥狀:應(yīng)用可以非常好的運(yùn)行在數(shù)據(jù)庫(kù)上。但是當(dāng)新的應(yīng)用接入的時(shí)候會(huì)很麻煩。因?yàn)樵O(shè)計(jì)數(shù)據(jù)模型的時(shí)候考慮到了查詢。所以當(dāng)有新應(yīng)用,新查詢接入的時(shí)候,就會(huì)難于使用原有的模型。
壞處:
新應(yīng)用接入難。當(dāng)新的應(yīng)用試圖使用同一個(gè)數(shù)據(jù)庫(kù)的時(shí)候,接入比較困難。因?yàn)椴樵儠r(shí)不同的,需要調(diào)整數(shù)據(jù)模型才能適應(yīng)。但是調(diào)整模型又會(huì)影響原有應(yīng)用。集成難。不同的關(guān)系型數(shù)據(jù)庫(kù)可以集成在一起,共同使用。但是對(duì)于文檔數(shù)據(jù)庫(kù),雖然功能上可以互補(bǔ),但是由于內(nèi)聯(lián)數(shù)據(jù)結(jié)構(gòu)的差異,也比較難于集成。ETL難?,F(xiàn)在大部分的數(shù)據(jù)分析系統(tǒng)使用的是關(guān)系模型,就連Hadoop雖然不用關(guān)系模型,但是其上的Hive的常用工具也是按關(guān)系模型設(shè)計(jì)的。
解決方案:
使用范式設(shè)計(jì)數(shù)據(jù)庫(kù),即用引用代替內(nèi)聯(lián)?;蛘咴谑褂脙?nèi)聯(lián)的時(shí)候,給每個(gè)內(nèi)聯(lián)對(duì)象一個(gè)全局唯一的Key,保證其和關(guān)系模型直接可以存在映射關(guān)系,這樣可以提高數(shù)據(jù)模型的靈活性。如Blog表:
Blog_id | content | Comment[] | …… |
0 | “…” | [{"id"=1,"content"=“NoSQL反模式是好文章”}, {"id"=2,"content"=“是啊”}…] | …… |
這樣的設(shè)計(jì)既可以利用到內(nèi)聯(lián)的好處,又能將其和關(guān)系模型映射起來(lái)。確定是需要手動(dòng)維護(hù)comment_id,保證其全局唯一性。
反模式四:在線計(jì)算
癥狀:有一些運(yùn)行時(shí)間很長(zhǎng)的Query,由于有聚合計(jì)算,索引也不能解決。隨著數(shù)據(jù)量的增長(zhǎng),逐漸成為性能瓶頸。
壞處:
影響用戶體驗(yàn)。在線業(yè)務(wù)中,如果一個(gè)查詢大于4s,用戶體驗(yàn)會(huì)急劇下降。按主鍵和按索引的查詢都能滿足要求。但是聚合操作往往需要掃描全表或者大量的數(shù)據(jù),隨著數(shù)據(jù)量的增加,查詢時(shí)間會(huì)變長(zhǎng),用戶不可容忍。影響數(shù)據(jù)庫(kù)性能。長(zhǎng)查詢的壞處數(shù)不清。在線上應(yīng)用中,如果出現(xiàn)長(zhǎng)查詢,可能會(huì)霸占數(shù)據(jù)的大部分資源,包括IO,連接,CPU等等。導(dǎo)致其他很好的查詢,輕則性能也下降,重者無(wú)法使用數(shù)據(jù)庫(kù)。長(zhǎng)查詢可以稱之為DB殺手。
解決方案:首先要權(quán)衡,這個(gè)聚合操作是不是必要的,必須實(shí)時(shí)完成。如果沒(méi)有必要實(shí)時(shí)完成的話,可以采取離線操作的方案。在夜深人靜的時(shí)候,跑一個(gè)長(zhǎng)查詢,將結(jié)果緩存起來(lái),給第二天使用。如果必須實(shí)時(shí)完成,則可以新建一個(gè)字段,用“incr”這樣的操作,在運(yùn)行的時(shí)候,實(shí)時(shí)聚合結(jié)果。而不是查詢的時(shí)候執(zhí)行一次長(zhǎng)查詢。如果邏輯比較復(fù)雜,或者覺(jué)得大量“incr”操作給數(shù)據(jù)庫(kù)系統(tǒng)帶來(lái)了壓力,可以使用Storm之類的實(shí)時(shí)數(shù)據(jù)處理框架??傊饔瞄L(zhǎng)查詢。
反模式五:把內(nèi)聯(lián)Map對(duì)象的Key當(dāng)作ID用
癥狀:文檔數(shù)據(jù)庫(kù)支持內(nèi)聯(lián)Map類型。將其中Map的Key當(dāng)作數(shù)據(jù)庫(kù)的主鍵來(lái)用。
Blog_id | content | Comment{} | …… |
0 | “…” | {"1"=“NoSQL反模式是好文章”, "2"=“是啊”} | …… |
壞處:
無(wú)法通過(guò)數(shù)據(jù)庫(kù)做各種(>=)查詢。對(duì)于關(guān)系型數(shù)據(jù)庫(kù)來(lái)說(shuō),雖然數(shù)據(jù)結(jié)構(gòu)可以很靈活,但查詢的時(shí)候都是按層次的。比如comment.id,comment.content。也就是說(shuō)其Map類型中的Key可以理解為屬性名的,而不是用作ID。因此一旦這樣使用,就脫離的數(shù)據(jù)庫(kù)管制,無(wú)法使用各種查詢功能。無(wú)法通過(guò)索引查詢。文檔數(shù)據(jù)可建立索引是需要列名的。比如comment.id。而這樣的數(shù)據(jù)結(jié)構(gòu)沒(méi)有固定的列名,因此無(wú)法建立索引。
解決方案:使用數(shù)組+Map來(lái)解決。如:
Blog_id | content | Comment[] | …… |
0 | “…” | [{"id"=1,"content"=“NoSQL反模式是好文章”}, {"id"=2,"content"=“是啊”}…] | …… |
反模式六:不合理的ID
癥狀:使用String甚至更復(fù)雜數(shù)據(jù)結(jié)構(gòu)作為的ID,或者全部使用數(shù)據(jù)庫(kù)提供的自生成ID。如:
id(該ID系系統(tǒng)自生成) | Blog_id | content | …… |
0 | 0 | ... | …… |
ID混亂。如果使用數(shù)據(jù)庫(kù)提供的自生成ID,同時(shí)表中還有一個(gè)類似有主鍵含義的Blog_id,這樣很不好,容易造成邏輯混亂。由于文檔數(shù)據(jù)庫(kù)不支持ID的重命名,習(xí)慣關(guān)系數(shù)據(jù)庫(kù)做法的人可能會(huì)再建立一個(gè)自己的邏輯ID字段。這是沒(méi)有必要的。索引龐大,性能低下。ID是數(shù)據(jù)庫(kù)的非常重要的部分。ID的長(zhǎng)度將決定索引(包括主鍵的索引)的大小,直接影響到數(shù)據(jù)庫(kù)性能。如果索引比內(nèi)存小,性能會(huì)很好。但一旦索引大小超過(guò)內(nèi)存,出現(xiàn)數(shù)據(jù)交換,性能會(huì)急劇下降。一個(gè)Long占8字節(jié),一個(gè)20個(gè)字符的UTF8 String占用約60個(gè)字節(jié)。相差10倍之巨,不能不考慮。
解決方案:盡量使用有一定意義的字段做ID,并且不在其他字段中重復(fù)出現(xiàn)。不使用復(fù)雜的數(shù)據(jù)類型做ID,只使用int,long或者系統(tǒng)提供的主鍵類型做ID。
文檔數(shù)據(jù)庫(kù)的反模式總結(jié)
闡述了這么多的反模式,下面有個(gè)一覽表,涵蓋了上面所有的反模式。這個(gè)一覽表,是按照文檔數(shù)據(jù)庫(kù)模型建立的。是個(gè)文檔數(shù)據(jù)庫(kù)模型的例子。
ID | 反模式名 | 問(wèn)題 |
0 | 存在描述多對(duì)多的關(guān)系表 | [{ID:00 癥狀:文檔數(shù)據(jù)庫(kù)中存儲(chǔ)在有純粹的關(guān)系表 壞處:[破壞數(shù)據(jù)完備性,索引過(guò)多] 解決方案:加入一個(gè)List字段 },{ ID:01 癥狀:關(guān)系模型不區(qū)分“一對(duì)多”和“多對(duì)一” 壞處:額外索引 解決方案:合理的安排字段的位置 }] |
1 | 處處引用客戶端Join | [{ ID:10 癥狀:查詢的時(shí)候需要大量的手動(dòng)Join操作 壞處:[手動(dòng)Join,多次查詢, 事務(wù)處理繁瑣] 解決方案:適當(dāng)使用內(nèi)聯(lián)數(shù)據(jù)結(jié)構(gòu)。 }] |
2 | 濫用內(nèi)聯(lián)后患無(wú)窮 | [{ ID:20 癥狀:頻繁查詢一些內(nèi)聯(lián)字段,丟棄其他字段 壞處:[無(wú)ID約束,索引泛濫, 性能浪費(fèi)] 解決方案:使用引用代替內(nèi)聯(lián)了,允許重復(fù)數(shù)據(jù) },{ ID:21 癥狀:List,Map類型的內(nèi)聯(lián)字段不斷膨脹,而且沒(méi)有限制 壞處:[插入失敗, 性能拖油瓶] 解決方案:設(shè)定最大數(shù)目或者使用引用。 },{ ID:22 癥狀:DBA想單獨(dú)維護(hù)內(nèi)聯(lián)字段,但無(wú)法做到 壞處:[權(quán)限管理難, 切表難, 備份難] 解決方案:設(shè)計(jì)數(shù)據(jù)庫(kù)模型的時(shí)候需要考量之后的維護(hù)操作 },{ ID:23 癥狀:應(yīng)用可以非常好的運(yùn)行在數(shù)據(jù)庫(kù)上。但是當(dāng)新的應(yīng)用接入的時(shí)候會(huì)很麻煩。內(nèi)聯(lián)盯死了應(yīng)用 壞處:[新應(yīng)用接入難, 集成難, ETL難] 解決方案:使用范式設(shè)計(jì)數(shù)據(jù)庫(kù),即用引用代替內(nèi)聯(lián)。保證其和關(guān)系模型直接可以存在映射關(guān)系 }] |
3 | 在線計(jì)算 | [{ ID:30 癥狀:有一些運(yùn)行時(shí)間很長(zhǎng)的Query, 逐漸成為性能瓶頸。 壞處:[影響用戶體驗(yàn),影響數(shù)據(jù)庫(kù)性能] 解決方案:取消不必要的聚合操作. 運(yùn)行的時(shí)候,實(shí)時(shí)聚合結(jié)果.使用第三方實(shí)時(shí)或非實(shí)時(shí)工具。如Hadoop,Storm. }] |
4 | 把內(nèi)聯(lián)Map對(duì)象的Key當(dāng)作ID用 | [{ ID:40 癥狀:文檔數(shù)據(jù)庫(kù)支持內(nèi)聯(lián)Map類型。將其中Map的Key當(dāng)作數(shù)據(jù)庫(kù)的主鍵來(lái)用。 壞處:[無(wú)法通過(guò)數(shù)據(jù)庫(kù)做各種(>""" =)查詢,無(wú)法通過(guò)索引查詢] 解決方案:使用數(shù)組+Map來(lái)解決。 }] |
5 | 不合理的ID | [{ ID:50 癥狀:用String甚至更復(fù)雜數(shù)據(jù)結(jié)構(gòu)作為的ID,或者全部使用數(shù)據(jù)庫(kù)提供的自生成ID。 壞處:[ID混亂,索引龐大] 解決方案:盡量使用有一定意義的字段做ID。不使用復(fù)雜的數(shù)據(jù)類型做ID。 }] |
本文試圖總結(jié)了筆者知道的重要的文檔數(shù)據(jù)庫(kù)的反模式?,F(xiàn)在關(guān)于NoSQL數(shù)據(jù)模型設(shè)計(jì)模式的討論才剛剛起步,將來(lái)也許會(huì)逐漸自成體系。對(duì)于列數(shù)據(jù)庫(kù)和Key-Value的反模式,筆者等到有了足夠積累的時(shí)候,再和大家分享。
標(biāo)簽:吉安 丹東 本溪 鶴崗 邯鄲 昭通 大理 景德鎮(zhèn)
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《NoSQL反模式 - 文檔數(shù)據(jù)庫(kù)篇》,本文關(guān)鍵詞 NoSQL,反,模式,文檔,數(shù)據(jù)庫(kù),;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。