一、特点
学习一个东西,至少首先得知道它能做什么?适合做什么?有什么优缺点吧?
传统关系型数据库,遵循三大范式。即原子性、唯一性、每列与主键直接关联性。但是后来人们慢慢发现,不要把这些数据分散到多个表、节点或实体中,将这些信息收集到一个非规范化(也就是文档)的结构中会更有意义。尽管两个或两个以上的文档有可能会彼此产生关联,但是通常来讲,文档是独立的实体。能够按照这种方式优化并处理文档的数据库,我们称之为文档数据库。
设计MongoDB的初衷就是用作分布式数据库。
MongoDB 的优点:
1、性能优越。MongoDB在各方面的设计都旨在保持它的高性能,MongoDB能对文档进行动态填充(dynamic padding),也能预分配数据文件以利用额外的空间来换取稳定的性能。MongoDB把尽可能多的内存用作缓存(cache),试图为每次查询自动选择正确的索引。
2、丰富的数据类型。采用BSON形式存储。几乎你想要的数据类型是怎么样的,存在Mongo里面就会是什么样的。避免像关系型数据那样分table,然后使用的时候再join。
3、易于扩展。MongoDB的设计采用横向扩展。面向文档的数据模型使它能很容易地在多台服务器之间进行数据分割。MongoDB能自动处理跨集群的数据和负载,自动重新分配文档,以及将用户请求路由到正确的机器上。
MongoDB 的缺点:
1、不支持事务。MongoDB 牺牲了数据库的事务性以追求性能的提升。
2、无法进行关联操作。不适用于关系复杂的数据。
应用场景 :主要解决海量数据的访问效率问题。适合进行大数据存储,而且数据的更新和删除尽可能少(避免造成磁盘碎片)。比如我们公司的系统用它来存储司机的定位点信息,15秒上传一次,自定义的BSON 格式,后期主要是查询相关数据,修改较少。
二、基础知识
1、文档
文档是MongoDB的核心概念,文档就是键值对的一个有序集。
文档的键是字符串;不能含有\0(空字符),这个字符用于表示键的结尾;不能使用系统保留的 . 和 $;键不能重复。
文档的值可以是任意的MongoDB支持的类型。
MongoDB的键值对不但区分类型,而且区分大小写,并且是有序的。"3" 和 3 表示不同的值。"foo" 和 "Foo"表示不同的值。{"x" : 1, "y":2}与{"y": 2, "x": 1}是不同的。
2、集合
集合就是一组文档。如果把MongoDB的一个文档比喻成关系数据库中的一行,那么一个集合就相当于一张表。不同于表的是,一个集合里面的文档可以是各式各样的,例如,下面两个文档可以存储在同一个集合里面:
{"greeting" : "Hello, world!"}
{"foo" : 5}
尽管如此,从开发、管理以及后面的优化来考虑还是不赞同将各式各样的文档不加区分地放在一个集合里。强烈建议把相关类型的文档组织在一起!
组织集合的一种惯例是使用 " . " 分隔不同命名空间的子集合。例如,一个具有博客功能的应用可能包含两个集合,分别是blog.posts和blog.authors。这是为了使组织结构更清晰,这里的blog集合(这个集合甚至不需要存在)跟它的子集合没有任何关系。
在MongoDB中,使用子集合来组织数据非常高效,值得推荐。
3、数据库
在MongoDB中,多个文档组成集合,而多个集合可以组成数据库。
要记住一点,数据库最终会变成文件系统里的文件,而数据库名就是相应的文件名,所以数据库名有诸多限制。
系统预留数据库:
- admin
从身份验证的角度来讲,这是“root”数据库。如果将一个用户添加到admin数据库,这个用户将自动获得所有数据库的权限。再者,一些特定的服务器端命令也只能从admin数据库运行,如列出所有数据库或关闭服务器。
- local
这个数据库永远都不可以复制,且一台服务器上的所有本地集合都可以存储在这个数据库中
- config
MongoDB用于分片设置时,分片信息会存储在config数据库中。
4、数据类型
MongoDB在保留JSON基本键/值对特性的基础上,添加了一些数据类型。
- null
--表示空值或者不存在的字段
- boolean
-- 布尔类型有两个值true和false
- 数值
-- shell 默认使用64位浮点型数值。可使用NumberInt类(表示4字节带符号整数)或NumberLong类(表示8字符带符号整数)
-- {"x" : NumberInt("3")}
-- {"x" : NumberLong("3")}
- 字符串
- 日期
-- 日期存储为新纪元以来经过的毫秒数,不存储时区。
- 数组
-- 数组可以包含不同数据类型的元素
- 对象(内嵌文档)
-- {"x" : {"foo" : "bar"}}
- 对象id
-- 对象id是一个12字节的ID,是文档的唯一标识。
-- {"x" : ObjectId()}
- 二进制数据
-- 如果要将非UTF- 8字符保存到数据库中,二进制是唯一的方式
- 代码
-- 查询和文档中可以包括任意JavaScript代码
-- {"x" : function() { /* ... */ }}
三、创建、更新和删除文档
1、插入(insert)
插入单条:db.foo.insert({"bar" : "baz"})
批量插入:db.foo.batchInsert([{"_id" : 0}, {"_id" : 1}, {"_id" : 2}])
当前版本的MongoDB能接受的最大消息长度是48 MB,所以在一次批量插入中能插入的文档是有限制的。如果试图插入48 MB以上的数据,多数驱动程序会将这个批量插入请求拆分为多个48 MB的批量插入请求。
如果在执行批量插入的过程中有一个文档插入失败,那么在这个文档之前的所有文档都会成功插入到集合中,而这个文档以及之后的所有文档全部插入失败。
2、删除(remove)
db.foo.remove()
--会删除foo集合中的所有文档。但是不会删除集合本身,也不会删除集合的元信息。接受一个查询文档作为可选参数。
db.foo.drop()
--整个集合都被删除,所有元数据也都不见。
$pop(针对数组)
-- $pop 可以从数组任何一端删除元素。
{"$pop":{"key":1}} 从数组末尾删除一个元素
{"$pop":{"key":-1}} 则从头部删除。
$pull(针对数组)
-- $pull 删除数组中满足条件的元素。
-- db.lists.update({}, {"$pull" : {"todo" : "laundry"}}) 将数组中 todo键 等于 laundry 的元素全部剔除掉。即 数组中不会有 "todo" : "laundry" 这个键值对了。
3、修改(update)
db.collection.update(
<query>,
<update>,
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>
)
update有两个必选参数,一个是查询文档,用于定位需要更新的目标文档;另一个是修改器文档,用于说明要对找到的文档进行哪些修改。
update 有三个可选参数,upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。writeConcern :可选,抛出异常的级别。
MongoDB的修改、删除、保存都是原子性的。更新操作是不可分割的:若是两个更新同时发生,先到达服务器的先执行,接着再执行另外一个。所以文档的最终结果取决于最后时间执行的更新操作。
$inc
db.analytics.update({"url" : "www.example.com"},{"$inc" : {"pageviews" : 1}})
-- 匹配到 url 等于 www.example.com 的文档,将它的字段pageviews 加1
-- $inc 只能用于整型、长整型或 双精度浮点型的值。
$set
-- $set 用来指定一个字段的值,如果这个字段不存在,则创建它。
db.users.update({"_id" : ObjectId("4b253b067525f35f94b60a31")},{"$set" : {"favorite book" : "War and Peace"}})
-- $set 甚至可以修改键的类型。
db.users.update({"name" : "joe"},{"$set" : {"favorite book" :["Cat's Cradle", "Foundation Trilogy", "Ender's Game"]}})
-- $unset 将一个键完成删除
db.users.update({"name" : "joe"},{"$unset" : {"favorite book" : 1}})
$push (针对数组)
-- 如果数组已经存在,$push 会向已有的数组末尾加入一个元素,要是没有就创建一个新的数组。
db.blog.posts.update(
{"title" : "A blog post"},
{"$push" :
{"comments" :{"name" : "bob", "email" : "bob@example.com","content" : "good post."}}
})
-- 使用 $each 操作符,可以通过$push 操作添加多个值。
db.blog.posts.update(
{"title" : "A blog post"},
{"$push" :
{"comments" : $each:[
{"name" : "bob", "email" : "bob@example.com","content" : "good post."},
{"name" : "job", "email" : "job@example.com","content" : "job post."}]}
})
$addToSet(针对数组)
-- $addToSet添加值到一个数组中去,如果数组中已经存在该值那么将不会有任何的操作。
db.users.update({"_id" : ObjectId("4b2d75476cc613d5ee930164")},{"$addToSet" : {"emails" : "joe@gmail.com"}})
4、保存(save)
save是一个shell函数,如果文档不存在,它会自动创建文档;如果文档存在,它就更新这个文档。它只有一个参数:文档。要是这个文档含有"_id"键,save会调用upsert。否则,会调用insert。
5、findAndModify
findAndModify 可以在一个操作中返回匹配结果并进行更新。这对于操作队列 以及 执行其他需要进行原子性取值 和赋值的操作来说,十分方便。
findAndModify命令有很多可以使用的字段:
- findAndModify --字符串,集合名。
- query --查询文档,用于检索文档的条件。
- sort --排序结果的条件。
- update --修改器文档,用于对匹配的文档进行更新(update和remove必须指定一个)。
- remove --布尔类型,表示是否删除文档(remove和update必须指定一个)。
- new --布尔类型,表示返回更新前的文档还是更新后的文档。默认是更新前的文档。
- fields --文档中需要返回的字段(可选)。
- upsert --布尔类型,值为true时表示这是一个upsert。默认为false。
db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"update" : {"$set" : {"status" : "RUNNING"}}})
db.runCommand({"findAndModify" : "processes",
"query" : {"status" : "READY"},
"sort" : {"priority" : -1},
"remove" : true})