MongoDB权威指南—读书笔记(Part One)
非常棒的官方网站!
MongoDB基础知识
- 文档是MongoDB中数据的基本单元,非常类似于关系学数据库管理系统中的行,但更具表现力。
- 类似地,集合(collection)可以看作是一个拥有动态模式(dynamic schema)的表。
- MongoDB的一个实例可以拥有多个相互独立的数据库(database),每一个数据库都拥有自己的集合。
- 每一个文档都由一个特殊的键”_id”,这个键在文档所属的集合中是唯一的。
- MongoDB自带了一个简单但功能强大的JavaScript shell,可用于管理MongoDB的实例或数据操作。
文档
文档就是键值对的一个有序集。
例如:{"greeting":"Hello,world!","foo":3}
文档中的值可以是多种不同的数据类型(甚至可以是一个完整的内嵌文档)。
文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
- 键不能含有\0(空字符)。这个字符用于表示键的结尾。
- .和$具有特殊意义,只能在特定环境下使用。通常,这两个字符是被保留的。
MongoDB不但区分类型,而且区分大小写,例:
下面两组均不相同:
{"foo":3}
{"foo":"3"}
/*-------------------------*/
{"foo":3}
{"Foo":3}
MongoDB的文档不能有重复的键。
集合
集合就是一组文档。
动态模式
集合是动态模式的。这意味着一个集合里面的文档可以是各式各样的。
“既然没有必要区分不同类型文档的模式,为什么还要使用多个集合呢?”:
- 如果把各种各样的文档不加区分地放在同一个集合里,无论对开发者还是对管理员来说都将是噩梦。
- 在一个集合里查询特定类型的文档在速度上页很不划算,分开查询多个集合要快得多。
- 把同种类型的文档放在一个集合里,数据会更加集中。
- 创建索引时,需要使用文档的附加结构(特别是创建唯一索引时)。索引是按照集合来定义的。在一个集合中只放入一种类型的文档,可以更有效地对集合进行索引。
命名
集合名可以是满足下列条件的任意UTF-8
- 集合名不能是空字符串(“”)。
- 集合名不能包含\0字符(空字符),这个字符表示集合名的结束。
- 集合名不能以”system.”开头,这是为系统集合保留的前缀。
- 用户创建的集合不能在集合名中包含保留字符”$”。引文某些系统生成的集合中包含$,很多驱动程序确实支持在集合名里包含该字符。除非你要访问这种系统创建的集合,否则不应该在集合名中包含$。
子集合
组织集合的一种惯例是使用”.”分隔不同命名空间的集合。
数据库
在MongoDB中,多个文档组成集合,而多个集合可以组成数据库。一个MongoDB实例可以承载多个数据库,每个数据库拥有0个或者多个集合。每个数据库都由独立的权限,即便是在磁盘上,不同的数据库也放置在不同的文件中。按照经验,我们将有关一个应用程序的所有数据都存储在同一个数据库中。要想在同一个MongoDB服务器上存放多个应用程序或者用户的数据,就需要使用不同的数据库。
数据库通过名称来识别,数据库名可以是满足以下条件的任意UTF-8字符串:
- 不能是空字符串(“”)
- 不得含有/,\,.,",*,<,>,:,|,?,$ (一个空格),\0
。基本上,只能使用ASCII中的字母和数字
- 数据库名区分大小写,即便是在不去放大小写的文件系统中也是如此。简单起见,数据库名应全部小写。
- 数据库名最多为64字节。
另外,有一些数据库名是保留的,可以直接访问这些有特殊语义的数据库。这些数据库如下所示。
- admin
从身份验证的角度来说,这是”root”数据库。如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。再者,一些特殊的服务器端命令也只能从admin数据库运行,如列出所有数据库或关闭服务器。
- local
- config
启动MongoDB
MongoDB shell简介
这是JavaScript shell
运行shell
mongo
MongoDB客户端
启动时,shell会连到MongoDB服务器的test数据库,并将数据库连接赋值给全局变量db。
如果想要查看db当前指向哪个数据库,可以使用db命令:
> db
test
选择数据库:
> use foobar
switched to db foobar
shell中的基本操作
创建
insert函数可将一个文档添加到集合中。例:
{
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2014-11-11T10:39:47.464Z")
}
> db.blog.insert(post)
WriteResult({ "nInserted" : 1 })
//结果
> db.blog.find()
{ "_id" : ObjectId("5461e78066f397ec9d6bf459"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2014-11-11T10:39:47.464Z") }
读取
find和findOne方法可以用于查询集合中的文档。若只想查看一个文档,可用findOne:
> db.blog.findOne()
{
"_id" : ObjectId("5461e78066f397ec9d6bf459"),
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2014-11-11T10:39:47.464Z")
}
find和findOne可以接受一个查询文档作为限定条件。这样就可以查询滏阳河一定条件的文档。使用find时,shell会自动显示最多20个匹配的文档,也可获取更多文档。
更新
使用update。update接受(至少)两个参数:
1. 限定条件
2. 新的文档
> db.blog.update({title:"My Blog Post"},post)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("5461e78066f397ec9d6bf459"), "title" : "My Blog Post", "content" : "Here's my blog post.", "date" : ISODate("2014-11-11T10:39:47.464Z"), "comments" : [ ] }
删除
使用remove方法,如果需要全部删除则使用remove(“”)
> db.blog.remove({title:"My Blog Post"})
WriteResult({ "nRemoved" : 1 })
> db.blog.find()
/*---------------*/
> db.blog.remove("")
WriteResult({ "nRemoved" : 1 })
> db.blog.find()
数据类型
基本数据类型
- null
- boolean
- number
- string
- date
- 数组
- 内嵌文档
- 对象id
- 二进制数据
- 代码
查询和文档中可以包含任意JavaScript代码
日期
使用new Date(...)
数组
这里的数组可以包含不同的数据类型的元素
{"things":["pie",3.14]}
内嵌文档
{
"name":"John Doe",
"address":{
"city":"Anytown",
"state":"NY"
}
}
_id和ObjectId
是在一个集合中每个文档的唯一标识。
生成方式看书p20
使用MongoDB shell
可以连接到其他机器的MongoDB上(服务器)
shell小贴士
help
使用shell执行脚本
mongo script1.js script2.js
mongo shell会依次执行传入的脚本,然后退出。
也可以使用load()函数,从交互式shell中运行脚本:
load("script1.js")
在脚本中可以访问db变量,以及其他全局变量。然而,shell辅助函数(比如”use db”和”show collections”)不可以在文件中使用。这些辅助函数都有对应的JavaScript函数。
创建.mongorc.js文件
在用户主目录下创建一个名为.mongorc.js的文件,这是一个before文件,最常见的用途之一是移除那些比较”危险”的shell辅助函数。可以在这里集中重写这些方法。例如
var no = function() {
print("Not on my watch.");
};
// Prevent dropping databases
db.dropDatabase = DB.prototype.dropDatabase = no;
// Prevent dropping collections
DBCollection.prototype.drop = no;
// Prevent dropping indexes
DBCollection.prototype.dropIndex = no;
改变数据库函数时,要确保同时对db变量和DB原型进行改变(如上例)。如果只改变了其中一个,那么db变量可能没有改变,或者这些改变在新使用的所有数据库(运行use anotherDB命令)中都不会生效。
如果在启动shell时指定–norc参数,就可以禁止加载.mongorc.js。
定制shell提示
将prompt变量设为一个字符串或者函数,就可以重写默认的shell提示。
例如,显示当前使用的数据库:
prompt = function() {
if (typeof db == 'undefined') {
return '(nodb)> ';
}
// Check the last db operation
try {
db.runCommand({getLastError:1});
}
catch (e) {
print(e);
}
return db+"> ";
};
可以在.mongorc.js中定制自己想要的提示。
编辑复合变量
集合命名注意事项
创建,更新和删除文档
插入并保存文档
db.mycollection.insert({"key":"value"})
这个操作会给文档自动增加一个”_id”键(要是原来没有的话),然后将其保存到MongoDB中。
批量插入
暂时找不到2.6批量插入的方法。
插入效验
文档大小必须小于16MB
删除文档
db.mycollection.remove({})/remove{"key":"value"}
删除速度
如果要清空整个集合,使用drop直接删除集合会更快(然后在这个空集合上重建各项索引)。
更新文档
文档存入数据库以后,就可以使用update方法来更新它。update有两个参数:
1. 查询文档,用于定位需要更新的目标文档
2. 修改器(modifier)文档,用于说明要对找到的文档进行哪些修改。
更新操作是不可分割的:若是两个更新同时发生,先到达服务器的先执行,接着执行另外一个。
文档替换
最简单的更新就是用一个新文档完全替换匹配的文档。这适用于进行大规模模式迁移的情况。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing" }
> var name = db.blog.findOne()
> name
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing" }
> name.age=22
22
> db.blog.update({"name":"Bing"},name);
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 22 }
>
使用”_id”作为查询条件比使用随机字段速度更快,因为是通过”_id”建立的索引。
使用修改器
通常文档只会有一部分要更新。可以使用原子性的更新修改器(update modifier),指定对文档中的某些字段进行更新。
- $inc 修改器增加某个键的值,若该键不存在则创建一个只能用于整型,长整型或双精度浮点型的值
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 22 }
> db.blog.update({"name":"Bing"},
... {"$inc":{"age":-1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21 }
- $set 指定一个字段的值。如果这个字段不存在,则创建它。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21 }
> db.blog.update({"name":"Bing"},
... {"$set":{"favorite moive in the year":"星际穿越"}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21, "favorite moive in the year" : "星际穿越" }
- $unset 将一个键完全删除
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "age" : 21, "favorite moive in the year" : "星际穿越" }
> db.blog.update({"name":"Bing"},
... {"$unset":{"age":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越" }
- $push 向已有的数组末尾加入一个元素,要是没有就创建一个新的数组。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越" }
> db.blog.update({"name":"Bing"},
... {"$push":{"comments":{"content":"getArray"}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" } ] }
> db.blog.update({"name":"Bing"}, {"$push":{"comments":{"content":"2"}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ] }
- $each $push的简单使用形式,可以通过一次”$push”操作添加多个值
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ] }
> db.blog.update({"name":"Bing"},
... {"$push":{"hourly":{"$each":[563.321,532.0,234]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 563.321, 532, 234 ] }
$slice 和$push组合使用,可以保证数组不会超出设定号的最大长度,这实际上就得到了一个最多包含N个元素的数组。$slice的值必须是负整数。不能智将#slice或者$sort与$push配合使用,且必须使用$each。
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 563.321, 532, 234, 32, 43, 53, 1, 2345, 322, 0.23, 324 ] }
> db.blog.update(
... {name:"Bing"},
... {
... $push:{
... hourly:{
... $each:[3,4,5],
... $slice:-5
... }
... }
... }
... )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 324, 22.2, 3, 4, 5 ] }
/*---------------------------------------------------------*/
> db.blog.update( {name:"Bing"},
...{ $push:{ hourly:{ $each:[3,4,5], $slice:-5,$sort:-1 } } } )
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
- $ne 保证数组内的元素不重复
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
> db.blog.update({name:{"$ne":"Bing"}},
...{$push:{name:"Bing"}})
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
- $addToSet 作用与$ne一致,但是它可以与$each组合使用
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3 ] }
> db.blog.update({name:"Bing"},
... {$addToSet:{
... hourly:{
... $each:[3,53,0,66]}}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "comments" : [ { "content" : "getArray" }, { "content" : "2" } ], "hourly" : [ 5, 4, 4, 3, 3, 53, 0, 66 ] }
$pull 删除元素
db.blog.update({name:"Bing"},{$pull:{comments:{}}})
$pop 删除元素
{$pop:{"key":1}}
从数组末尾删除,{$pop:{"key":-1}}
从数组开头删除基于位置的数组修改器,数组下标都是以0开头的,可以将下标直接作为键来选择元素
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "hourly" : [ 6, 4, 4, 3, 3, 53, 0, 66 ] }
> db.blog.update({name:"Bing"}, {$inc:{"hourly.0":1}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "hourly" : [ 7, 4, 4, 3, 3, 53, 0, 66 ] }
upsert
upsert是一种特殊的更新。要是没有找到符合更新条件的文档,就会以这个条件和更新文档为基础创建一个新的文档。如果找到了匹配的文档,则正常更新。当update第三个参数为true
时代表upsert。
更新多个文档
update第四个参数为true
代表更新多个文档。
返回被更新的文档
通过findAndModify得到被更新的文档
API
db.collection.findAndModify({
query: <document>,
sort: <document>,
remove: <boolean>,
update: <document>,
new: <boolean>,
fields: <document>,
upsert: <boolean>
});
> db.blog.findAndModify({query: { name: "Bing" },update: { $inc: { score: 1 } },upsert: true})
{
"_id" : ObjectId("54641eeaa5fa343b3e09e590"),
"name" : "Bing",
"favorite moive in the year" : "星际穿越",
"hourly" : [
7,
4,
4,
3,
3,
53,
0,
66
]
}
> db.blog.find()
{ "_id" : ObjectId("54641eeaa5fa343b3e09e590"), "name" : "Bing", "favorite moive in the year" : "星际穿越", "hourly" : [ 7, 4, 4, 3, 3, 53, 0, 66 ], "score" : 1 }
写入安全机制
写入安全(Write Concern)是一种客户端设置,用于控制写入的安全级别。
查询
find()间接
db.collection.find(<criteria>, <projection>)
criteria相当于where语句,projection相当于投影
eg:
//criteria
db.bios.find()
db.bios.find( { _id: 5 } )
db.bios.find(
{
_id: { $in: [ 5, ObjectId("507c35dd8fada716c89d0013") ] }
}
)
db.collection.find( { field: { $gt: value1, $lt: value2 } } );
db.bios.find(
{
awards: {
$elemMatch: {
award: "Turing Award",
year: { $gt: 1980 }
}
}
}
)
//projection
db.products.find( { qty: { $gt: 25 } }, { item: 1, qty: 1 } )
db.bios.find( { }, { name: 1, contribs: 1 } )
db.products.find( { qty: { $gt: 25 } }, { _id: 0, qty: 0 } )
db.bios.find(
{ },
{
_id: 0,
'name.last': 1,
contribs: { $slice: 2 }
}
)
现在介绍criteria有什么:
- $lt
: <
- $lte
: <=
- $gt
: >
- $gte
: >=
- $in
: 相当于sql中的in,可以用来查询一个键的多个值
- $nin
: 与in相反,not in
- $ne
: !=
- $or
: or
db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725, 542, 390]}},{"winner" : true}]})
- $and
: and
db.users.find({"$and" : [{"x" : {"$lt" : 1}}, {"x" : 4}]})
匹配那些”x”字段的值小于1并且等于4的文档。但不如以下效率高:
db.users.find({"x" : {"$lt" : 1, "$in" : [4]}})
- 可以查询null
db.c.find({"y" : null})
- 可以查询正则表达式
db.users.find({"name" : /joe/i})
i是忽略大小写,/这里面是正则表达式内容/
- $exists
db.inventory.find( { qty: { $exists: true, $nin: [ 5, 15 ] } } )
选择不等于5和15的
- $all
通过多个元素来匹配数组
db.food.find({fruit : {$all : ["apple", "banana"]}})
{"_id" : 1, "fruit" : ["apple", "banana", "peach"]}
{"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}
$size
查询特定长度的数组- 更多
现在介绍projection有什么:
最下面
where查询
游标
数据库使用游标返回find的执行结果。客户端对游标的实现通常能够对最终结果进行有效的控制。可以限制结果的数量,略过部分结果,根据任意键按任意顺序的组合对结果进行各种排序,或者是执行其他一些强大的操作。
创建一个游标(先创建文档吧):
> for(i=0; i<100; i++) {
...
db.collection.insert({x : i});
... }
> var cursor = db.collection.find();
这么做的好擦是可以一次查看一条结果。
> while(cursor.hasNext()){
... obj=cursor.next();
... print(obj.x);
... }
cursor.hasNext()
检查是否有后续结果存在,然后用cursor.next()
获取它
它还实现了迭代器接口:
cursor.forEach(
function(x){
print(x.x)
})
limit,skip,sort
db.test.find().limit(3)
db.test.find().skip(3)//略过
db.test.find().sort({x:-1})//1正序,-1倒序