1.1 MongoDB的簡單介紹
在當今的數據庫市場上,MySQL無疑是占有一席之地的。作為一個開源的關系型數據庫,MySQL被大量應用在各大網站后台中,承擔着信息存儲的重要作用。2009年,甲骨文公司(Oracle)收購Sun公司,MySQL成為Oracle旗下產品。
而MongoDB是一種文件導向的數據庫管理系統,屬於一種通稱為NoSQL的數據庫,是10gen公司旗下的開源產品,其內部數據存儲的方式與傳統的關系型數據有很大差別。
NoSQL的全稱是Not Only SQL,也可以理解非關系型的數據庫,是一種新型的革命式的數據庫設計方式,不過它不是為了取代傳統的關系型數據庫而被設計的,它們分別代表了不同的數據庫設計思路。
雖然MongoDB背后的公司沒有Oracle強大,但其目前也正在被應用在各行各業中。MongoDB是目前被應用最廣泛的NoSQL數據庫產品。
1.2 MongoDB的存儲特點
在傳統的關系型數據庫中,數據是以表單為媒介進行存儲的,每個表單均擁有縱向的列和橫向的行。以MySQL為例,如果用戶想以學生的學號為索引,存入其姓名與住址信息時,數據庫中存放的信息便如下圖所示:
ID |
Name |
Address |
10001 |
Alice |
A1 |
10002 |
Bob |
A2 |
10003 |
Cara |
A3 |
10004 |
David |
A4 |
10005 |
Eve |
A5 |
上圖表明數據庫中存入了5個表項,其記錄了學號為10001 – 10005的學生的姓名與住址信息。
如果用戶欲把相應的信息重新存入MongoDB數據庫,那么數據庫中的信息應該為如下所示:
"_id" : "10001", "Name" : "Alice", "Address" : "A1",
"_id" : "10002", "Name" : "Bob", "Address" : "A2",
"_id" : "10003", "Name" : " Cara", "Address" : "A3",
"_id" : "10004", "Name" : " David", "Address" : "A4",
"_id" : "10005", "Name" : " Eve", "Address" : "A5", |
由此可見,相比較MySQL,MongoDB以一種直觀文檔的方式來完成數據的存儲。它很像JavaScript中定義的JSON格式,不過數據在存儲的時候MongoDB數據庫為文檔增加了序列化的操作,最終存進磁盤的其實是一種叫做BSON的格式,即Binary-JSON。
對比兩個數據庫中數據存儲的差異,你可能沒有什么特別的直觀感受。讓我們再看看MongoDB存放的另一組數據:
"_id" : "10001", "score" : { "Maths" : 71, "English" : 62, }
"_id" : "10002", "score" : { "Maths" : 81, "Chemistry" : 74, "Sport" : { "Basketball" : 67, "Badminton" : 76, }, } |
上述數據表明了學號為10001與10002兩名學生的課程分數信息。如果想把同樣的數據存入MySQL數據中的話,勢必要很費一番功夫。在關系型數據庫中,列的數目一般事先固定,各列之間可以由列名來識別。如果想存入以上數據,我們可以采取如下方法:
ID |
Maths |
English |
Chemistry |
Basketball |
Badminton |
10001 |
71 |
62 |
null |
null |
null |
10002 |
81 |
null |
74 |
67 |
76 |
或者如下這種:
ID |
Course |
Score |
Course |
Score |
Course |
Score |
Course |
Score |
10001 |
Maths |
71 |
English |
62 |
null |
null |
null |
null |
10002 |
Maths |
81 |
Chemistry |
74 |
Basketball |
67 |
Badminton |
76 |
上述兩種存儲方式無論選哪一種,都不能很直觀地呈現兩名學生的各科成績與各學科之間隸屬關系,在存儲空間上的利用也不盡如意,並且可擴展性也不夠好。當然,為了解決這些問題,我們還可以使用多張表單來存儲學生的成績,但這樣也會使數據庫中的內容更加復雜。
1.2 MongoDB的應用場景
在另一方面,對開發者來說,如果是因為業務需求或者是項目初始階段,而導致數據的具體格式無法明確定義的話,MongoDB的這一鮮明特性就脫穎而出了。相比傳統的關系型數據庫,它非常容易被擴展,這也為寫代碼帶來了極大的方便。
不過MongoDB對數據之間事務關系支持比較弱,如果業務這一方面要求比較高的話,MongoDB還是並不適合此類型的應用。
另外,MongoDB出現的時機比較晚,還具備一些非常鮮明的特性。比如:
1. 它里面自帶了一個名叫GirdFS的分布式文件系統,這就為MongoDB的部署提供了很大便利。而像MySQL這種比較早的數據庫,雖然市面上有很多不同的分表部署的方案,但這種終究不如MongoDB直接官方支持來得便捷實在。
2. 另外,MongoDB內部還自建了對map-reduce運算框架的支持,雖然這種支持從功能上看還算是比較簡單的,相當於MySQL里GroupBy功能的擴展版,不過也為數據的統計帶來了方便。
3. MongoDB在啟動后會將數據庫中的數據以文件映射的方式加載到內存中。如果內存資源相當豐富的話,這將極大地提高數據庫的查詢速度,畢竟內存的I/O效率比磁盤高多了。
但是,作為一個新鮮的事務,MongoDB也存在着很多不足。它在為開發人員提供了便利的情況下,卻在運維上面臨着不少難題,比如:
1. 比起MySQL,MongoDB沒有成熟的運維經驗,需要不斷地探索。
2. MongoDB中的數據存放具有相當的隨意性,不具有MySQL在開始就定義好了。對運維人員來說,他們可能不清楚數據庫內部數據的數據格式,這也會數據庫的運維帶來了麻煩。
2. 測試目的
MongoDB與MySQL作為兩種不同類型的數據庫,當其中存放的記錄越來越多的時候,其插入效率將會受到怎樣的影響,是本次實驗所關注的對象。
在這里,我們將本次實驗數據庫中數據存儲的規模定在1億條。
3. 測試條件
機器配置: CPU:Intel(R) Xeon(R) CPU E5-2620 @ 2.00GHz
內存:65954040 KB
(關鍵詞:12核CPU,64G內存,給我多好)
操作系統: Linux version 2.6.32_1-8-0-0 (gcc version 4.4.4 20100726 (Red Hat 4.4.4-13) (GCC) )
MongoDB版本: 2.2.3,無任何優化配置,單機測試
MySQL版本: 5.1.49,無任何優化配置,單機測試
測試語言: Python 2.7.1
數據庫接口驅動:
MongoDB : PyMongo 2.1.1
MySQL: MySQLdb 1.2.3
4. 概念普及
在數據庫存放的數據中,有一種特殊的鍵值叫做主鍵,它用於惟一地標識表中的某一條記錄。也就是說,一個表不能有多個主鍵,並且主鍵不能為空值。
無論是MongoDB還是MySQL,都存在着主鍵的定義。
對於MongoDB來說,其主鍵名叫”_id”,在生成數據的時候,如果用戶不主動為其分配一個主鍵的話,MongoDB會自動為其生成一個隨機分配的值。
在MySQL中,主鍵的指定是在MySQL插入數據時指明PRIMARY KEY來定義的。當沒有指定主鍵的時候,另一種工具 —— 索引,相當於替代了主鍵的功能。索引可以為空,也可以有重復,另外有一種不允許重復的索引叫惟一索引。如果既沒有指定主鍵也沒有指定索引的話,MySQL會自動為數據創建一個。
5. 測試方法
1. 制定一個數據庫表項的字段模板,以此模板為基准向數據庫中插入數據。
2. 在內存中自動生成1億條待測試數據。數據的格式這里不再一一列出,其里面包含了大概45個字段,其中有一個關鍵的字段是1 – 100,000,000 的md5值,它們彼此並不相同,其他字段的數據都是寫死。每條數據的大小大概有1K。
記住,本次測試的方法是先在內存中生成1億條數據后,再執行插入操作的。還好測試機器的內存足夠大,能夠存下如此多的數據。
3. 以如下四種模式向數據庫中插入數據,每插1000條數據時,就往一個固定的文件中寫入該時刻的時間:
a) 在MongoDB中指定_id為1 – 100,000,000 的md5值,將數據插入;
b) 在MongoDB中不指定_id值,將1 – 100,000,000 的md5值視為普通的字段插入;
c) 在MySQL中以1 – 100,000,000 的md5值為PRIMARY KEY,將數據插入;
d) 在MySQL中不指定PRIMARY KEY,將1 – 100,000,000 的md5值視為普通的字段插入。
4. 根據生成的時間文件記錄,分析MySQL與MongoDB的插入性能。
5. 進一步,在以上四種數據庫的基礎上,再分別測試一下數據庫的讀取性能。
6. 測試過程
寫好測試腳本之后,運行之,睡一覺起床來看結果就可以了。過程是漫長的,但結果卻是可喜可賀的 :D
7. 測試結果
7.1 平均每條數據的插入時間
先上張圖,來點直觀感受:
圖上數據橫坐標是平均每插入1000條數據所需要的時間,單位是秒。記住,是每1000條數據,不是每條數據哦。
總結:
1. 數據庫的平均插入速率:MongoDB不指定_id插入 > MySQL不指定主鍵插入 > MySQL指定主鍵插入 > MongoDB指定_id插入。
2. MongoDB在指定_id與不指定_id插入時速度相差很大,而MySQL的差別卻小很多。
分析:
1. 在指定_id或主鍵時,兩種數據庫在插入時要對索引值進行處理,並查找數據庫中是否存在相同的鍵值,這會減慢插入的速率。
2. 在MongoDB中,指定索引插入比不指定慢很多,這是因為,MongoDB里每一條數據的_id值都是唯一的。當在不指定_id插入數據的時候,其_id是系統自動計算生成的。MongoDB通過計算機特征值、時間、進程ID與隨機數來確保生成的_id是唯一的。而在指定_id插入時,MongoDB每插一條數據,都需要檢查此_id可不可用,當數據庫中數據條數太多的時候,這一步的查詢開銷會拖慢整個數據庫的插入速度。
3. MongoDB會充分使用系統內存作為緩存,這是一種非常優秀的特性。我們的測試機的內存有64G,在插入時,MongoDB會盡可能地在內存快寫不進去數據之后,再將數據持久化保存到硬盤上。這也是在不指定_id插入的時候,MongoDB的效率遙遙領先的原因。但在指定_id插入時,當數據量一大內存裝不下時,MongoDB就需要將磁盤中的信息讀取到內存中來查重,這樣一來其插入效率反而慢了。
4. MySQL不愧是一種非常穩定的數據庫,無論在指定主鍵還是在不指定主鍵插入的情況下,其效率都差不了太多。
7.2 插入穩定性分析
插入穩定性是指,隨着數據量的增大,每插入一定量數據時的插入速率情況。
在本次測試中,我們把這個指標的規模定在10w,即顯示的數據是在每插入10w條數據時,在這段時間內每秒鍾能插入多少條數據。
先呈現四張圖上來:
1. MongoDB指定_id插入:
2. MongoDB不指定_id插入:
3. MySQL指定PRIMARY KEY插入:
4. MySQL不指定PRIMARY KEY插入:
總結:
1. 整體上的插入速度還是和上一回的統計數據類似:MongoDB不指定_id插入 > MySQL不指定主鍵插入 > MySQL指定主鍵插入 > MongoDB指定_id插入。
2. 從圖中可以看出,在指定主鍵插入數據的時候,MySQL與MongoDB在不同數據數量級時,每秒插入的數據每隔一段時間就會有一個波動,在圖表中顯示成為規律的毛刺現象。而在不指定插入數據時,在大多數情況下插入速率都比較平均,但隨着數據庫中數據的增多,插入的效率在某一時段有瞬間下降,隨即又會變穩定。
3. 整體上來看,MongoDB的速率波動比MySQL的嚴重,方差變化較大。
4. MongoDB在指定_id插入時,當插入的數據變多之后,插入效率有明顯地下降。在其他三種的插入測試中,從開始到結束,其插入的速率在大多數的時候都固定在一個標准上。
分析:
1. 毛刺現象是因為,當插入的數據太多的時候,MongoDB需要將內存中的數據寫進硬盤,MySQL需要重新分表。這些操作每當數據庫中的數據達到一定量級后就會自動進行,因此每隔一段時間就會有一個明顯的毛刺。
2. MongoDB畢竟還是新生事物,其穩定性沒有已應用多年的MySQL優秀。
3. MongoDB在指定_id插入的時候,其性能的下降還是很厲害的。
7.3 MySQL與MongoDB讀取性能的簡單測試
這是一個附加的測試,也並沒有測試得非常完整,但還是很能說明一些問題的。
測試方法:
先在1 – 100, 000, 000這一億個數中,分別隨機取1w, 5w, 10w, 20w, 50w個互不相同的數字,再計算其md5值,並保存。
至於為什么最高只選到50w這個規模,這是因為我在隨機生成100w個互不相同的數字的時候,寫的腳本跑了一晚上都沒有跑出來,估計是我生成的算法寫得太爛了。我不想重新再弄了,暫就以50w為上限吧。
在上述帶主鍵插入的兩個數據庫里,分別以上一步生成的md5源為輸入進行查詢操作。同樣,每查詢1000條數據在日志文件中將當前系統時間寫入。
測試結果:
以下三張圖的橫坐標是每查詢1000條數據所需要的時間,單位為s;縱坐標是查詢的規模,分為1w, 5w,10w, 20w, 50w五個等級。
這張圖是詳細對比,可以看出MySQL與MongoDB之間的差異了嗎……
總結:
1. 在讀取的數據規模不大時,MongoDB的查詢速度真是一騎絕塵,甩開MySQL好遠好遠。
2. 在查詢的數據量逐漸增多的時候,MySQL的查詢速度是穩步下降的,而MongoDB的查詢速度卻有些起伏。
分析:
1. 如果MySQL沒有經過查詢優化的話,其查詢速度就不要跟MongoDB比了。MongoDB可以充分利用系統的內存資源,我們的測試機器內存是64GB的,內存越大MongoDB的查詢速度就越快,畢竟磁盤與內存的I/O效率不是一個量級的。
2. 本次實驗的查詢的數據也是隨機生成的,因此所有待查詢的數據都存在MongoDB的內存緩存中的概率是很小的。在查詢時,MongoDB需要多次將內存中的數據與磁盤進行交互以便查找,因此其查詢速率取決於其交互的次數。這樣就存在這樣一種可能性,盡管待查詢的數據數目較多,但這段隨機生成的數據被MongoDB以較少的次數從磁盤中取出。因此,其查詢的平均速度反而更快一些。這樣看來,MongoDB的查詢速度波動也處在一個合理的范圍內。
3. MySQL的穩定性還是毋庸置疑的。
8. 測試總結
8.1 測試結論
1. 相比較MySQL,MongoDB數據庫更適合那些讀作業較重的任務模型。MongoDB能充分利用機器的內存資源。如果機器的內存資源豐富的話,MongoDB的查詢效率會快很多。
2. 在帶”_id”插入數據的時候,MongoDB的插入效率其實並不高。如果想充分利用MongoDB性能的話,推薦采取不帶”_id”的插入方式,然后對相關字段作索引來查詢。
8.2 測試需要進一步注意的問題
對MongoDB的讀取測試考慮不周,雖然這只是一個額外的測試。在這個測試中,隨機生成大量待測試的數據很有必要,但生成大量互不相同的數據就沒有必要了。正是這一點,把我的讀取測試規模限定在了50w條,沒能進一步進行分析。
8.3 MongoDB的優勢
1. MongoDB適合那些對數據庫具體數據格式不明確或者數據庫數據格式經常變化的需求模型,而且對開發者十分友好。
2. MongoDB官方就自帶一個分布式文件系統,可以很方便地部署到服務器機群上。MongoDB里有一個Shard的概念,就是方便為了服務器分片使用的。每增加一台Shard,MongoDB的插入性能也會以接近倍數的方式增長,磁盤容量也很可以很方便地擴充。
3. MongoDB還自帶了對map-reduce運算框架的支持,這也很方便進行數據的統計。
其他方面的優勢還在發掘中,本人也是剛剛接觸這個不久。
8.4 MongoDB的缺陷
1. 事務關系支持薄弱。這也是所有NoSQL數據庫共同的缺陷,不過NoSQL並不是為了事務關系而設計的,具體應用還是很需求。
2. 穩定性有些欠缺,這點從上面的測試便可以看出。
3. MongoDB一方面在方便開發者的同時,另一方面對運維人員卻提出了相當多的要求。業界並沒有成熟的MongoDB運維經驗,MongoDB中數據的存放格式也很隨意,等等問題都對運維人員的考驗。