由于公司系统使用 MongoDB,虽然之前了解,但并没有深入学习 MongoDB。见此机会,参考《MongoDB 权威指南》深入学习,结合对比 MySQL,加深对两种不同数据库的理解。特把学习过程记录和大家分享,欢迎批评指正。
表结构对比 | MongoDB | MySQL |
---|---|---|
表 | collections | tables |
行 | documents | rows |
主键 | _id | id 与业务无关的值作为主键。如果没有显式地在表定义时指定主键,InnoDB 存储引擎会为每一行生成一个 6 字节的 ROWID |
主键生成策略 | 24 位的字符串 (time + machine + pid + inc),自己指定 | UUID, 自增 |
面向文档数据库 | T | F |
面向行数据库 | F | T |
约束 | 无 | 主键约束,外键约束 |
数据类型对比 | MongoDB | MySQL |
---|---|---|
整形 | NumberInt("3"),NumberLong("3") | TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT |
浮点 | 默认使用 64 位浮点型数值 | FLOAT, DOUBLE, DECIMAL |
字符 | utf8 字符串 | VARCHAR, CHAR |
日期/时间 | new Date(), 自新纪元依赖经过的毫秒数,不存储时区 | DATE, DATETIME, TIMESTAMP |
NULL | null | 不支持(null 与 null 不相等) |
布尔类型 | true/false | 不支持 |
正则表达式 | 支持 { "x" : /foobar/i } | 不支持 |
数组 | 支持 { "x" : ["a", "b", "c"]} | 不支持 |
二进制数据 | 支持 GridFS | BLOB, TEXT |
代码片段 | { "x" : function() { /... / } } | 不支持 |
对比项 | MongoDB | MySQL |
---|---|---|
启动 | mongo | mysql -u root -p |
查看库 | show dbs | show databases |
使用库 | use test | use test |
查看表 | show collections | show tables |
查询对比 | 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 分别对应 <, <=, >, >= | 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 号迁移成用户名的过程中,查询时需要同时匹配 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 不仅会匹配某个键的值为 null 的文档,而且还会匹配不包含这个键的文档。所以,这种匹配还会返回缺少这个键的所有文档。如果 仅想要匹配键值为 null 的文档,既要检查改建的值是否为 null, 还要通过 $exists 条件 判定键值已经存在 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 } }] }) 由于查询优化器不会对 $and 进行优化,所以可以改写成下面的 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 兼容的正则表达式 (PCRE) 库来匹配正则表达式,任何 PCRE 支持表达式的正则表达式语法都能被 MongoDB 接受 | SELECT * FROM blogs WHERE title LIKE "post%"; |
{ "_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 }
函数对比 | 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" } } } ]); 结果: { "_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 对比 | MongoDB | MySQL |
---|---|---|
插入数据 | post = {"title" : "My Blog Post", "content" : "Here`s my blog post"}; db.blog.insert(post) 如果 blog 这个集合不存在,则会创建 | 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--" }]) 当前版本的 MongoDB 能接受最大消息长度 48MB,所以在一次批量插入中能插入的文档是有限制的。并且在执行批量插入的过程中,有一个文档插入失败,那么在这个文档之前的所有文档都会成功插入到集合中,而这个文档以及之后的所有文档全部插入失败。 | INSERT INTO blogs(title , blog_content ) VALUES('AAA', 'AAA---'), ('BBB', 'BBB---'); |
查询数据 | db.blog.find(); db.blog.findOne(); | SELECT * FROM blogs; SELECT * FROM blogs LIMIT 1; |
更新旧数据 | 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'; |
删除数据 | db.blog.remove({ title : "My Blog Post" }) | DELETE FROM blogs WHERE title = 'My Blog Post' |
校验 | 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) 插入的时候,检查大小。所有的文档都必须小于 16MB。这样做的目的是为了防止不良的模式设计,并且保持性能一直。由于 MongoDB 只进行最基本的检查,所以插入非法的数据很容易。 | 类型校验,长度校验。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
可以用来存储大文件 (>16M), 与 MySQL BLOB,TEXT 类似。
MapReduce
MapReduce 是一种计算模型,简单的说就是将大批量的工作数据分解执行,然后再将结果合并成 最终结果。MongoDB 提供的 MapReduce 非常灵活,对于大规模数据分析也相当实用。
时间有限的集合
MongoDB 2.2 引入一个新特性-- TTL 集合,TTL 集合支持失效时间设置,使用 expireAfterSeconds 来实现 当超过指定时间后,集合自动清除超时的文档,这用来保存一些诸如 session 会话信息 的时候非常有用,或者存储数据使用。
无 JOIN
MongoDB 为了更快的读,以及更方便的分布式,抛弃了 JOIN 操作。 JOIN 开销其实很大。
关于锁
当资源被代码的多个部分所共享时,需要确定这处资源只能在一个地方被操作。 就版本的 MongoDB(pre 2.0) 拥有一个全局的写入锁。这就意味着贯穿整个服务器只有一个地方做写操作。 这就可能导致数据库因为某个地方锁定超负载而停滞。这个问题在 2.0 版本中得到 了显著的改善,并且在 2.2 版本中得到了进一步的加强。MongoDB 2.2 使用数据库级别的锁再这个问题上迈进了一大步。
图中是两个不同版本的 MongoDB,写入性能对比。
MySQL InnoDB 使用行级锁,有效提高并发。
无事务
不像 MySQL 这些多行数据原子操作的传统数据库。MongoDB 只支持单个文件的原子修改。 但这也正是 MongoDB 可以更快地读的原因,没有事务这些负载的处理。MongoDB 可以轻松处理 TB 级别的数据。
磁盘消耗
MongoDB 会消耗太多的磁盘空间了。当然,这与它的编码方式有关,因为 MongoDB 会通过预分配 大文件空间来避免磁盘碎片问题。它的工作方式是这样:在创建数据库时,系统会创建 一个名为 [db_name].0 的文件,当该文件有一半以上被使用时,系统会再次创建一个名 为 [db_namel].1 的文件,该文件的大小是方才的两倍。这个情况会持续不断的发生,因此 256、512、1024、2048 大小的文件会被写到磁盘上。
MySQL:
强大的引擎
InnoDB 引擎:MySQL 默认的事务性引擎。它被设计用来处理大量的短期事务,短期 事务大部分情况是正常提交的,很少会被回滚。InnoDB 采用 MVCC 来支持高并发。
MyISAM 引擎:5.1 以及之前的默认版本。全文索引,压缩,空间函数,但是 MyISAM 不支持事务和 行级锁。MyISAM 最整张表加锁,而不是针对行。读取时会对需要读到的所有表加 共享锁,写入时则对表加排它锁。
Memory 引擎:比 MyISAM 表块一个数量级,因为所有的数据都保存在内存中,不需要进行磁盘 I/O。数据会丢失
Infobright 是最有名的面向列的存储引擎。但该引擎不支持索引。
事务
确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中
的数据应满足完整性约束。
schema
有利于数据整理,数据存储,并执行正规化的行为。保证数据的完整性,一致性。
描述了数据存储的模板,比如创建 table。
校验数据的格式,比如整形的 column 就不能存放字符串数据。
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 |
在本测试例子中,MySQL 写入情况好于 MongoDB
压测参考命令:
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
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 |
在本测试例子中,MySQL 读取性能没有 MongoDB 好
压测参考命令:
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
参考