最近将以前的笔记和一些常识捋了一遍,并结合最新的 MongoDB 内容,重新写了这篇文章。

简介

MongoDB 是一个开源的 NoSQL 文档型数据库,它使用 BSON ( 即 Binary 的 JSON 格式 ) 文档来存储数据,而不是使用 SQL 语句来操作数据。MongoDB 是一个分布式数据库,可以 horizontal scale,可以水平扩展。

业务应用场景
传统的关系型数据库(如 MySQL),在数据操作的“三高“需求以及应对 Web2.0 的网站需求面前,显得力不从心。

  • High performance - 对数据库高并发读写的需求。
  • Huge Storage-对海量数据的高效率存储和访问的需求。
  • High Scalability && High Availability-对数据库的高可扩展性和高可用性的需求。
    而 MongoDB 可应对”三高”需求。

具体的应用场景如:
1)社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
2)游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。
3)物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
4)物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
5)视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

2024 年,MongoDB 官方,宣布:

MongoDB 很荣幸被评为 2023 年 Gartner® 云数据库管理系统(CDBMS)魔力象限领导者。我们相信,这使得 MongoDB 成为唯一连续两年被评为领导者的专用应用程序数据库提供商。

MongoDB 现在是云数据库的领导者,在 B 端领域,尤其是工业及企业互联网,MongoDB 已经成为非常流行的数据库。

安装:

Mac

参考这篇官方安装文档

我用的是 Mac 电脑, 所以安装包用 Homebrew 来管理即可。

先安装 HomebrewHomebrew会自动创建本地的 mongo 存储目录及 log 目录。并设置源为国内源,他是 Mac 的默认包管理工具。

安装成功后,会看到 mongod 的版本信息:

1
2
3
4
5
6
7
8
9
10
11
12
(base) zyzy:~ $ mongod --version
db version v7.0.8
Build Info: {
"version": "7.0.8",
"gitVersion": "c5d33e55ba38d98e2f48765ec4e55338d67a4a64",
"modules": [],
"allocator": "system",
"environment": {
"distarch": "aarch64",
"target_arch": "aarch64"
}
}

根据上面 mongo 的安装的文档,我还碰到一个比较奇葩的问题,在运行 brew services start [email protected] 时,会报错如下:

1
2
3
4
5
(base) zyzy:~ $ brew services start [email protected]
Error: Your Homebrew is too outdated for `brew services`. Please run `brew update`!
(base) zyzy:~ $ brew update
HOMEBREW_BREW_GIT_REMOTE set: using https://mirrors.aliyun.com/homebrew/brew.git as the Homebrew/brew Git remote.
Already up-to-date.

我在 stack overflow 上找到了些方法:https://stackoverflow.com/questions/64821648/homebrew-fails-on-macos-big-sur ,但试过之后还是无解。

既然不能用 Homebrew 跑服务,那么就执行另一个命令在运行,根据官网建议,Mongo DB 在 Mac 后台运行,需跑:

1
mongod --config /opt/homebrew/etc/mongod.conf --fork

Mongo Compass

Mongo 官方的 GUI 界面,在这里下载

表结构

Mongo 和普通 SQL 数据库的区别:

术语 SQL MongoDB
数据库 数据库 集合(collection)
表格(table) 集合(collection)
行(row) 文档(document)
列(column) 字段(field)
索引 索引(index) 索引(index)
表连接 表连接(table joins) 嵌入式文档(json 嵌套格式)
主键 主键(primary key) 自动生成的唯一标识符(_id)

可存储数据类型

