文档是Mongodb中数据的基本单元,非常类似于关系数据库管理系统中的行(但是比行要复杂得多)。
类似地,集合可以被看做是没有模式的表。
Mongodb单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限。
Mongodb自带简洁但功能强大的JavaScript Shell,这个工具对于管理Mongodb实例和操作数据作用非常大。
每一个文档都有一个特殊的键“_id”,它在文档所处的集合中是唯一的。
2.1 文档
文档是Mongodb的核心概念,多个键及其关联的值有序地放置在一起便是文档。
在JavaScript里面,文档表示为对象:
以下的文档也是不同的
文档中的键/值对是有序的。通常文档中键的顺序并不重要。
文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型。
文档的键是字符串。除了少数例外情况,键可以使用任意UTF-8字符。
键不能含有\0(空字符),这个字符用来表示键的结尾
.和$有特别的意义,只有在特定环境下才能使用。
以下划线“_”开头的键是保留的。
Mongodb不但区分类型,也区分大小写。例如,下面两个文档是不同的:
集合就是一组文档,如果说Mongodb中的文档类似于关系型数据库中的行,那么集合就如同表。
2.2.1 无模式
集合是无模式的。这就意味着一个集合里面的文档可以是各式各样的。
把同种类型的文档放入同一个集合里面,可以使索引更加有效。
2.2.2 命名
集合名可以是满足下列条件的任意UTF-8字符串
集合名不能是空字符串“”
集合名不能含有\0字符(空字符),这个字符表示集合名的结尾
集合名不能以“system.”开头,这是为系统集合保留的前缀。例如system.users这个集合保存着数据库的用户信息。
用户创建的集合名字不能含有保留字符$.
子集合
组织集合的一种惯例是使用"."字符分开的按命名空间划分的子集合。例如,带有博客功能的应用可能包含两个集合,分别是blog.posts和blog.authors.
这样做的目的只是为了使组织结构更好些,也就是说blog这个集合(这里根本就不需要存在)及其子集合没有任何关系。
GridFS是一种存储大文件的协议,使用子集合来存储文件的元数据,这样就与内容块分开了。
Mongodb的web控制台通过子集合的方式将数据组织在DBTOP部分
在Mongodb中使用子集合来组织数据是很好的方法。
2.3 数据库
Mongodb中多个文档组成集合,同样多个集合可以组成数据库。一个Mongodb实例可以承载多个数据库。将一个应用的所有数据都存储在同一个数据库中的作法就很好。
和集合一样,数据库也通过名字来标识。
不能是空字符串("")
不能含有''(空格)、.、$、/、\和\0(空字符)
应全部小写
最多64个字节
有一些数据库名是保留的
admin
从权限的角度来看,这是"root"数据库
local
这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合
config
当Mongodb用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
把数据库的名字放到集合名前面,得到就是集合的全限定名,成为命名空间。命名空间的长度不得超过121字节,在实际使用当中应该小于100字节
2.4 启动Mongodb
Mongodb一般作为网络服务器来运行,客户端可以连接到服务器并执行操作。
要启动该服务器,需要运行mongodb可执行文件。
$ ./mongodb
$mongodb.exe
mongodb在没有参数的情况下会使用默认数据目录/data/db(在windows下是C:\data\db),并使用27017端口。在启动Mongodb前,创建数据目录(比如mkdir -p /data/db),并确保对该目录有写权限。
默认情况下,Mongodb是监听27017端口。
mongodb还会启动一个非常基本的HTTP服务器,监听数据比主端口号高1000的端口,也就是28017端口。这也就意味着你可以通过浏览器访问http://localhost:28017来获取数据库的管理信息
在启动服务的shell下可以键入Crtl+C来完全停止Mongodb的运行。
2.5 Mongodb Shell
MongoDB自带一个JavaScript Shell,可以从命令行与MongoDB实例交互
2.5.1 运行shell
运行mongo启动shell
> x=200
200
> x/5
40
>
还可以充分利用JavaScript的标准库> Math.sin(Math.PI/2);
1
> new Date("2010/1/1");
ISODate("2009-12-31T16:00:00Z")
> "Hello,World!".replace("World","MongoDB");
Hello,MongoDB!
也可以定义和调用JavaScript函数> function factorial(n){
... if(n<=1) return 1;
... return n*factorial(n-1);
... }
> factorial(5);
120
2.5.2 MongoDB客户端虽然能运行任意JavaScript程序很酷,但shell的真正威力还在于它是一个独立的MongoDB客户端。开启的时候,shell会连到MongoDB服务器的test数据库,并将这个数据库连接赋值给全局变量db。这个变量是通过shell访问MongoDB的主要入口点。shell还有些非JavaScript语法的扩展,是为了方便习惯于sql Shell的用户而添加的。如:选择要使用的数据库> db
test
> use foobar
switched to db foobar
> db
foobar
>
因为这是一个JavaScript shell,所以键入一个变量会将变量的值转换为字符串(这里就是数据库名)并打印出来。可以通过db变量来访问其中的集合。例如db.baz返回当前数据库的baz集合。
2.5.3 shell中的基本操作
在shell查看操作数据会用到4个基本操作:创建、读取、更新和删除(CRUD).
1 创建insert函数添加一个文档到集合里面。例如,假设要存储一篇博客文章。
首先,创建一个局部变量post,内容是代表文档的JavaScript对象。这里会有“title”,“content”和“date”(发表日期)几个键
> post = {"title":"My Blog Post",
... "content":"Here's my blog post.",
... "date":new Date()}
{
"title" : "My Blog Post",
"content" : "Here's my blog post.",
"date" : ISODate("2012-08-26T14:50:26.687Z")
}
这个对象是有效的MongoDB文档,所以可以用insert方法将其保存到blog集合中
> db.blog.insert(post)
> db.blog.find()
{ "_id" : ObjectId("503a37d2ea5c5826dc1e84fa"),"title" : "My Blog Post","content" : "Here's my blog post.","date" : ISODate("2012-08-26T14:50:26.687Z") }
>
除了我们输入的键/值对都完整地被保存下来,还有一个额外添加的键“_id”。
2 读取find会返回集合里面所有的文档。若只想查看一个文档,可以用findOne:
> db.blog.findOne()
{
"_id" : ObjectId("503a37d2ea5c5826dc1e84fa"),
"title" : "My Blog Post",
"date" : ISODate("2012-08-26T14:50:26.687Z")
}
find和findOne可以接受查询文档形式的限定条件。这将通过查询限制匹配的文档。使用find时,shell自动显示最多20个匹配的文档,但可以获取更多的文档。
3 更新如果要更改博客文章,就要用到update了。update接受(至少)两个参数;第一个是要更新文档的限定条件,第二个是新的文档。假设决定给我们先前写的文章增加评论内容,则需要增加一个新的键,对应的值是存放评论的数组。
> post.comments = []
[ ]
然后执行update操作,用新版本的文档替换标题为"My Blog Post"的文章:
> db.blog.update({title:"My Blog Post"},post)
> db.blog.find()
{ "_id" : ObjectId("503a37d2ea5c5826dc1e84fa"),"date" : ISODate("2012-08-26T14:50:26.687Z"),"comments" : [ ] }
> db.blog.findOne()
{
"_id" : ObjectId("503a37d2ea5c5826dc1e84fa"),
"date" : ISODate("2012-08-26T14:50:26.687Z"),
"comments" : [ ]
}
文档已经有了"comments"键。再用find查看一下,可以看到新的键:如上图。
4 删除remove用来从数据库中永久性的删除文档。在不使用参数进行调用的情况下,它会删除一个集合内的所有文档。它也可以接受一个文档以指定限定条件。例如,下面的命令会删除我们刚刚创建的文章:
> db.blog.remove({title:"My Blog Post"})
> db.blog.findOne()
null
> db.blog.find()
2.5.4 使用shell窍门由于mongo是个JavaScript shell,通过在线查看JavaScript的文档能获得很多帮助。shell本身内置了帮助文档,可以通过help命令查看。
> help
db.help() help on db methods
db.mycoll.help() help on collection methods
rs.help() help on replica set methods
help admin administrative help
help connect connecting to a db help
help keys key shortcuts
help misc misc things to know
help mr mapreduce
show dbs show database names
show collections show collections in current database
show users show users in current database
show profile show most recent system.profile entries with time >= 1ms
show logs show the accessible logger names
show log [name] prints out the last segment of log in memory,'global' is default
use <db_name> set current database
db.foo.find() list objects in collection foo
db.foo.find( { a : 1 } ) list objects in foo where a == 1
it result of the last line evaluated; use to further iterate
DBQuery.shellBatchSize = x set default number of items to display on shell
exit quit the mongo shell
>
使用db.help()可以查看数据库级别的命令的帮助
> db.help()
DB methods:
db.addUser(username,password[,readOnly=false])
db.auth(username,password)
db.cloneDatabase(fromhost)
db.commandHelp(name) returns the help for the command
db.copyDatabase(fromdb,todb,fromhost)
db.createCollection(name,{ size : ...,capped : ...,max : ... } )
db.currentOp() displays the current operation in the db
db.dropDatabase()
db.eval(func,args) run code server-side
db.getCollection(cname) same as db['cname'] or db.cname
db.getCollectionNames()
db.getLastError() - just returns the err msg string
db.getLastErrorObj() - return full status object
db.getMongo() get the server connection object
db.getMongo().setSlaveOk() allow this connection to read from the nonmaster member of a replica pair
db.getName()
db.getPrevError()
db.getProfilingLevel() - deprecated
db.getProfilingStatus() - returns if profiling is on and slow threshold
db.getReplicationInfo()
db.getSiblingDB(name) get the db at the same server as this one
db.isMaster() check replica primary status
db.killOp(opid) kills the current operation in the db
db.listCommands() lists all the db commands
db.logout()
db.printCollectionStats()
db.printReplicationInfo()
db.printSlaveReplicationInfo()
db.printShardingStatus()
db.removeUser(username)
db.repairDatabase()
db.resetError()
db.runCommand(cmdObj) run a database command. if cmdObj is a string,turns it into { cmdObj : 1 }
db.serverStatus()
db.setProfilingLevel(level,<slowms>) 0=off 1=slow 2=all
db.shutdownServer()
db.stats()
db.version() current version of the server
db.getMongo().setSlaveOk() allow queries on a replication slave server
db.fsyncLock() flush data to disk and lock server for backups
db.fsyncUnock() unlocks server following a db.fsyncLock()
>
集合的相关帮助可以通过db.foo.help()来查看
> db.foo.help()
DBCollection help
db.foo.find().help() - show DBCursor help
db.foo.count()
db.foo.dataSize()
db.foo.distinct( key ) - eg. db.foo.distinct( 'x' )
db.foo.drop() drop the collection
db.foo.dropIndex(name)
db.foo.dropIndexes()
db.foo.ensureIndex(keypattern[,options]) - options is an object with these possible fields: name,unique,dropDups
db.foo.reIndex()
db.foo.find([query],[fields]) - query is an optional query filter. fields is optional set of fields to return.
e.g. db.foo.find( {x:77},{name:1,x:1} )
db.foo.find(...).count()
db.foo.find(...).limit(n)
db.foo.find(...).skip(n)
db.foo.find(...).sort(...)
db.foo.findOne([query])
db.foo.findAndModify( { update : ...,remove : bool [,query: {},sort: {},'new': false] } )
db.foo.getDB() get DB object associated with collection
db.foo.getIndexes()
db.foo.group( { key : ...,initial: ...,reduce : ...[,cond: ...] } )
db.foo.mapReduce( mapFunction,reduceFunction,<optional params> )
db.foo.remove(query)
db.foo.renameCollection( newName,<dropTarget> ) renames the collection.
db.foo.runCommand( name,<options> ) runs a db command with the given name where the first param is the collection name
db.foo.save(obj)
db.foo.stats()
db.foo.storageSize() - includes free space allocated to this collection
db.foo.totalIndexSize() - size in bytes of all the indexes
db.foo.totalSize() - storage allocated for all data and indexes
db.foo.update(query,object[,upsert_bool,multi_bool])
db.foo.validate( <full> ) - SLOW
db.foo.getShardVersion() - only for use with sharding
db.foo.getShardDistribution() - prints statistics about data distribution in the cluster
>
有个了解函数公用的技巧,就是在输入的时候不要输括号。这样就会显示该函数的JavaScript源代码。
> db.foo.update
function (query,obj,upsert,multi) {
assert(query,"need a query");
assert(obj,"need an object");
var firstKey = null;
for (var k in obj) {
firstKey = k;
break;
}
if (firstKey != null && firstKey[0] == "$") {
this._validateObject(obj);
} else {
this._validateForStorage(obj);
}
this._mongo.update(this._fullName,query,upsert ? true : false,multi ? true : false);
}
>
要查看shell提供的所有自动生成的JavaScript函数API文档,可以访问http://api.mongodb.org/js蹩脚的集合名使用db.集合名的方式来访问集合一般不会有问题,但如果集合名恰好是数据库类的一个属性就有问题了,
例如,要访问version这个集合,使用db.version就不行,因为db.version是个数据库函数(它返回正在运行的MongoDB服务器的版本)
> db.version
function () {
return this.serverBuildInfo().version;
}
当JavaScript只有在db中找不到指定的属性时,才会将其作为集合返回。当有属性与目标集合同名时,可以使用getCollection函数:
> db.getCollection("version");
foobar.version
要查看名称中含有无效JavaScript字符的集合,这个函数也可以派上用场。比如foo-bar是个有效的集合名,但是在JavaScript中就变成了变量相减了。
通过db.getCollection("foo-bar")可以得到foo-bar集合在JavaScript中,x.y与x['y']完全等价。这就意味着不但可以直呼其名,也可以使用变量来访问子集合。也就是说,当需要对blog的每个子集合执行操作时,只要像下面这样迭代就好了。
2.6 数据类型
2.6.1 基本数据类型MongoDB的文档类似于JSON,在概念上和JavaScript中的对象神似。JSON是一种简单的表示数据的方式,仅包含6种数据类型。只有null、布尔、数字、字符串、数组和对象几种类型。
MongoDB在保留JSON基本的键/值对特性的基础上,添加了其他一些数据类型。
null
null用于表示空值或者不存在的字段{"x":null}
布尔
布尔类型有两个值'true'和'false'{"x",true}
32位整数
shell中这个类型不可用,前面提到,JavaScript仅支持64位浮点数,所以32位整数会被自动转换
64位整数
shell也不支持这个类型,shell会使用一个特殊的内嵌文档来显示64位整数
64位浮点数
shell中的数字都是这种类型。下面是一个浮点数{"x":3.14}这个也是浮点数:{"x":3}
字符串
UTF-8字符串都可以表示为字符串类型的数据:
{"x":"foobar"}
符号
shell不支持这种类型。shell将数据库里面的符号类型转换成字符串
对象id
对象id是文档的12字节的唯一ID
{"x":ObjectId()}
日期
日期类型存储的是从标准纪元开始的毫秒数。不存储时区:
{“x”:new Date()}
正则表达式
文档中可以包含正则表达式,采用JavaScript的正则表达式语法:
{"x":/foobar/i}
文档中还可以包含JavaScript代码:{"x":function(){/*...*/}}
二进制数据
二进制数据可以由任意字节的串组成。不过shell中服务使用。
最大值
BSON包括一个特殊类型,表示可能的最大值。shell中没有这个类型
最小值
BSON包括一个特殊类型,表示可能的最小值,shell中没有这个类型
未定义
文档中也可以使用未定义类型(JavaScript中null和undefined是不同的类型)
{"x":undefined}
数组:值的集合或者列表可以表示成数组
{"x":["a","b","c"]}
内嵌文档文档可以包含别动文档,也可以作为值嵌入到父文档中
{"x":{"foo":"bar"}}
2.6.2 数字
JavaScript中只有一种“数字”类型。因为MongoDB中有3种数字类型(32位整数、64位整数和64位浮点数),shell必须绕过JavaScript限制。
默认情况下,shell中的数字都被MongoDB当做是双精度数。
这就意味着如果你从数据库中获得的是一个32位整数,修改文档后,将文档存回数据库的时候,这个整数也被转换成了浮点数,及时保持这个整数原封不动也会这样。
所以明智的作法是尽量不要在shell下覆盖整个文档。
数字只能表示为双精度数(64位浮点数)
2.6.3 日期
在JavaScript中,Date对象用做MongoDB的日期类型,创建一个新的Date对象时,通常会调用new Date(...)而不只是Date(...)
shell中的日期显示时使用本地时区设置,但是,日期在数据中是以从标准纪元开始的毫秒数的形式存储的,没有与之相关的时区信息。
2.6.4 数组
数组是一组值,既可以作为有序对象(像列表、栈或队列)来操作,也可以作为无序对象(像集合)操作。
在下面的文档中,"things"这个键的值就是一个数组
数组可以包含不同数据类型的元素
2.6.5 内嵌文档
内嵌文档就是把整个MongoDB文档作为另一个文档中键的一个值。
例如,用文档来表示一个人,同时还要保存他的地址,可以将地址内嵌到"add-ress"文档中
{
“name”:"John Doe",
"address":{
"street":"123 Park Street",
"city":"Anytown",
"state":"NY"
}
}
在关系型数据库中,以上文档一般会被拆分成两个表"people"和“address”中的两个行。在MongoDB中,就可以将地址文档直接嵌入人员文档中。
2.6.6 _id和ObjectId
MongoDB中存储的文档必须有一个"_id"键,这个键的值可以是任何类型的,默认是个ObjectId对象。在一个集合里面,每一个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标示。
1 ObjectId是"_id"的默认类型
ObjectId使用12字节的存储空间,每个字节两位16进制数字,是一个24位的字符串。
前4个字节是从标准纪元开始的时间戳,单位为秒,
时间戳,与随后的5个字节组合起来,提供了秒级别的唯一性
由于时间戳在前,这意味着ObjectId大致会按照插入的顺序排列。
中4个字节也隐含了文档创建的时间,绝大多数驱动都会公开一个方法从ObjectId获取这个信息。
接下来的3个字节是所在主机的唯一标识符
为了确保在同一台机器上并发的多个线程产生的ObjectId是唯一的,接下来的两个字节来自产生ObjectId的进程标识符(PID)
前9个字节保证了同一秒钟不同机器不同进程产生的ObjectId是唯一的。后3个字节就是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId也是不一样的。
如果插入文档的时候没有“_id”键,系统会自动帮你创建一个。
可以由MongoDB服务器来做这件事情,但通常会在客户端由驱动程序完成。
虽然ObjectId设置成轻量型的,易于生成,但是毕竟生成的时候还是产生开销。
在客户端生成提现了MongoDB的设计理念:能从服务器端转移到驱动程序来做的事,就尽量转移。将事物交给客户端来处理,就减轻了数据库扩展的负担。
在客户端生成ObjectId,驱动程序能够提供更加丰富的API