MongoDB 與 MySQL 對(duì)比
由于公司系統(tǒng)使用MongoDB嚷缭,雖然之前了解掺出,但并沒有深入學(xué)習(xí)MongoDB甸箱。見此機(jī)會(huì)闽坡,參考《MongoDB 權(quán)威指南》深入學(xué)習(xí)栽惶,結(jié)合對(duì)比MySQL愁溜,加深對(duì)兩種不同數(shù)據(jù)庫的理解疾嗅。特把學(xué)習(xí)過程記錄和大家分享。
一冕象、 表結(jié)構(gòu)對(duì)比
表結(jié)構(gòu)對(duì)比 | MongoDB | MySQL |
---|---|---|
表 | collections | tables |
行 | documents | rows |
主鍵 | _id | id 與業(yè)務(wù)無關(guān)的值作為主鍵代承。如果沒有顯式地在表定義時(shí)指定主鍵,InnoDB存儲(chǔ)引擎會(huì)為每一行生成一個(gè)6字節(jié)的ROWID |
主鍵生成策略 | 24位的字符串(time + machine + pid + inc),自己指定 | UUID, 自增 |
面向Documents數(shù)據(jù)庫 | T | F |
面向行數(shù)據(jù)庫 | F | T |
約束 | 無 | 主鍵約束渐扮,外鍵約束 |
二论悴、 數(shù)據(jù)類型對(duì)比
數(shù)據(jù)類型對(duì)比 | MongoDB | MySQL |
---|---|---|
整形 | NumberInt("3"),NumberLong("3") | TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT |
浮點(diǎn) | 默認(rèn)使用64位浮點(diǎn)型數(shù)值 | FLOAT, DOUBLE, DECIMAL |
字符 | utf8 字符串 | VARCHAR, CHAR |
日期/時(shí)間 | new Date(), 自新紀(jì)元依賴經(jīng)過的毫秒數(shù),不存儲(chǔ)時(shí)區(qū) | DATE, DATETIME, TIMESTAMP |
NULL | null | 不支持(null與null不相等) |
布爾類型 | true/false | 不支持 |
正則表達(dá)式 | 支持 { "x" : /foobar/i } | 不支持 |
數(shù)組 | 支持 { "x" : ["a", "b", "c"]} | 不支持 |
二進(jìn)制數(shù)據(jù) | 支持 GridFS | BLOB, TEXT |
代碼片段 | { "x" : function() { /... / } } | 不支持 |
三墓律、 SHELL終端對(duì)比
對(duì)比項(xiàng) | MongoDB | MySQL |
---|---|---|
啟動(dòng) | mongo | mysql -u root -p |
查看庫 | show dbs | show databases |
使用庫 | use test | use test |
查看表 | show collections | show tables |
四膀估、 查詢對(duì)比
查詢對(duì)比 | MongoDB | MySQL |
---|---|---|
檢索單列 | db.users.find({ "age" : 27 }) | SELECT * FROM users WHERE age = 27; |
檢索多列 | db.users.find({ "age" : 27, "username" : "joe" }) | SELECT * FROM users WHERE age = 27 and username = 'joe'; |
指定需要返回的鍵 | db.users.find({}, { "username" : 1, "email" : 1 }) | SELECT username, email FROM users; |
范圍檢索 | db.users.find({"age" : { "$gte" : 18, "$lte" : 30 }}) $lt, $lte, $gt, $gte 分別對(duì)應(yīng) <, <=, >, >= | SELECT * FROM users WHERE age >= 18 AND age <=30; |
不匹配檢索 | db.users.find({ "username" : { "$ne" : "joe" } }) | SELECT * FROM users WHERE username <> 'joe'; |
IN 操作符 | db.raffle.find({ "ticket_no" : { "$in" : [725, 542, 390] } }) $in非常靈活,可以指定不同類型 的條件和值耻讽。 例如在逐步將用戶的ID號(hào)遷移成用戶名的過程中察纯, 查詢時(shí)需要同時(shí)匹配ID和用戶名 | SELECT ticket_no FROM raffles WHERE ticket_no IN (725, 542, 390); |
NOT IN 操作符 | db.raffle.find({ "ticket_no" : { "$nin" : [725, 542, 390] } }) | SELECT * FROM raffles WHERE ticket_no not in (725, 542, 390); |
OR 操作符 | db.raffle.find({ "$or" : [{ "ticket_no" : 725 }, { "winner" : true }] }) | SELECT * FROM raffles WHERE ticket_no = 725 OR winner = 'true'; |
空值檢查 | db.c.find({"y" : null}) null不僅會(huì)匹配某個(gè)鍵的值為null的文檔 ,而且還會(huì)匹配不包含這個(gè)鍵的文檔。 所以饼记,這種匹配還會(huì)返回缺少這個(gè)鍵的所有文檔香伴。 如果 僅想要匹配鍵值為null的文檔, 既要檢查改建的值是否為null, 還要通過 $exists 條件 判定鍵值已經(jīng)存在 db.c.find({ "z" : { "$in" : [null], "$exists" : true }}) | SELECT * FROM cs WHERE z is null; |
多列排序 | db.c.find().sort({ username : 1, age: -1 }) | SELECT * FROM cs ORDER BY username ASC, age DESC; |
AND操作符 | db.users.find({ "$and" : [{ "x" : { "$lt" : 1 }, { "x" : 4 } }] }) 由于查詢優(yōu)化器不會(huì)對(duì) $and進(jìn)行優(yōu)化具则, 所以可以改寫成下面的 db.users.find({ "x" : { "$lt" : 1, "$in" : [4] } }) | SELECT * FROM users WHERE x > 1 AND x IN (4); |
NOT 操作符 | db.users.find({ "id_num" : { "$not" : { "$mod" : [5,1] } } }) | SELECT * FROM users WHERE id_num NOT IN (5,1); |
LIKE 操作符(正則匹配) | db.blogs.find( { "title" : /post?/i } ) MongoDB 使用Perl兼容的正則表達(dá)式(PCRE) 庫來匹配正則表達(dá)式即纲, 任何PCRE支持表達(dá)式的正則表達(dá)式語法都能被MongoDB接受 | SELECT * FROM blogs WHERE title LIKE "post%"; |
五、 函數(shù)對(duì)比
{ "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2 }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1 }
{ "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5 }
{ "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10 }
{ "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10 }
函數(shù)對(duì)比 | MongoDB | MySQL |
---|---|---|
COUNT | db.foo.count() | SELECT COUNT(id) FROM foo; |
DISTINCT | db.runCommand({ "distinct": "people", "key": "age" }) | SELECT DISTINCT(age) FROM people; |
MIN | db.sales.aggregate( [ { $group: { _id: {}, minQuantity: { $min: "$quantity" } } } ]); 結(jié)果: { "_id" : { }, "minQuantity" : 1 } | SELECT MIN(quantity) FROM sales; |
MAX | db.sales.aggregate( [ { $group: { _id: {}, maxQuantity: { $max: "$quantity" } } } ]); | SELECT MAX(quantity) FROM sales; |
AVG | db.sales.aggregate( [ { $group: { _id: {}, avgQuantity: { $avg: "$quantity" } } } ]); | SELECT AVG(quantity) FROM sales; |
SUM | db.sales.aggregate( [ { $group: { _id: {}, totalPrice: { $sum: "$price" } } } ]); | SELECT SUM(price) FROM sales; |
六博肋、 CURD 對(duì)比
CURD 對(duì)比 | MongoDB | MySQL |
---|---|---|
插入數(shù)據(jù) | post = {"title" : "My Blog Post", "content" : "Here`s my blog post"}; db.blog.insert(post) 如果blog 這個(gè)集合不存在低斋,則會(huì)創(chuàng)建 | INSERT INTO blogs(title , blog_content ) VALUES ('My Blog Post', 'Here`s my blog post.') |
批量插入 | db.blog.batchInsert([{ "title" : "AAA", "content" : "AAA---" }, { "title" : "BBB", "content" : "JJJJ--" }]) 當(dāng)前版本的MongoDB能接受最大消息長度48MB, 所以在一次批量插入中能插入的文檔是有限制的匪凡。 并且在執(zhí)行批量插入的過程中拔稳,有一個(gè)文檔插入失敗, 那么在這個(gè)文檔之前的所有文檔都會(huì)成功插入到集合中锹雏, 而這個(gè)文檔以及之后的所有文檔全部插入失敗巴比。 | INSERT INTO blogs(title , blog_content ) VALUES('AAA', 'AAA---'), ('BBB', 'BBB---'); |
查詢數(shù)據(jù) | db.blog.find(); db.blog.findOne(); | SELECT * FROM blogs; SELECT * FROM blogs LIMIT 1; |
更新舊數(shù)據(jù) | post.blog_content = "十一"; db.blog.update({title: "My Blog Post"}, post) | UPDATE set blog_content = "十一" WHERE title = "My Blog Post"; |
更新新增COLUMN | post.comments = "very good"; db.blog.update({title : "My Blog Post"}, post) | ALTER table blogs ADD COLUMN comments varchar(200); UPDATE blogs set comments = "very good" WHERE title = 'My Blog Post'; |
刪除數(shù)據(jù) | db.blog.remove({ title : "My Blog Post" }) | DELETE FROM blogs WHERE title = 'My Blog Post' |
校驗(yàn) | post.blog_visit = 123; db.blog.update({title : "My Blog Post"}, post); post.blog_visit = "asd.123aaa"; db.blog.update({title : "My Blog Post"}, post) 插入的時(shí)候,檢查大小礁遵。所有的文檔都必須小于16MB轻绞。 這樣做的目的是為了防止不良的模式設(shè)計(jì),并且保持性能一直佣耐。由于MongoDB只進(jìn)行最基本的檢查政勃,所以插入非法的數(shù)據(jù)很容易。 | 類型校驗(yàn)兼砖,長度校驗(yàn)奸远。 ALTER table blogs ADD COLUMN blog_visit INT(10); UPDATE blogs SET blog_visit = "asdasd" WHERE id = 1; ERROR 1366 (HY000): Incorrect integer value: 'asdasd' for column 'blog_visit' at row 1 |
刪除表 | db.blog.remove({}), db.blog.drop() | DELETE from blogs; drop table blogs; |
七、有的沒的
MongoDB:
-
GridFS
可以用來存儲(chǔ)大文件(>16M), 與MySQL BLOB讽挟,TEXT 類似懒叛。
-
MapReduce
MapReduce
是一種計(jì)算模型,簡單的說就是將大批量的工作數(shù)據(jù)分解執(zhí)行耽梅,然后再將結(jié)果合并成
最終結(jié)果薛窥。MongoDB提供的MapReduce
非常靈活,對(duì)于大規(guī)模數(shù)據(jù)分析也相當(dāng)實(shí)用眼姐。
-
時(shí)間有限的集合
MongoDB 2.2 引入一個(gè)新特性--
TTL集合诅迷,TTL集合支持失效時(shí)間設(shè)置,使用expireAfterSeconds 來實(shí)現(xiàn)
當(dāng)超過指定時(shí)間后众旗,集合自動(dòng)清除超時(shí)的文檔罢杉,這用來保存一些諸如session會(huì)話信息
的時(shí)候非常有用,或者存儲(chǔ)數(shù)據(jù)使用贡歧。 -
無JOIN
MongoDB 為了更快的讀滩租,以及更方便的分布式拱镐,拋棄了JOIN操作。
JOIN開銷其實(shí)很大持际。 -
關(guān)于鎖
當(dāng)資源被代碼的多個(gè)部分所共享時(shí)沃琅,需要確定這處資源只能在一個(gè)地方被操作。
就版本的MongoDB(pre
2.0)擁有一個(gè)全局的寫入鎖蜘欲。這就意味著貫穿整個(gè)服務(wù)器只有一個(gè)地方做寫操作益眉。
這就可能導(dǎo)致數(shù)據(jù)庫因?yàn)槟硞€(gè)地方鎖定超負(fù)載而停滯。這個(gè)問題在2.0版本中得到
了顯著的改善姥份,并且在2.2版本中得到了進(jìn)一步的加強(qiáng)郭脂。MongoDB
2.2使用數(shù)據(jù)庫級(jí)別的鎖再這個(gè)問題上邁進(jìn)了一大步。圖中是兩個(gè)不同版本的MongoDB澈歉,寫入性能對(duì)比展鸡。
MySQL InnoDB使用行級(jí)鎖,有效提高并發(fā)埃难。
-
無事務(wù)
不像MySQL
這些多行數(shù)據(jù)原子操作的傳統(tǒng)數(shù)據(jù)庫莹弊。MongoDB只支持單個(gè)文件的原子修改。
但這也正是MongoDB可以更快地讀的原因涡尘,沒有事務(wù)這些負(fù)載的處理忍弛。MongoDB
可以輕松處理TB級(jí)別的數(shù)據(jù)。 -
磁盤消耗
MongoDB
會(huì)消耗太多的磁盤空間了考抄。當(dāng)然细疚,這與它的編碼方式有關(guān),因?yàn)镸ongoDB會(huì)通過預(yù)分配
大文件空間來避免磁盤碎片問題川梅。它的工作方式是這樣:在創(chuàng)建數(shù)據(jù)庫時(shí)疯兼,系統(tǒng)會(huì)創(chuàng)建
一個(gè)名為[db_name].0的文件,當(dāng)該文件有一半以上被使用時(shí)贫途,系統(tǒng)會(huì)再次創(chuàng)建一個(gè)名
為[db_namel].1的文件吧彪,該文件的大小是方才的兩倍。這個(gè)情況會(huì)持續(xù)不斷的發(fā)生潮饱,因此
256来氧、512、1024香拉、2048大小的文件會(huì)被寫到磁盤上。
MySQL:
-
強(qiáng)大的引擎
InnoDB 引擎: MySQL默認(rèn)的事務(wù)性引擎中狂。它被設(shè)計(jì)用來處理大量的短期事務(wù)凫碌,短期
事務(wù)大部分情況是正常提交的,很少會(huì)被回滾胃榕。InnoDB采用MVCC來支持高并發(fā)盛险。MyISAM 引擎: 5.1以及之前的默認(rèn)版本瞄摊。全文索引,壓縮苦掘,空間函數(shù)换帜,但是MyISAM不支持事務(wù)和
行級(jí)鎖。MyISAM最整張表加鎖鹤啡,而不是針對(duì)行惯驼。讀取時(shí)會(huì)對(duì)需要讀到的所有表加
共享鎖,寫入時(shí)則對(duì)表加排它鎖递瑰。Memory 引擎:比MyISAM表塊一個(gè)數(shù)量級(jí)祟牲,因?yàn)樗械臄?shù)據(jù)都保存在內(nèi)存中,不需要進(jìn)行磁盤I/O抖部。數(shù)據(jù)會(huì)丟失
Infobright 是最有名的面向列的存儲(chǔ)引擎说贝。但該引擎不支持索引。
-
事務(wù)
確保數(shù)據(jù)庫的狀態(tài)從一個(gè)一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€(gè)一致狀態(tài)慎颗。一致狀態(tài)的含義是數(shù)據(jù)庫中
的數(shù)據(jù)應(yīng)滿足完整性約束乡恕。
-
schema
有利于數(shù)據(jù)整理,數(shù)據(jù)存儲(chǔ)俯萎,并執(zhí)行正規(guī)化的行為几颜。 保證數(shù)據(jù)的完整性,一致性讯屈。
描述了數(shù)據(jù)存儲(chǔ)的模板蛋哭,比如創(chuàng)建table。
校驗(yàn)數(shù)據(jù)的格式涮母,比如整形的column 就不能存放字符串?dāng)?shù)據(jù)谆趾。
八、 MySQL 與 MongoDB 寫入對(duì)比
options | MySQL | MongoDB |
---|---|---|
Time taken for tests: | 548.281 seconds | 661.318 seconds |
Total transferred: | 44000000 bytes | 44200000 bytes |
Requests per second: | 182.39 [#/sec] | 151.21 [#/sec] |
Time per request: | 274.141 [ms] | 330.659 [ms] |
Time per request(across all concurrent requests): | 5.483 [ms] | 6.613 [ms] |
Transfer rate: | 78.37 [Kbytes/sec] received | 65.27 [Kbytes/sec] received |
在本測(cè)試?yán)又信驯荆琈ySQL 寫入情況好于 MongoDB
壓測(cè)參考命令:
ab -c 50 -n 100000 http://127.0.0.1:6666/deals/mysql_write
ab -c 50 -n 100000 http://127.0.0.1:6666/deals/mongodb_write
九沪蓬、 MySQL 與 MongoDB 讀取對(duì)比
options | MySQL | MongoDB |
---|---|---|
Time taken for tests: | 1181.881 seconds | 606.406 seconds |
Failed requests: | 2239 | 0 |
Non-2xx responses: | 2239 | 0 |
Total transferred: | 359397490 bytes | 44100000 bytes |
Requests per second: | 84.61 [#/sec] | 164.91 [#/sec] |
Time per request: | 590.941 [ms] | 303.203 [ms] |
Time per request(across all concurrent requests): | 11.819 [ms] | 6.064 [ms] |
Transfer rate: | 296.96 [Kbytes/sec] received | 71.02 [Kbytes/sec] received |
在本測(cè)試?yán)又校?MySQL 讀取性能沒有 MongoDB好
壓測(cè)參考命令:
ab -c 50 -n 100000 http://127.0.0.1:6666/deals/mysql_read
ab -c 50 -n 100000 http://127.0.0.1:6666/deals/mongodb_read
參考
MongoDB核心貢獻(xiàn)者:不是MongoDB不行,而是你不懂来候!