数据类型 描述 举例
字符串 UTF-8 字符串都可表示为字符串类型的数据 {“x”:”foobar”}
对象 id 对象 id 是文档的 12 字节的唯一 ID {“X”:ObjectId()}
布尔值 真或者假:true 或者 false {“x”:true}
数组 值的集合或者列表可以表示成数组 {“x”:[“a”,”b”,”c”]}
类型不可用 JavaScript 仅支持 64 位浮点数,所以 shell 是不支持该类型的,shell 中默认会转换成 32 位整数,32 位整数会被自动转换成 64 位浮点数
64 位整数 shell 中使用一个特殊的内嵌文档来显示 64 位整数
64 位浮点 shell 中的数字就是这一种类型 {“x”:3.14159,”y”:3}
null 表示空值或者未定义的对象 {“x”:null}
undefined 文档中也可以使用未定义类型 {“x”:undefined}
符号 shell 会将数据库中的符号类型的数据自动转换成字符串
正则表达式 文档中可以包含正则表达式,采用 JavaScript 的正则表达式语法 {“x”:/foobar/i}
代码 文档中还可以包含 JavaScript 代码 {“x”:function(//)}
二进制数 二进制数据可以由任意字节的串组成,不过 shell 中无法使用
最大值/最小值 BSON 包括一个特殊类型,表示可能的最大值和最小值

collections

在命令行输入 show dbsshow databases:

1
2
3
4
> show dbs
admin 40.00 KiB
config 72.00 KiB
local 40.00 KiB
  • admin:从权限的角度来看,这是”root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。1 I
  • local: 集群模式下,该数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
  • config:当 Mongo 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息。

MongoDB 有 3 种方式可以学习,分别是:

  1. CLI (Command Line Interface) 命令行模式
  2. API (Application Programming Interface) 应用程序编程接口
  3. GUI (Graphical User Interface) 图形用户界面

以下的命令行操作,我们可以在 MongoDB 的 CLI 模式下进行。

数据库操作基本命令

命令/操作 描述
mongosh 打开一个连接到本地实例的 MongoShell。所有其他命令都需要在mongosh中执行。
show databasesshow dbs 显示当前 MongoDB 实例中的所有数据库。
use <dbname> 切换到数据库<dbname>
db 显示当前使用中的数据库名称。
cls 清屏。
show collections 显示当前数据库中的所有集合。
db.dropDatabase() 删除当前的数据库。
exit 退出mongosh会话。
1
2
3
show dbs
use articledb
db.createCollection("my")

操作示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
test> show dbs
admin 40.00 KiB
config 108.00 KiB
local 40.00 KiB
test> use game
# 创建数据库,此时数据库还在内存里,没写到硬盘里:
switched to db games
game> show dbs
admin 40.00 KiB
config 108.00 KiB
local 40.00 KiB
# 必须创建集合createCollection,才能show bds看到改数据库
test> db.createCollection("my")
{ ok: 1 }
switched to db articledb
articledb> show collections
my
# 创建完collection之后,数据库才会写到硬盘里:
test> show dbs
admin 40.00 KiB
articledb 8.00 KiB
config 108.00 KiB
local 40.00 KiB

其他数据库和表操作命令

1
2
3
4
5
# 删除数据库:
db.collection.dropDatabase()

# 删除表:
db.collection.drop()

插入数据

操作 描述 示例
创建/插入 在集合中插入一个新的文档 db.users.insertOne({name: "老杨"})
插入多个 在集合中插入多个新的文档 db.users.insertMany([{name:"李四"}, {name: "王五"}])

插入单条数据 db.collection.insertOne()

例如:

1
db.users.insertOne({ name: "zhangsan" });

查看插入的所有数据 db.collection.find()

发现刚刚插入的数据

1
2
games> db.games.users.find()
[ { _id: ObjectId('665a90210b1418930d2b9c8d'), name: 'zhangsan' } ]

MongoDB 的集合没有像 MySQL 一样有表结构,所以插入的数据没有字段限制。例如我们重新插入一条, 第二条数据多了一个 age 字段, 依然能正常插入:

1
2
3
4
5
6
7
8
9
10
games> db.users.insertOne({name: '张三', age:18})
{
acknowledged: true,
insertedId: ObjectId('665a931c0b1418930d2b9c90')
}
games> db.users.find()
[
{ _id: ObjectId('665a90210b1418930d2b9c8d'), name: 'zhangsan' },
{ _id: ObjectId('665a931c0b1418930d2b9c90'), name: '张三', age: 18 }
]

插入多条数据 db.collection.insertMany()

我们来插入一些有意义点的数据:

1
2
3
4
5
6
db.users.insertMany([
{name: '吕布', email: '[email protected]', level: 1, skills: ['无双', '战神']},
{name: '赵云', email: '[email protected]', level: 2, skills: ['热血', '魂']},
{name: '张飞', email: ' [email protected]', level: 3, skills: ['气合', '吼']},
{name: '关羽', email: ' [email protected]', level: 5, skills: ['必杀', '神圣']}
])

查询 db.users.find()

条件查询

查找 name 为吕布的数据

1
db.users.find({name: '吕布'})

查找 name 名字有’张’, 只返回第一条数据:

1
db.users.findOne({ name: { $regex: /张/ } });

查找 level 为 3 的数据, 不返回 email 字段:

1
db.users.find({ level: 3 }, { email: 0 });

查找 level 为 3 的数据, 只返回 email skills 两个字段, 不返回 _id 字段, 值可以为 1/0, 或者 ture/false:

1
db.users.find({ level: 3 }, { email: 1, skills: 1, _id: 0 });

逻辑查询

$gt, $lt, $gte, $lte

1
2
3
4
5
6
7
8
9
# 查找 level 大于 2 的数据:
db.users.find({level: {$gt: 2}})
# 查找 level 大于等于 2 的数据:
db.users.find({level: {$gte: 2}})
# 查找 level 小于 2 的数据:
db.users.find({level: {$lt: 2}})
# 查找 level 小于等于 2 的数据:
db.users.find({level: {$lte: 2}})

$and, $or, $not, $ne

1
2
3
4
5
6
# 查找 level 大于 2 小于等于 5 的数据:
db.users.find({level: {$gt: 2, $lte: 5}})
# 或者用 $and
db.users.find({$and: [{level: {$gt:2}}, {level: {$lte: 5}}]})
# 查找 level 不等于5 的数据:
db.users.find({level: {$ne: 5}})

$in, $nin, $exits

1
2
3
4
5
6
7
8
# 查找 level 等于 1 或 3 的数据:
db.users.find({level: {$in: [1, 3]}})
# 查找 level 不等于 1 或 3 的数据:
db.users.find({level: {$nin: [1, 3]}})


# 查找 level 字段是否存在的数据, 即使该数据为 null:
db.users.find({level: {$exists: true }})

$regex 正则

1
2
3
db.users.find({name: {$regex: /^张/}})
# 忽略大小写:
db.users.find({name: {$regex: /^张/, $options: 'i'}})

聚合查询

db.users.countDocuments()

1
db.users.countDocuments({ level: { $gt: 2 } });

db.collection.aggregate()

多条件聚合, 用数组把多个条件放在一起:

1
2
3
4
5
db.users.aggregate([
{ $match: { level: { $gt: 2 } } },
{ $group: { _id: "$level", count: { $sum: 1 } } },
{ $sort: { level: -1 } },
]);

得到如下结果:

1
2
3
4
[
{ _id: 3, count: 1 },
{ _id: 5, count: 1 },
];

limit()

limit() 方法用于限制查询结果的数量, 参数传入数字 n, 表示返回前 n 条。

1
db.users.find().limit(1);

sort()

sort() 方法用于排序, 传入参数为对象, {字段名字: 1 或 -1}, 例如:

1
db.users.find().sort({ level: -1, name: 1 }).limit(2);

表示按 level 降序排列, 如果等级相同, 再按 name 升序排列, 只显示前 2 条数据。

skip()

skip() 方法用于跳过指定数量的文档, 参数传入数字 n, 表示跳过前 n 条。一般和 sort() 一起使用, 用于分页

更新数据

db.collection.updateOne() 更新单条数据

1
db.users.updateOne({ name: "吕布" }, { $set: { level: 10 } });

db.collection.updateMany() 更新多条数据

1
db.users.updateMany({ level: { $gt: 2 } }, { $set: { level: 10 } });

删除数据

db.collection.deleteOne() 删除单条数据

1
db.users.deleteOne({ name: "吕布" });

db.collection.deleteMany() 删除多条数据

1
db.users.deleteMany({ level: { $gt: 2 } });

索引

索引是 MongoDB 数据库优化性能的重要手段, 索引能够加快查询速度, 降低磁盘 I/O 压力。

  1. 创建 db.users.createIndex()
1
2
3
4
// 升序
db.users.createIndex({ level: 1 });
// 降序:
db.users.createIndex({ level: -1 });
  1. 删除 db.users.dropIndex({ level: 1 })

  2. 查看 db.users.getIndexes()

多字段索引

1
db.users.createIndex({ level: 1, name: 1 });

而复合索引必须遵循最左匹配原则, 上面的索引创建成功后, 必须用:

1
db.users.find({ level: { $gt 2}, name: "吕布" });

虽然索引可以提高查询性能,但它们也会占用额外的存储空间,并可能增加插入、更新和删除操作的开销。因此,在创建索引时需要仔细权衡利弊,确保所创建的索引能够真正提升应用程序的性能。

性能分析 (Analyze Query Performance)

查询语句执行时所需的时间可以通过 explain() 方法来查看, 语法:

1
db.collection.find(query.options).explain(options)
1
db.users.find({level: 3}).explain()