MongoDB 学习笔记2 - 基础知识和使用

0. 背景

MongoDB 的一些基础知识和使用。

MongoDB

1. 基础知识

六个简单的概念:

  • (1) database(数据库):MongoDB中 也有 数据库 的概念,和关系型数据中的的"数据库"一样的概念。一个 MongoDB 实例中,可以有零个或多个数据库,用于存储数据。
  • (2) collections (集合):一个数据库中可以有多个 collections (集合)。它和传统意义上的 table 是一样的东西。
  • (3) documents (文档):一个集合由多个 documents (文档)组成。一个文档可以看成是一条数据的记录( row 或者 record)。
  • (4) fields (字段:一个文档是由多个 fields (字段)组成。它就是 columns。
  • (5) Indexes (索引): MongoDB 的索引和 RDBMS 中的一样。
  • (6) Cursors (游标):当你问 MongoDB 拿数据的时候,它会给你返回一个结果集的指针而不是真正的数据,这个指针我们叫它游标。

概念对照表:

MongoDB 传统的关系型数据库
database database, 相同
collections table
documents row
fields columns
Indexes Indexes 相同
Cursors -

当我们从 MongoDB 获取数据的时候,我们通过 cursor 来操作,读操作会被延迟到需要实际数据的时候才会执行。

核心差异在于,在MongoDB里,collection中的每个documents都可以有自己独立的 field (字段),而关系型数据中每行的字段都智能相同

要点就是,集合不对存储内容严格限制 (所谓的无模式(schema-less))。字段由每个独立的文档进行跟踪处理。

总结:MongoDB 可以每行数据的结构都不同,支持非结构化数据。 区别于 传统的严格结构化数据。

2. 基本操作

2.1 连接到数据库

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 类型的。

2.2 插入数据

db.unicorns.insert({name: 'Aurora',
    gender: 'f', weight: 450})

2.3 删除数据

db.unicorns.remove({})。

2.4 查询

掌握选择器(Selector):MongoDB 的查询选择器就像 SQL 语句里面的 where 一样。

因此,你会在对集合的文档做查找,计数,更新,删除的时候用到它。选择器是一个 JSON 对象,最简单的是就是用 {} 匹配所有的文档。比如可以用 {gender:'f'}。

{field: value} 用来查找那些 field 的值等于 value 的文档。 {field1: value1, field2: value2} 相当于 and 查询。还有 lt,lte, gt,gte 和 $ne 被用来处理 小于,小于等于,大于,大于等于,和不等于操作。

db.unicorns.find({gender: 'm',
    weight: {$gt: 700}})

db.unicorns.find({gender: {$ne: 'f'},
    weight: {$gte: 701}})

2.5 判断是否存在

$exists 用来匹配字段是否存在,比如:

db.unicorns.find({
vampires: {$exists: false}})

2.6 是否被包含用 $in

'$in' 被用来匹配查询文档在我们传入的数组参数中是否存在匹配值,比如:

db.unicorns.find({
loves: {$in:['apple','orange']}})

2.7 逻辑操作中的 or,通过 $or 操作符 来操作。

使用 $or 操作符,再给它一个我们要匹配的数组:

db.unicorns.find({gender: 'f',
$or: [{loves: 'apple'},
      {weight: {$lt: 500}}]})

2.8 查询 _id 字段

_id 字段生成的 ObjectId 可以这样查询:

db.unicorns.find(
{_id: ObjectId("TheObjectId")})

3. 更新数据 (Update)

如果改变一个或者几个字段的值的时候,你应该用 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

字段加一个值,通过 $push 操作:

db.unicorns.update({name: 'Aurora'},
{$push: {loves: 'sugar'}})

Upserts

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});

4. 查询

字段选择

find 有第二个可选参数,叫做 "projection"。这个参数是我们要检索或者排除字段的列表。

db.unicorns.find({}, {name: 1});

默认的,_id 字段总是会返回的。我们可以通过这样显式的把它从返回结果中排除 {name:1, _id: 0}。

排序(Ordering)

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 对未经索引的字段进行排序是有大小限制的。如果你试图对一个非常大的没有经过索引的结果集进行排序的话,你会得到个异常。

分页(Paging)

对结果分页可以通过 limitskip 游标方法来实现。比如:

db.unicorns.find()
    .sort({weight: -1})
    .limit(2)
    .skip(1)

通过 limitsort 的配合,可以在对非索引字段进行排序时避免引起问题。

count 计数

shell 中可以直接对一个集合执行 count ,像这样:

db.unicorns.count({vampires: {$gt: 50}})

实际上,count 是一个 cursor 的方法,shell 只是简单的提供了一个快捷方式。

以不提供快捷方式的方法来执行的时候需要这样:

db.unicorns.find({vampires: {$gt: 50}})
    .count()

5. 数据建模

不支持 join

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'})

反规范化(Denormalization)

一些对冗余处理 的讨论。
并不是需要对你文档里的每条数据都做冗余处理。而是说,与其对冗余数据心存恐惧,让它影响你的设计决策,不如在建模的时候考虑什么信息应当属于什么文档。

假设你要写一个论坛应用。

  • 传统的方式是通过 posts 中的 userid 列,来关联一个特定的 user 和一篇 post 。这样的建模,在显示 posts 的时候要查询 (链接到) users。

  • 一个代替案是“增加冗余字段”,在每篇 post 中都冗余的多存储 name 和 userid 两个字段。这要用到内嵌文档,比如 user: {id: ObjectId('Something'), name: 'Leto'}。缺点是,如果用户可以更新他们的名字,那将不得不对所有的文档都进行更新。

其他选择

记住: 一个独立文档的大小当前被限制在 16MB

处理一对多(one-to-many)或者多对多(many-to-many)场景的时候,id 数组通常是一个正确的选择。

内嵌文档经常使用的情形:大多数情况下多是很小的数据块,面对总是被和父节点一起拉取的数据块。

集合的规模讨论:单个大而全?还是拆分小而专?

比如,常见的例子就是博客。你是应该分成一个 posts 集合和一个 comments 集合呢,还是应该每个 post 下面嵌入一个 comments 数组?

MongoDB 的处理方式:MongoDB 的灵活架构允许你把这两种方式结合起来,你可以把评论放在独立的集合中,同时在博客帖子下嵌入一小部分评论 (比如说最新评论) ,以便和帖子一同显示。

这遵守以下的规则:“ 你到底想在一次查询中获取到什么内容,那就怎么做。”

想一想,如果在关系型数据库中,要把上面说的这两种方式结合起来用,“要不要再建一个关联表呢?”

6. MongoDB 适用场景

单一解决方案还是多技术方案?
对于许多项目来说 - 或者说大多数 - 单一解决案是一个明智的选择。只有你自己才知道,引进新技术是否利大于弊。引入MongoDB 往往不会完全替换旧的方案(比如用Mongo替换MySQL),而是说“不用再依赖单一的解决案来处理你的数据”,作为数据存储的局部替代方案,是对你现有数据存储方案能力的局部增强。

比如说用 Lucene 作为关系型数据库的全文检索索引的加强,或者用 Redis 作为持久型 key-value 存储对缓存存储的增强,MongoDB 就是用来保存你的数据能力的处理增强。

写操作(Writes)

MongoDB 可以胜任的一个特殊角色是在日志领域。有两点使得 MongoDB 的写操作非常快。首先,你可以选择发送了写操作命令之后立刻返回,而无须等到操作完成。

如果想让你的数据 "过期" ,基于时间而不是整个集合的大小,你可以用 TTL 索引 ,所谓 TTL 是 "time-to-live" 的缩写。

持久性(Durability)

从 2.0 版的 MongoDB 开始,日志是默认启动的,该功能允许快速恢复服务器,比如遭遇到了服务器崩溃或者停电的情况。

事务(Transactions)

MongoDB 不支持事务。两个代替案:

  • 第一个方案,就是各种原子更新操作。比如 inc 和set。还有像 findAndModify 命令,可以更新或删除文档之后,自动返回修改过的文档
  • 第二个方案,当原子操作不能满足的时候,回到两段提交上来。

地理空间查询(Geospatial)

一个很强大的功能就是 MongoDB 支持 geospatial 索引。这允许你保存 geoJSON 或者 x 和 y 坐标到文档,并查询文档,用如 near 来获取坐标集,或者within 来获取一个矩形或圆中的点。

7. 聚合管道(Aggregation Pipeline)

聚合管道提供了一种方法用于转换整合文档到集合。你可以通过管道来传递文档,就像 Unix 的 "pipe" 一样,将一个命令的输出传递到另第二个,第三个,等等

8. 性能和工具

索引(Index)

创建索引用 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

需要检查你的查询是否用到了索引,你可以通过 explain 方法:

db.unicorns.find().explain()

复制(Replication)

MongoDB 的复制在某些方面和关系型数据库的复制类似。所有的生产部署应该都是副本集,理想情况下,三个或者多个服务器都保持相同的数据。写操作被发送到单个服务器,也即主服务器,然后从它异步复制到所有的从服务器上。你可以控制是否允许从服务器上进行读操作,这可以让一些特定的查询从主服务器中分离出来,当然,存在读取到旧数据的风险。如果主服务器异常关闭,从服务中的一个将会自动晋升为新的主服务器继续工作。

分片(Sharding)

MongoDB 支持自动分片。分片是实现数据扩展的一种方法,依靠在跨服务器或者集群上进行数据分区来实现。一个最简单的实现是把所有的用户数据,按照名字首字母 A-M 放在服务器 1 ,然后剩下的放在服务器 2。

状态(Stats)

你可以通过 db.stats() 查询数据库的状态。

分析器(Profiler)

可以这样执行 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

9. 参考

https://github.com/ilivebox/the-little-mongodb-book/blob/master/zh-cn/mongodb.markdown

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270