MongoDB 的一些基础知识和使用。
MongoDB
六个简单的概念:
概念对照表:
MongoDB | 传统的关系型数据库 |
---|---|
database | database, 相同 |
collections | table |
documents | row |
fields | columns |
Indexes | Indexes 相同 |
Cursors | - |
当我们从 MongoDB 获取数据的时候,我们通过 cursor 来操作,读操作会被延迟到需要实际数据的时候才会执行。
核心差异在于,在MongoDB里,collection中的每个documents都可以有自己独立的 field (字段),而关系型数据中每行的字段都智能相同
要点就是,集合不对存储内容严格限制 (所谓的无模式(schema-less))。字段由每个独立的文档进行跟踪处理。
总结:MongoDB 可以每行数据的结构都不同,支持非结构化数据。 区别于 传统的严格结构化数据。
MongoDB 的 shell MongoDB 的 shell 是一个连接数据库服务的客户端控制台工具。MongoDB 启动shell ,在命令行输入:
mongo
shell 用的是 JavaScript。 因为这是一个 JavaScript shell,如果你输入的命令漏了 (),你会看到这个命令的源码,拿到一个以 function (...){ 开头的返回的内容。
MongoDB 内部用二进制序列化 JSON 格式,称为 BSON。
如果要操作当前数据库,用 db ,比如 db.help() 或者 db.stats()。
use 用来切换数据库,比如,输入 use learn。
大多数情况下我们会操作集合, 而不是数据库。比如:用 db.COLLECTION_NAME ,比如 db.unicorns.help() 或者 db.unicorns.count()。
注意,除你指定的字段之外,会多出一个 _id 字段。每个文档都会有一个唯一 _id 字段。你可以自己生成一个,或者让 MongoDB 帮你生成一个 ObjectId 类型的。
db.unicorns.insert({name: 'Aurora',
gender: 'f', weight: 450})
db.unicorns.remove({})。
掌握选择器(Selector):MongoDB 的查询选择器就像 SQL 语句里面的 where 一样。
因此,你会在对集合的文档做查找,计数,更新,删除的时候用到它。选择器是一个 JSON 对象,最简单的是就是用 {} 匹配所有的文档。比如可以用 {gender:'f'}。
{field: value} 用来查找那些 field 的值等于 value 的文档。 {field1: value1, field2: value2} 相当于 and 查询。还有
lte,
gte 和 $ne 被用来处理 小于,小于等于,大于,大于等于,和不等于操作。
db.unicorns.find({gender: 'm',
weight: {$gt: 700}})
db.unicorns.find({gender: {$ne: 'f'},
weight: {$gte: 701}})
$exists 用来匹配字段是否存在,比如:
db.unicorns.find({
vampires: {$exists: false}})
'$in' 被用来匹配查询文档在我们传入的数组参数中是否存在匹配值,比如:
db.unicorns.find({
loves: {$in:['apple','orange']}})
使用 $or 操作符,再给它一个我们要匹配的数组:
db.unicorns.find({gender: 'f',
$or: [{loves: 'apple'},
{weight: {$lt: 500}}]})
_id 字段生成的 ObjectId 可以这样查询:
db.unicorns.find(
{_id: ObjectId("TheObjectId")})
如果改变一个或者几个字段的值的时候,你应该用 MongoDB 的 $set 操作。
$set 操作
db.unicorns.update({weight: 590}, {$set: {
name: 'Roooooodles',
dob: new Date(1979, 7, 18, 18, 44),
loves: ['apple'],
gender: 'm',
vampires: 99}})
直接用 update 会产生覆盖效果,谨慎使用:
db.unicorns.update({name: 'Roooooodles'},
{weight: 590})
$inc 可以用来给一个字段增加一个正/负值
db.unicorns.update({name: 'Pilot'}, {$inc: {vampires: -2}})
字段加一个值,通过 $push 操作:
db.unicorns.update({name: 'Aurora'}, {$push: {loves: 'sugar'}})
update 语法还支持 upsert 更新,即:在文档中找到匹配值时更新它,无匹配时向文档插入新值。
要使用 upsert 我们需要向 update 写入第三个参数 {upsert:true},示例:
db.hits.update({page: 'unicorns'},
{$inc: {hits: 1}}, {upsert:true});
db.hits.find();
批量 Updates时,multi 选项需要设为 true:
db.unicorns.update({},
{$set: {vaccinated: true }},
{multi:true});
db.unicorns.find({vaccinated: true});
find 有第二个可选参数,叫做 "projection"。这个参数是我们要检索或者排除字段的列表。
db.unicorns.find({}, {name: 1});
默认的,_id 字段总是会返回的。我们可以通过这样显式的把它从返回结果中排除 {name:1, _id: 0}。
sort 用于排序,我们指定我们希望排序的字段,以 JSON 方式,其中 1 表示升序 -1 表示降序。比如:
//heaviest unicorns first
db.unicorns.find().sort({weight: -1})
//by unicorn name then vampire kills:
db.unicorns.find().sort({name: 1,
vampires: -1})
MongoDB 对未经索引的字段进行排序是有大小限制的。如果你试图对一个非常大的没有经过索引的结果集进行排序的话,你会得到个异常。
对结果分页可以通过 limit
和 skip
游标方法来实现。比如:
db.unicorns.find()
.sort({weight: -1})
.limit(2)
.skip(1)
通过 limit
和 sort
的配合,可以在对非索引字段进行排序时避免引起问题。
shell 中可以直接对一个集合执行 count
,像这样:
db.unicorns.count({vampires: {$gt: 50}})
实际上,count
是一个 cursor
的方法,shell 只是简单的提供了一个快捷方式。
以不提供快捷方式的方法来执行的时候需要这样:
db.unicorns.find({vampires: {$gt: 50}})
.count()
mongoDB 没有 join (链接, 比如 内连接inner Join,外连接out join)。传统数据库中的 join 基本上意味着不可扩展。就是说,如果想把数据水平扩展,你只能放弃在使用join。事实就是,数据之间的关系, 在 MongoDB 中无法直接表达和查询。只能在我们的应用代码中自己实现,需要进行二次查询 find ,把相关数据保存到另一个集合中。
示例:
先添加一个名叫 Leto 的 主管
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d730"),
name: 'Leto'})
然后再加几个工人,把他们的 主管 设置为 Leto:
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d731"),
name: 'Duncan',
manager: ObjectId(
"4d85c7039ab0fd70a117d730")});
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d732"),
name: 'Moneo',
manager: ObjectId(
"4d85c7039ab0fd70a117d730")});
(有必要再重复一次, _id 可以是任何形式的唯一值。因为你很可能在实际中使用 ObjectId ,我们也在这里用它。)
当然,要找出 Leto 负责管理的所有工人,只需要执行:
db.employees.find({manager: ObjectId(
"4d85c7039ab0fd70a117d730")})
示例:
// 插入一个 manager 是单个对象。
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d732"),
name: 'Moneo',
manager: ObjectId(
"4d85c7039ab0fd70a117d730")});
// 插入一个 manager 是 数组。
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d733"),
name: 'Siona',
manager: [ObjectId(
"4d85c7039ab0fd70a117d730"),
ObjectId(
"4d85c7039ab0fd70a117d732")] })
注意上面的文档,manager 字段的值既可以是单个对象,也可以是数组。而我们原来的 find 查询依旧可用:
db.employees.find({manager: ObjectId(
"4d85c7039ab0fd70a117d730")})
MongoDB 还支持内嵌文档。来试试看向文档插入一个内嵌文档,像这样:
db.employees.insert({_id: ObjectId(
"4d85c7039ab0fd70a117d734"),
name: 'Ghanima',
family: {mother: 'Chani',
father: 'Paul',
brother: ObjectId(
"4d85c7039ab0fd70a117d730")}})
像你猜的那样,内嵌文档可以用 dot-notation 查询:
db.employees.find({
'family.mother': 'Chani'})
一些对冗余处理 的讨论。 并不是需要对你文档里的每条数据都做冗余处理。而是说,与其对冗余数据心存恐惧,让它影响你的设计决策,不如在建模的时候考虑什么信息应当属于什么文档。
假设你要写一个论坛应用。
记住: 一个独立文档的大小当前被限制在 16MB
。
处理一对多(one-to-many)或者多对多(many-to-many)场景的时候,id 数组通常是一个正确的选择。
内嵌文档经常使用的情形:大多数情况下多是很小的数据块,面对总是被和父节点一起拉取的数据块。
比如,常见的例子就是博客。你是应该分成一个 posts 集合和一个 comments 集合呢,还是应该每个 post 下面嵌入一个 comments 数组?
MongoDB 的处理方式:MongoDB 的灵活架构允许你把这两种方式结合起来,你可以把评论放在独立的集合中,同时在博客帖子下嵌入一小部分评论 (比如说最新评论) ,以便和帖子一同显示。
这遵守以下的规则:“ 你到底想在一次查询中获取到什么内容,那就怎么做。”
想一想,如果在关系型数据库中,要把上面说的这两种方式结合起来用,“要不要再建一个关联表呢?”
单一解决方案还是多技术方案? 对于许多项目来说 - 或者说大多数 - 单一解决案是一个明智的选择。只有你自己才知道,引进新技术是否利大于弊。引入MongoDB 往往不会完全替换旧的方案(比如用Mongo替换MySQL),而是说“不用再依赖单一的解决案来处理你的数据”,作为数据存储的局部替代方案,是对你现有数据存储方案能力的局部增强。
比如说用 Lucene 作为关系型数据库的全文检索索引的加强,或者用 Redis 作为持久型 key-value 存储对缓存存储的增强,MongoDB 就是用来保存你的数据能力的处理增强。
MongoDB 可以胜任的一个特殊角色是在日志领域。有两点使得 MongoDB 的写操作非常快。首先,你可以选择发送了写操作命令之后立刻返回,而无须等到操作完成。
如果想让你的数据 "过期" ,基于时间而不是整个集合的大小,你可以用 TTL 索引 ,所谓 TTL 是 "time-to-live" 的缩写。
从 2.0 版的 MongoDB 开始,日志是默认启动的,该功能允许快速恢复服务器,比如遭遇到了服务器崩溃或者停电的情况。
MongoDB 不支持事务。两个代替案:
set。还有像 findAndModify 命令,可以更新或删除文档之后,自动返回修改过的文档
一个很强大的功能就是 MongoDB 支持 geospatial 索引。这允许你保存 geoJSON 或者 x 和 y 坐标到文档,并查询文档,用如
within 来获取一个矩形或圆中的点。
聚合管道提供了一种方法用于转换整合文档到集合。你可以通过管道来传递文档,就像 Unix 的 "pipe" 一样,将一个命令的输出传递到另第二个,第三个,等等
创建索引用 ensureIndex :
// where "name" is the field name
db.unicorns.ensureIndex({name: 1});
删除索引用 dropIndex:
db.unicorns.dropIndex({name: 1});
可以创建唯一索引,这需要把第二个参数 unique 设置为 true:
db.unicorns.ensureIndex({name: 1},
{unique: true});
索引可以内嵌到字段中和任何数组字段。我们可以这样创建复合索引:
db.unicorns.ensureIndex({name: 1,
vampires: -1});
需要检查你的查询是否用到了索引,你可以通过 explain 方法:
db.unicorns.find().explain()
MongoDB 的复制在某些方面和关系型数据库的复制类似。所有的生产部署应该都是副本集,理想情况下,三个或者多个服务器都保持相同的数据。写操作被发送到单个服务器,也即主服务器,然后从它异步复制到所有的从服务器上。你可以控制是否允许从服务器上进行读操作,这可以让一些特定的查询从主服务器中分离出来,当然,存在读取到旧数据的风险。如果主服务器异常关闭,从服务中的一个将会自动晋升为新的主服务器继续工作。
MongoDB 支持自动分片。分片是实现数据扩展的一种方法,依靠在跨服务器或者集群上进行数据分区来实现。一个最简单的实现是把所有的用户数据,按照名字首字母 A-M 放在服务器 1 ,然后剩下的放在服务器 2。
你可以通过 db.stats() 查询数据库的状态。
可以这样执行 MongoDB profiler :
db.setProfilingLevel(2);
来备份我们的 learn 数据库导 backup 文件夹,我们需要在控制台或者终端中执行执行
mongodump --db learn --out backup
如果只还原 unicorns 集合,我们可以这样做:
mongorestore --db learn --collection unicorns \
backup/learn/unicorns.bson
文件导入导入数据 mongoexport 和 mongoimport 是另外两个可执行文件,用于导出和从 JSON/CSV 格式文件导入数据。比如说,我们可以像这样导出一个 JSON:
mongoexport --db learn --collection unicorns
CSV 格式是这样:
mongoexport --db learn \
--collection unicorns \
--csv --fields name,weight,vampires
https://github.com/ilivebox/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown