今天安装好了MongoDB在阿里云。看过很多次MongoDB的架构,知道是非关系型的,似乎是使用文档+BJson存储的,这一点和es很像。今天就尝试操作一下MongoDB。

个人看法

MongoDB的语法糖很像JS和Python,解决了一些关系型数据库的痛点,就拿Mysql举例子,假如目前有个一个用户表,刚开始开发的时候只设置了用户名和年龄列,后续功能更新又添加了是否是篮球队列,这就会导致,之前有很多的数据是没有该列的,添加该列以后,之前的数据对于该列就都是null,这样出现了两个问题,第一个需要修改表结构,第二个需要添加可能对之前数据无用的字段。MongoDB解决了这个问题,他使用类似于JSON的格式,比如之前有个{name:小明,age:18},后面又要添加小刚,但是他是篮球队的,那就直接加进来{name:小刚,age:19,isbasketballer:true},对之前的小明来说是无感知的,该字段对小明来说是不需要的。不过MongoDB并不是用传统的JSON存储,因为传统JSON只能用来存字符和数字,有些二进制数据不支持(需要base64编码),所以MongoDB采用的是BJON,也就是支持二进制的JSON,BSON=JSON+base64

BSON

集合xx.wt内有文档,如果单个集合有很多文档,磁盘IO顶不住,所以MongoDB创建了数据页,将文档放不同的数据页中。每个页32KB,这样查询某个文档的时候,只要找对应的数据页就好了,再从数据页找文档,有些类似找主键索引。后面就是用到的B+树。

B+树存储

因为每个数据页内有文档,所以可以给每个数据页添加页号,由于每个文档都有对应的id主键,所以可以将id+页号组成一个新的页号,放在上一层,这样查询的时候从上往下查询,只要查询两次就可以找到对应的文档。这点和Mysql的B+树基本一致。上面的id是主键索引,当然也可以用文档的其他属性当索引,也就是辅助索引。但是mysql因为要保证并发写冲突,他的B+树有短暂的内存锁,而MongoDB是写时复制,copy on write,管自己写,原来的旧数据还放那边,后面找机会再合并。

cache内存缓存

因为数据在磁盘,IO比较慢,可以将经常查询的数据页放到cache中,优先查询cache,cache查不到再去磁盘找,也可以设置策略删除一些cache中的冷数据。

差不多了

剩下的Jourmal和分布式集群,目前实习面试估计也问不到,只要知道会有WAL机制像undo log和redo log一样有日志保存cache缓存数据不丢失。

基本操作语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//插入单条数据
db.NPC.insertOne({
name: "新NPC-雷克斯",
age: 33,
level: 24,
life: 920,
message: "我是新来的守卫",
email: "rex_guard@newtown.com"
})
//批量插入
db.NPC.insertMany([
{ name: "商人A", age: 40, level: 11 },
{ name: "法师B", age: 180, level: 38, email: "mage_b@magic.com" }
])
db.NPC.find() // 简洁版
db.NPC.find().pretty() // 格式化输出(可读性更好)
// 查询 level 等于 35 的 NPC
db.NPC.find({ level: 35 }).pretty()
// 查询 name 为 "阿尔温" 的 NPC
db.NPC.find({ name: "阿尔温" }).pretty()
// 查询 level 大于 30 的 NPC
db.NPC.find({ level: { $gt: 30 } }).pretty()
// 查询 age 介于 30-50 之间的 NPC
db.NPC.find({ age: { $gte: 30, $lte: 50 } }).pretty()
// 查询包含 message 字段的 NPC
db.NPC.find({ message: { $exists: true } }).pretty()
// 查询不包含 email 字段的 NPC
db.NPC.find({ email: { $exists: false } }).pretty()
// 只返回 name 和 level 字段(_id 默认显示,用 0 隐藏)
db.NPC.find({}, { name: 1, level: 1, _id: 0 }).pretty()
// 按 level 降序排序,只显示前 3 条
db.NPC.find().sort({ level: -1 }).limit(3).pretty()
// 按 age 升序排序(1 表示升序,-1 表示降序)
db.NPC.find().sort({ age: 1 }).pretty()

// 将 name 为 "塔克农夫" 的文档全量替换
db.NPC.replaceOne(
{ name: "塔克农夫" }, // 条件
{ name: "塔克农夫", age: 52, level: 11, life: 666 } // 新文档
)
db.NPC.find({ name: "塔克农夫" }).pretty()

// 将所有 level < 20 的 NPC 的 life 增加 100
db.NPC.updateMany(
{ level: { $lt: 20 } },
{ $inc: { life: 100 } } // $inc 用于数值增减
)

// 移除 "格雷探长" 的 message 字段
db.NPC.updateOne(
{ name: "格雷探长" },
{ $unset: { message: 1 } }
)
// 删除 name 为 "戈布尔矿工" 的文档(只删第一条匹配项)
db.NPC.deleteOne({ name: "戈布尔矿工" })
// 删除所有不包含 life 字段的 NPC
db.NPC.deleteMany({ life: { $exists: false } })
db.NPC.findOne({ level: { $gte: 30 } }) // 返回第一条符合条件的文档