what is MongoDB ?

面向文档的数据库

  • 不再有行的概念,不再有预定义模式

  • 易于拓展

  • 丰富的功能

    • 索引

    • 聚合

    • 特殊的集合类型

    • 文件存储

  • 高性能

  • 可以一个示实例拥有多个相互独立的数据库,每个数据库拥有自己的集合

文档(document)

数据的基本单元,键值对的有序集

  • 文档格式:
    { “foo” : “3” }

  • 每个文档都有一个特殊的键 "_id" ,这个键在文档中是唯一的

  • 文档的值可以是不同的类型,使用类似 json 的语法来存储数据

  • 文档的键是字符串,除了少数情况,键可以使用任意 UTF-8 字符

  • 键不能含有 \0(空字符),这个符号用来表示键的结尾

  • .$ 有特殊含义,只能在特定情况下使用

  • MongoDB不但区分·类别,而且区分大小写

  • 文档中不能有重复的键

  • 文档的键值对是有序的,但通常字段顺序·不重要

集合(collection)

一组文档的集合,可以看做有动态模式的表

动态模式

  • 具有动态的模式的集合中,文档可以是各式各样的

  • 例如下面两个文档可以存储在同一个集合里面:
    { “greeting” : “Hello, world” } , { “foo” : 5 }

  • 把同类型的文档放在一个集合里,数据会更加集中

  • 在集合中只放入一种类型的文档,可以更有效地对集合进行索引

命名

集合使用命名进行标识,集合名可以是以下任意UTF-8字符串

  • 集合名不能为空字符串 ""

  • 集合名不能包含字符 \0,它表示集合名的结束

  • 集合名不能以 stystem. 开头,这是因为它是系统保留的前缀

  • 集合名不能包含 $ 符号,因为系统生成的集合中包含 $

子集合

使用 . 分隔命名空间的子集合,用来高效、简洁的组织数据

数据库

MongoDB 中,一个实例可以承载多个数据库,每个数据库可以有零个或多个集合

每个数据库拥有独立权限,不同的数据库存储在不同的文件中

数据库名区分大小写,简单起见,数据库名全部小写,其它与集合命名方式类似

MongoDB 的保留数据库名:

  • admin
    用户权限,特定服务端命令

  • local
    不可复制,用于储存服务器本地集合

  • config
    用于分片设置

启动MongoDB

  1. $ mongod --dbpath E:\mongodb\

终止 在 shell 中按下 Ctrl + C

MongoDB shell

运行

  1. $ mongo

连接其他计算机上的mongod实例

$ mongo host:30000/myDB

不连接到任何mongod实例

$ mongo --nodb
  • shell 可以运行绝大多数的 JavaScript 程序

  • 多行输入时会进入未输入完成的状态,连续三次回车强制退出

MongoDB客户端

  • MongoDB shell 是一个独立运行的 MongoDB 客户端

  • 启动时,shell 连接到 test 数据库,并将数据库连接赋值给全局变量 db

显示当前数据库

> db
test

显示所有数据库

> show databases

使用数据库user

> use user

shell的基本操作

创建

> post = {
    "content":"here is my blog",
    "date":new Date()
}

增加

> post = { "content":"here is my blog",  "date":new Date() }
> db.blog.insert(post)

查找

所有

> db.blog.find()

第一个

> db.blog.findOne()

更新

> db.blog.update({"content":"here is my blog"},"here")

删除

> db.blog.remove({"content":"here"})

基本数据结构

  • null
    null 用于表示空值或者不存在的字段

  • 布尔型
    布尔型有两个值 true 和 false

  • 数值

    • shell 默认使用 64 浮点数值

    • 整型数值可以使用 NumberInt()(4字节带符号整型)或者 NumberLog()(8字节带符号整型)

  • 字符型
    UTF-8 字符串

  • 日期
    新纪元以来的毫秒数,不存储时区

  • 正则表达式
    和 JavaScript 正则语法相同

  • 数组

  • 内嵌文档

{"x":{"foo":"bar"}}
  • 对象id
{"x":ObjectId()}
  • 二进制数据

  • 代码

{"x":function(){}}

_id 和 ObjectId

_id

  • MongoDB文档必须有一个”_id”键,这个键可以是任意类型,默认是一个ObjectId对象

  • 每个文档都有一个唯一的”_id”作为它的标识,同一个集合中的”_id”不能相同

ObjectId

  • 由24个16位进制数字组成的字符串

  • 前四位-时间戳 五到八-机器码 七和八位-进程标识符(PID) 九到十一-计数器

集合命名注意事项

  • db.version 是 db 的一个方法,用来返回当前服务器的版本

  • 访问 version 数据库必须使用 db.getCollection(“version”);

可以使用对象访问特殊命名集合或数据库

例如 db.blog[collection_name] db[db_name].find()

删除更新、和删除文档

插入新的文档

> db.foo.insertOne({"bar":"baz"})

这个操作会新建一个文档,自动新增 “_id” 键,然后保存到 MongoDB 中

批量插入

> db.foo.insert({"_id":1},{"_id":2},{"_id":3})

如果其中有一个文档插入失败,这个文档及其之后的所有文档失败,之前的文档依旧插入

批量文档

> db.foo.remove({})

option 中可以传入条件,符合条件的数据将会被删除

删除集合

> db.foo.drop()

删除集合比删除所有文档的速度更快

文档替换

一个常见的问题:
查询条件匹配到了多个文档然后更新于第二个参数的存在就产生重复的 _id 值,数据库会抛出异常,所有文档都不会更新

正确的更新方法:

> db.people.update( { "_id" : ObjectId("4b3b9f67a1f631733d917a7c") } , joe )

使用文档修改器

  • $set修改器

设定一个键不存在就创建它,存在更新键对应的值,它还可以改变值的类型

  • $inc 修改器

增加已有键的值,键不存在就创建一个

必须是数字类型,不能增加其它类型的值,修改其它类型请使用 $set 或者数值修改器

  • $unset 修改器

删除一个键使用它

  • 数值修改器
    - `$push`已有数组末尾加入一个元素,没有就创建一个新数组
    
  • $each 批量更新数组
> db.foo.update({"_id":ObjectId("5bf7f5ab1fcca531779d012d")},{"$push":{"hourly":{"$each":[5,2,3,4]}}}
  • $slice 的值必须是负数,它会限制数组最多有多少元素

  • $addToSet 可以避免重复插入值

  • 删除数组中的元素
    特定元素

> db.foo.insert({"arr":["a","b"]})
> db.foo.update({},{"$pull":{"arr":"a"}})
  • 修改器速度

将文档插入数据库中时,依次插入的文档在磁盘上的位置是相邻的。
因此如果一个文档变大了,原来的位置放不下这个文档时,它会被移动到集合的另一个位置。
当 MongoDB 不得不移动一个文档时,它会创建一个,它会修改集合的填充因子(padding factor)
填充因子: MongoDB 为每个新文档预留的增长位置

查看填充因子

> db.coll.stats()

注意:
删除、修改、增加都要使用 $ 修改器,否则会将整个文档替换,例子:

> db.people.update( criteria , { "foo" : "bar" } )

这会使整个文档用 {“foo”:”bar”} 替换

提高磁盘复用率

如果你的集合在进行插入和删除时会进行大量的移动或者是经常打乱数据,可以实用 usePowerOf2Sizes 选项提高磁盘复用率。

> db.runCommand({"collMod":collectionName,"usePowerOf2Sizes":true})

这个选项会导致之后所有的空间分配都是2的幂,使得空间分配不再这么高效。

upsert

如果没有找到找到符合条件的文档,就会以这个条件和更新文档为基础新建一个新的文档。如果找到了匹配的文档,则正常更新。不必预置集合,同一个代码既可以创建文档又可以更新文档。
> db.foo.update({ "url" : "/blog"}, {"$inc" : { "pageviews" : 1 }}, true)

update 的第三个参数就是个 upsert

$setOnInsert

创建文档同时创建字段为它赋值,之后所有的更新操作这个字段的值不再改变。

> db.foo.update({}, {"$setOnInsert" : { "createdAt" : new Date() }}, true)
通常不需要保留 createAt 创建时间这样的字段,因为 ObjectId 中包含了创建时的时间戳,但是在预置或者初始化计数器时或者不使用 ObjectId 的集合来说, `$setOnInsert` 是非常有用的

save shell

若文档不存在创建,存在更新。它只有一个参数:文档,如果包含  `_id` 键,save 会调用 upsert,否则会调用insert 。使用它可以快速、方便的对文档进行更新。
> var x = db.foo.findOne()
> x.num = 42
> db.foo.save()

更新多个文档

默认情况下,文档的更新只针对第一个匹配到的文档,多个条件符合时,其它文档不会改变。若想同时跟新多个文档可以将 update 的第四个参数设为 true
> db.users.update({"birthday":"10/13/1988"},{"$set":{"gift":"Happy Brithday!"}}, false , true)

这样就给所有生日是 1998 年 10 月 13 的用户添加了礼物 gift

如果想获得跟新了多少文档可以运行 getLastError 命令

> db.runCommand(getLastError:1)
{
    "err" : null,
    "updatedExisting" : true,
    "n" : 5,
    "ok" : true
}

返回被更新的文档

> db.runCommand("findAndModify ": "processes", "query" : {"status" : "READY"}, "sort":{"priority" : -1}, "update":{"$set" : "RUNNING"})
{
    "ok" : 1,
    "value" : {
       "_id" : ObjectId("4b3e7a18005cab32be6291f7"),
       "priority" : 1,
       "status" : "READY",
    }
}

返回的文档是更新前的值,但实际的文档依旧更新

写入安全机制

  • 应答式写入

数据库会给出响应,告诉你写入操作是否成功执行

  • 非应答式写入

不返回任何响应,无法得知写入是否成功

手动强制 shell 中进行检查,检查最后一次操作中的错误

> db.getLastError()

参考

[1] MongoDB权威指南