最近将以前的笔记和一些常识捋了一遍,并结合最新的 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
来管理即可。
先安装 Homebrew
, Homebrew
会自动创建本地的 mongo 存储目录及 log 目录。并设置源为国内源,他是 Mac 的默认包管理工具。
安装成功后,会看到 mongod
的版本信息:
1 | (base) zyzy:~ $ mongod --version |
根据上面 mongo 的安装的文档,我还碰到一个比较奇葩的问题,在运行 brew services start [email protected]
时,会报错如下:
1 | (base) zyzy:~ $ brew services start [email protected] |
我在 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 dbs
或 show databases
:
1 | > show dbs |
admin
:从权限的角度来看,这是”root”数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。1 Ilocal
: 集群模式下,该数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。config
:当 Mongo 用于分片设置时,config 数据库在内部使用,用于保存分片的相关信息。
MongoDB 有 3 种方式可以学习,分别是:
- CLI (Command Line Interface) 命令行模式
- API (Application Programming Interface) 应用程序编程接口
- GUI (Graphical User Interface) 图形用户界面
以下的命令行操作,我们可以在 MongoDB 的 CLI 模式下进行。
数据库操作基本命令
命令/操作 | 描述 |
---|---|
mongosh |
打开一个连接到本地实例的 MongoShell。所有其他命令都需要在mongosh 中执行。 |
show databases 或 show dbs |
显示当前 MongoDB 实例中的所有数据库。 |
use <dbname> |
切换到数据库<dbname> 。 |
db |
显示当前使用中的数据库名称。 |
cls |
清屏。 |
show collections |
显示当前数据库中的所有集合。 |
db.dropDatabase() |
删除当前的数据库。 |
exit |
退出mongosh 会话。 |
1 | show dbs |
操作示例:
1 | test> show dbs |
其他数据库和表操作命令
1 | # 删除数据库: |
插入数据
操作 | 描述 | 示例 |
---|---|---|
创建/插入 | 在集合中插入一个新的文档 | db.users.insertOne({name: "老杨"}) |
插入多个 | 在集合中插入多个新的文档 | db.users.insertMany([{name:"李四"}, {name: "王五"}]) |
插入单条数据 db.collection.insertOne()
例如:
1 | db.users.insertOne({ name: "zhangsan" }); |
查看插入的所有数据 db.collection.find()
发现刚刚插入的数据
1 | games> db.games.users.find() |
MongoDB 的集合没有像 MySQL 一样有表结构,所以插入的数据没有字段限制。例如我们重新插入一条, 第二条数据多了一个 age 字段, 依然能正常插入:
1 | games> db.users.insertOne({name: '张三', age:18}) |
插入多条数据 db.collection.insertMany()
我们来插入一些有意义点的数据:
1 | db.users.insertMany([ |
查询 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 | # 查找 level 大于 2 的数据: |
$and
, $or
, $not
, $ne
1 | # 查找 level 大于 2 小于等于 5 的数据: |
$in
, $nin
, $exits
1 | # 查找 level 等于 1 或 3 的数据: |
$regex
正则
1 | db.users.find({name: {$regex: /^张/}}) |
聚合查询
db.users.countDocuments()
1 | db.users.countDocuments({ level: { $gt: 2 } }); |
db.collection.aggregate()
多条件聚合, 用数组把多个条件放在一起:
1 | db.users.aggregate([ |
得到如下结果:
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 压力。
- 创建
db.users.createIndex()
1 | // 升序 |
删除
db.users.dropIndex({ level: 1 })
查看
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() |