背景
一直受傳統rdb的影響,對于資料庫表的設計可能大多數開發者都形成了思維定勢。在雲計算和大資料背景下,rdbms正在接近極限,kv存儲将受到越來越多的關注。學習nosql,不求能革rdbms的命,但希望在設計思路上能得到一些拓寬,很多場景裡,sql表的設計和計算語句其實蠻難受的。
rdbms天生不是分布式的,因其保持着acid的特性發展至今,非常重視資料完整性,但在機器規模增長的情況下,acid是不可擴充的。同時,随着資料量和通路頻率增加,acid所要維護的開銷在增大。分割資料庫,無論水準還是垂直,都是在分散總資料和讀取需求,達到優化目的,維護代價和難度也随之上升。而kv的查找本質是散清單,且資料量無論如何增大,查找時間幾乎固定不變,即非常适合大規模資料。acid很注重cap中的c,而參考現實世界中很多事務,比如快遞,從你下單、付款到取貨,資金和物品的流轉并不嚴格一緻,隻要在一段時間内整個交易的最後結果滿足一緻性就可以了。同樣,nosql和rdbms比,更偏向于base(basically
available, soft-state, eventually consistent)的折中,重視可用性,但不追求狀态的嚴密性,且滿足最終一緻性。下面我以mongodb為例,展現一些他的特性和場景,期待nosql在當下能被更多的開發者拿來一顯身手。
mongodb與rdbms
mongodb是面向文檔的nosql,couchdb則是這一類資料庫的元祖。從總體上看,
1. mongodb是最親和rdbms的一個nosql,能解決大部分關系型資料庫解決的問題
2. 跟面向列存儲的hbase相比,面向文檔存儲和面向行存儲更接近,比如在沒有索引的情況下,掃描整個表内記錄,同樣是掃描全文檔,及文檔的每個字段
3. mongodb的索引同樣也是b樹,在一些索引的優化和設計上會和mysql比較相似(當然需要遵循mongo的設計來做,不完全劃等号)
4. 你可以把mongodb拿rdbms一樣來使用(當然不推薦這麼做),無非是将一行記錄變成mongodb裡的json對,在document(相當于mysql的table)之間,也可以做類似外鍵一樣的引用
5. mongodb雖然沒有嚴格的事務性操作,但是開發者自己可以做到類似事務的效果(詳見下文)。這一點也算是mongodb貼近rdbms的一個表現吧。
6. mongo的查詢中也有mysql中的in, where, group等字段,而且想group那樣的查詢會比mysql更強大(見下文)
以下會從各個主要關注點來展開mongo的特性,展現角度更偏向于想要調研使用mongodb的人,看看mongodb是否符合自己的業務場景,也希望我的分析會有所幫助。
存儲結構怎麼樣
mongodb的存儲類似json,每個db内有多個collection,相當于table,每個collection内是許許多多的document,這個document的schemeless的。本質上,他的面向文檔指的是key-value中的value,而這個value可以是一個值(引用id或基本類型),可以是一個數組,也可以是一個文檔(嵌套的json對)。
一對多是最常遇到的場景,mysql中要使用兩張或以上表的關聯甚至join進行查詢,在mongo中直接使用嵌套型或引用型(用id)就可以了。沒有特殊需求的話,嵌套的方式隻要一張"表"就可以實作。比如我這樣建立一個人的資訊:
上述這樣的結構中,展現了無模式、value為數組、嵌套、引用等。
處理好多對多的關系可謂是nosql的精髓所在。理論上,可以在一個集合中完成存儲。不過實際上這樣的情況非常罕見。這是由于查詢的多樣性所導緻的,若是隻有一種類型的查詢,則這種多對多的關系放在一個良好設計的集合中,雖然會有大量的備援,但是效率一定是最高的。如何設計這種資料庫的關鍵就是看你有多少種查詢,每一種的頻率是多少,使用的其他要求是什麼樣的。對于不同的查詢,同樣的資料庫設計的性能也是大不一樣。還有一點,一般不要拆成三個集合,這是傳統的關系型資料庫的思維方式。而常見的情況就是拆成兩個集合,然後有一部分備援,對最常用的查詢做一個索引。
總結就是兩張表,一張裡面存了另外一張裡的id集合,有備援存放,主要是根據查詢場景設計和建索引,不要和rdbms一樣變三張。此外還有個好處是可以進行正反向查詢,在各自的字段裡加上id數組。
創新、更新、删除文檔
為避免不良設計,doc大小有限制,mongodb 1.8支援16m
批量插入 mongoimport
更新:文檔替換:模式結構有大變化時,update(find_the_doc, new_doc)
修改器:$inc 操作整數,雙精度浮點數 小範圍改動 速度很快,如果不存在則建立 {"$inc" : {"keyname" : 100 }}
$set $unset 修改某個鍵,如果不存在則建立
$push $pop 操作數組 $ne來判斷某個數組裡是否存在某值 $addtoset和$each組合
修改器速度:$inc屬于就地修改 而數組修改器可能更改了文檔大小,會慢
mongodb給文檔預留了補白,來适應大小變化,如果超出了則重新配置設定空間,就會慢了
是以要自己設計。不然$push可能會成為瓶頸,就考慮把内嵌的數組獨立出來,單獨放到集合裡
特殊的更新 upsert (update第三個參數)是原子性的,比如db.math.update({"count":25}, {"$inc":{"count":3}}, true) 如果沒有count會生成,然後繼續加3
update的時候,第一個參數是找符合的doc,第二個參數是更新操作,第三個是否upsert,第四個參數是是否更新多個文檔
執行完後運作getlasterror看有沒有更新出錯
findandmodify,等待資料庫相應,在操作查詢、取值和指派整個原子性操作時很友善,速度慢些
查詢(簡單例舉一下)
find()指定傳回的鍵
查詢條件 $lt $gt $lte $gte 别的查詢鍵 $or $in $nin $not等,不具體舉例了,不是特性了解就好
查詢數組相關:$all $size $slice
正規表達式:支援perl相容的正規表達式(pcre)
對查詢結果的處理 $limit $skip $sort (其中$skip适合跳過少量文檔,否則性能影響)
以上不能滿足,還有$where,更複雜的查詢是mapreduce(下文介紹)
聚合(mongodb本身的聚合操作可能可以好好依賴一下,比如olap裡複雜的查詢和本地聚合操作可以大量借用mapreduce?)
count() distinct()
group() 類似 group by,且可以附帶一個finalize函數對結果修剪
mapreduce可以做複雜的聚合查詢,并行化到多個伺服器,當然mapreduce和group都不适合實時場景
簡單示範一下mongodb的group和mapreduce的基本寫法,不深究細節,但求了解個大概,進階查詢可以做些什麼。
group:
mapreduce:
函數分為map, reduce,還有finalize,query,sort,scope等函數輔助mr。
利用好以上的查詢和聚合方法,加上合理設計,應該滿足大部分查詢場景。
索引怎麼樣
和傳統rdbms幾乎一樣,好處是同樣的索引優化方法适用于mongodb
被索引之後,會按照索引排序
查詢一半以上的結果,不用索引,表掃描就可以了,比如查某個布爾或存在某個鍵
預設最大索引個數64,每次插入更新删除都會産生額外開銷
用ensureindex:1 or -1來控制索引方向
對内嵌文檔中的鍵建索引和普通索引沒有什麼差別
用.explain()看查找情況,有沒有用索引,掃描了多少文檔等等
還有基于地理空間的索引,$near :[-40, 73] $within $box / $center
索引補充:對沒有索引的鍵使用sort,mongo會把所有資料拿到記憶體排序,是以有上限,不能做t級别sort。是以可以為排序而建索引。
mongodb與js
cmd環境下是js引擎,記得使用的是v8引擎(可能随着版本更新引擎會改變)。所有的執行語句都有一套js的api。同時,db.eval()可以執行js。更令人驚喜的是,system.js裡可以存儲js變量,是以也可以直接存儲js函數,然後用db.eval()執行調用。比如:
然後調用它,
輸出結果是:
我對mongodb的這一特性充滿遐想,可能很多腳本和日常操作,包括運維操作,都可以用js寫好并存儲,而且js本身可以做一切mongodb的操作,這個特性可以給我們帶來更多的什麼呢?我覺得mongodb這樣的設計,和以前我們接觸的mysql這樣的資料庫有一個很本質的差別。我們通常意義中的資料庫隻負責存儲資料,除了sql提供一些查詢和增删改查外,其餘複雜的資料處理都需要通過各自語言的driver來連接配接資料庫并自己用代碼實作一些處理邏輯,最後再傳回到資料庫中。而mongodb,不但提供了可以任意執行js的環境,已經mapreduce這樣複雜的查詢設計,而且還支援儲存js代碼在system.js中,其實僅僅靠mongodb本身,就可以做任何你想做的資料操作和處理。這樣的好處是,使用mongodb的應用層可以完全解耦對mongodb資料處理的依賴,可能mongodb的dba可以除了專職維護外,用一些内置的js代碼完成複雜的處理邏輯,讓應用層可以放心使用預備好的資料。我覺得這在易用的同時做到了真正的強大。此外,可以想象前端開發人員如果單獨設計一套系統或搭建一個網站,會很喜歡使用mongodb。
事務性怎麼樣(沒有寫要求的可以忽略這段)
mongo的事務性操作其實蠻巧妙的,他支援一個findandmodify的操作,是一個保證原子性的操作。顧名思義,在一個操作裡先find目标文檔,然後進行修改,整個過程由mongo保證原子。具體使用方法不介紹了。
此外還有一套樂觀的并發控制。樂觀并發控制其實是update本身,基于認為“同一個文檔基本上不會被同時修改”這一樂觀的事務機制。整個事務的過程會分拆為以下幾個步驟,需要開發者自己來實作:
通過查詢獲得文檔
儲存文檔原始值
更新得到一個新的文檔
用原始文檔當作arg1,更新文檔當作arg2進行一次update操作
若更新失敗則重複1
這個并發控制其實就是一次文檔自身的替換,即update的目标是整個文檔。詳細的例子還有實作兩階段送出 http://docs.mongodb.org/manual/tutorial/perform-two-phase-commits/,即用一個記錄員collection來記錄一個事務(比如a和b之間彙款)進行過程中每一步的情況,包括initial, pending, commit, done等,進而可以進行恢複和确認事務進行情況。
分區分庫怎麼樣
有這樣的情況,同部門各個項目中,各組的dba會各自做各自的分庫分區,對于mysql的分區分庫不夠透明,,而在jdbc的sql語句層(不論使用的是mybatis還是spring jdbc等),不能完全将讀取資料庫路由與上層應用解耦。這應該是一個常見的問題。mongo的分片讀取都由路由來完成,應用程式隻要連接配接一個mongos路由進行即可,路由會從配置服務mongod裡獲得各個db和collection的情況以及分片片鍵,一般推薦一個mongos對應一個應用。
上圖是一種健壯的分片部署方式。黃色mongod為配置伺服器,綠色mongos為路由,藍色為真實存儲資料的分片。且每個shard裡有三個mongod,構成的是一個副本集(自動故障恢複的主從叢集,且沒有固定的主節點)。藍色mongod是實際存儲資料的分片,且shard之間需要手動設定負載均衡依據的片鍵,mongodb會自動做負載均衡,相關配置資訊會記錄在黃色mongod配置伺服器上。分片可以動态增加和減少,隻要啟動一個mongod,用指令添加到分片叢集裡,他就可以一起做負載了。而每個mongod其實都是一個單獨的mongodb執行個體,也可以不依賴叢集單獨啟動使用。應用程式通過連接配接路由mongos來和mongodb打交道,應該說讀取資料是比較透明。分片叢集的搭建,擴容與增減分片,副本集的搭建,應用層對分片叢集的使用接入等基本概念,具體可以參看我之前一篇部落格http://blog.csdn.net/pelick/article/details/8644116。總之,擴容、部
署、搭建等操作是非常友善的,這也是mongodb易用性高的一個重要原因。
gridfs存儲檔案
mongo自帶存儲大的二進制檔案。基本思想是把打檔案分成很多塊,每塊單獨作為一個文檔存。使用方面和存取一般的document一樣,也有分片機制,不産生磁盤碎片。視覺中國用gridfs存過大量圖檔。
管理和運維
./mongod --port xxx --logpath xxx 的啟動方式,也可以./mongod --config ~/.mongodb.conf 的配置檔案方式啟動
比啟動端口大1000号的端口有頁面版的管理資訊檢視
mongodump作備份,mongorestore恢複備份
(待補充)