MongoDB学习笔记

本文最后更新于:2022年6月13日 下午

MongoDB学习笔记

1.安装

1.1.Mac安装

官网 下载可执行文件包。选择本地部署,选择社区版服务端,选择你需要的版本,点击下载即可。

从大版本号6开始Mac版有ARM指令集可选,实际使用速度提升很高,但是截至本文创建时间,还没有形成稳定发布版本。

image-20220426140104500

下载得到的压缩包中只有一个bin文件包,内含4个最基本的服务端可执行文件。将mongodb文件夹放至合适的位置,给bin目录加环境变量。

1
2
# mysql也在/usr/local,因此也放在这里,省事
export PATH=/usr/local/mongodb/bin:$PATH

创建logdata目录,用于存放日志和数据库数据(叫啥都行,你知道就行)。

使用以下指令开启和关闭服务端,此时可以命令行输入mongo进入数据库shell。

--fork指令用于指定后台运行,而后台运行没有对应的指令一键退出,需要进入shell后输入db.adminCommand({ "shutdown" : 1 })。如果你确定你的数据库目前没有读写,而且是单机运行,那么也可以直接杀掉进程。

1
2
3
4
5
# 开启服务端
mongod --dbpath [data目录] --logpath [log目录/mongo.log] --fork
# 关闭服务端(偷懒)
killall -9 mongod
db.adminCommand({ "shutdown" : 1 })

1.2.数据迁移

MongoDB提供直接输入输出数据库到文件的功能,可以用于数据库迁移。进行这些操作需要补充可执行文件。官网下载即可。目前数据库工具还没有苹果ARM版本。

image-20220426142043224

下载的压缩包中同样只有一个bin文件夹,里面补充了一些可执行文件。将它们复制到你MongoDB的bin目录中。

image-20220426142304546

数据库导出也可以使用JetBrains全家桶的数据库插件,它可以一次性导出一整个库,效率比较高,但是不提供导入功能。mongodb官方也提供免费的GUI管理器——Compass。它提供完整的数据库操作、分析、导入导出功能。

1
2
3
4
5
6
7
8
9
10
11
12
# 带日志地导出整个数据库
mongodump --host localhost:27017 --oplog

# 导入二进制数据库备份文件。不指定数据库默认全导入。导入导出请使用相同版本
mongorestore --host localhost:27017 --nsInclude=[数据库名].[集合名] [从哪个目录导入]
# 恢复时重放日志。不指明路径默认转储路径顶层
mongorestore --oplogReplay [从哪个目录导入] --oplogFile [路径]

# 导入Json数据文件
mongoimport --db [数据库名] --collection [集合名] --file [数据文件]
# 或json数组(JetBrains导出的格式)
mongoimport --db [数据库名] --collection [集合名] --jsonArray [数据文件]

1.3.多节点部署

在单机上模拟多节点配置。对于实际生产环境不建议单机配置。

1.3.1.配置文件

创建三个不同的数据文件夹,分别在其内创建配置文件mongod.conf,写入如下配置内容。三个加了注释的地方要根据实际来改动,三个节点不准相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /Users/zql/Desktop/mongodb/data1/mongod.conf
systemLog:
destination: file
path: /Users/zql/Desktop/mongodb/data1/mongod.log # 日志文件路径
logAppend: true
storage:
dbPath: /Users/zql/Desktop/mongodb/data1 # 数据目录
net:
bindIp: 0.0.0.0
port: 28017 # 端口
replication:
replSetName: rs0
enableMajorityReadConcern: true
processManagement:
fork: true

1.3.2.配置复制集

命令行中输入以下命令,启动三个mongodb进程:

1
2
3
mongod -f /Users/zql/Desktop/mongodb/data1/mongod.conf &&
mongod -f /Users/zql/Desktop/mongodb/data2/mongod.conf &&
mongod -f /Users/zql/Desktop/mongodb/data3/mongod.conf

三个进程目前互相独立,互不知道,因此需要进入shell进行进一步配置:

1
mongo --port 28017

配置方式有如下两种,任一即可。

1
2
3
4
// 这种方法需要主机名能被解析。使用 hostname -f 来测试
rs.initiate()
rs.add("[你的主机名]:28018") //rs.add("Mac:28018")
rs.add("[你的主机名]:28019") //rs.add("Mac:28019")
1
2
3
4
5
6
7
8
9
10
11
12
13
rs.initiate({
_id: "rs0",
members: [{
_id: 0,
host: "localhost:28017"
},{
_id: 1,
host: "localhost:28018"
},{
_id: 2,
host: "localhost:28019"
}]
})
1
2
// 查看状态
rs.status()

想要在从节点上读取数据(比如搜索)默认被禁止,如果想启用,请在从节点的命令行输入rs.secondaryOk()

1.3.3.调整配置

1
2
3
4
5
6
7
8
9
var conf = rs.conf()
// 将0号节点的优先级调整为10
conf.members[0].priority = 10;
// 将1号节点调整为hidden节点
conf.members[1].hidden = true;
// hidden节点必须配置{priority: 0}
conf.members[1].priority = 0;
// 应用以上调整
rs.reconfig(conf);

2.shell操作

2.1.基本操作

  • show dbs,显示所有数据库。use [数据库],使用某数据库

  • db.collection.insertOne([Json])插入一条数据;db.collection.insertMany([Json,Json,Json])同时插入很多条数据。插入后会返回给你ObjectId字段

  • 查找

    • db.movies.find().pretty(),显示所有数据,且有缩进

    • db.movies.find({"year":1975},{"_id":0,"title":1}).sort("age":1),单条件查询,且不返回id返回title,且排序

    • db.movies.find({"year":1989,"title":"Batman'"}),多条件and查询

    • db.movies.find($and:[{"title":"Batman},{"category":"action"}]),and的另一形式

    • db.movies.find({$or:[{"year":1989},{"title":"Batman"}]),多条件or查询

    • db.movies.find({"title”:/^B/}),按正则表达式查找

    • 逻辑对照 image-20220531213055698

      子文档中如果有多个属性,搜索时应当搜索「子文档属性是…」,而不是「子文档是 属性…」。二者表达意义不同。

      如果某个属性是一个数组,搜索的元素在此数组中,那么就是可以搜到的。

      对子文档搜索时,使用$elemMatch指定的字段必须在同一条数据里 image-20220531220848199

  • 删除

    • db.testcol.remove( { a : 1 } ),删除a 等于1的记录
    • db.testcol.remove( { a : { $lt : 5 } } ),删除a 小于5的记录
    • db.testcol.remove( { } ),删除所有记录
    • db.testcol.remove(),报错
  • 更新

    • db.fruit.updateOne({name: "apple"}, {$set: {from: "China"}}),查询字段和更新字段都是必须的
    • 必须有如下操作符至少一个
      • $push:增加一个对象到数组底部
      • $pushAll:增加多个对象到数组底部
      • $pop:从数组底部删除一个对象
      • $pull:如果匹配指定的值,从数组中删除相应的对象
      • $pullAll:如果匹配任意的值,从数据中删除相应的对象
      • $addToSet:如果不存在则增加一个值到数组
  • 删除

    • db.[集合].drop(),删除一个集合
    • db.dropDatabase(),删除一个数据库
  • 索引

1
2
3
4
5
6
7
8
// 创建索引
db.[集合名].createIndex({"j":1, "w":1})
// 删除所有索引
db.[集合名].dropIndexes()
// 查看集合索引
db.[集合名].getIndexes()
// 查看集合索引大小
db.[集合名].totalIndexSize()

2.2.聚合查询

  • 操作符 image-20220531224931563
  • 运算符 image-20220531225004562
  • 特有操作 image-20220531225045462
  • 例1 image-20220531225231900
  • 例2 image-20220531225253295
  • 特有操作 image-20220531225329233
  • 自动分组 image-20220531230448316
  • 一次完成多个分组 image-20220531230644883

2.3.设计模式

  • mongodb使用中,通常直接把数据放到一个表里,除非超过了16M
  • 分表后可以这样聚合查询 image-20220601082824461
  • 只支持左外连接,不能是分片表
  • 推荐的设计模式
    • 表现形式类
      • 列转行 image-20220601085150750
        • db.movies.createIndex({"releases.country":1,"releases.date":1})
      • 文档版本
    • 数据访问类
      • 子集
      • 近似处理:每次写操作触发0-9随机数,随机数在0才计数加10。减少写操作
    • 组织结构类
      • 预聚合:数据中提前储存统计字段
      • 分桶:把每分钟的数据改成每小时一个表,其中的数据用数组存

2.4.事务

  • 写操作

    • writeConcern:一个写操作落到多少个节点上才算成功。发起写操作的程序将阻塞到写操作到达指定的节点数为止

      • db.test.insert( {count: 1}, {writeConcern: {w: "majority", wtimeout:3000 }})
      • 0:不关心是否成功
      • 1:写入全部节点
      • majority:大多数节点

      • journal:写操作到达多少个节点才算成功

        • true:写操作落到journal文件中才算成功
        • false:写操作到达内存即算作成功
    • 配置延迟节点

1
2
3
4
conf=rs.conf() 
conf.members[2].slaveDelay = 5
conf.members[2].priority = 0
rs.reconfig(conf)
  • 读操作

    • readPref:决定使用哪一个节点来满足正在发起的读请求

      • primary:只选择主节点
      • primaryPreferred:优先选择主节点,如果不可用则选择从节点
      • secondary:只选择从节点
      • secondaryPreferred:优先选择从节点, 如果从节点不可用则选择主节点
      • nearest:选择最近的节点
      • 可以自己打标签,例如为3个较好的节点打上{purpose:"online"}

      • readConcern:决定这个节点上的数据哪些 是可读的,类似于关系数据库的隔离级别

        • available:读取所有可用的数据
        • local:读取所有可用且属于当前分片的数据
        • majority:读取在大多数节点上提交完成的数据
        • linearizable:可线性化读取文档。性能一般,一般用不到
        • snapshot:读取最近快照中的数据。最高安全级别
    • 读写分离提高性能

      • db.orders.insert({ oid: 101, sku: "kiteboar", q: 1}, {writeConcern:{w: "majority"}})
      • db.orders.find({oid:101}).readPref("secondary").readConcern("majority")

2.5.变更流(触发器)

  • 与触发器不完全相同 image-20220601140247974
  • db.test.watch( [], {maxAwaitTimeMS:300000} ),通过另一个命令行插入了数据,就会有提示

2.6.开发要点

  • 如果一个游标已经遍历完,会自动关闭。如果没有遍历完,则需要手动调用 close() 方法,否则该游标将在服务器上存在 10 分钟(默认值)后超时释放,造成不必要的资源浪费
  • 索引
    • 没有资源隔离,一个没有索引的慢查询可能影响到所有其他操作。尽量使索引覆盖要查询的内容,避免查询数据文件
    • 使用projection 减少返回到客户端的的文档的内容
  • 写入
    • 在 update 语句里只包括需要更新的字段
    • 尽可能使用批量插入来提升写入性能
  • 文档结构
    • 防止使用太长的字段名(浪费空间)
    • 防止使用太深的数组嵌套(超过2层操作比较复杂)
    • 不使用中文,标点符号等非拉丁字母作为字段名
  • 分页
    • 使用limit限制返回的条数,避免遍历整个数据库 db.coll.find({x: 100}).limit(50)
    • 分页跳转代替传统遍历 image-20220601160730729
  • 事务
    • 尽可能不用
    • 不要过大(1000个文档更新以内),因为有60s限制
    • 当必须使用事务时,尽可能让涉及事务的文档分布在同一个分片上
  • 实际操作参数
    • 禁用 NUMA,否则在某些情况下会引起突发大量swap交换
    • 禁用 Transparent Huge Page,否则会影响数据库效率
    • tcp_keepalive_time 调整为120秒,避免一些网络问题
    • ulimit -n,避免打开文件句柄不足的情况
    • 关闭 atime,提高数据文件访问效率

3.python操作

3.1.基本操作

1
2
3
# 连接数据库,创建库和表
from pymongo import MongoClient
client = MongoClient("mongodb://127.0.0.1:27017")
1
2
3
4
5
6
7
8
# 插入一条数据
new_user = {"username": "nina", "password": "xxxx", "email":"123456@qq.com "}
result = client['数据库名']['表名'].insert_one(new_user)

# 一次插入多条数据
# 传入一个数组,数组中每个元素都是一个dict。如果用了json序列化,会变为一个字符串,记得反序列化回来
datas = [{"a":1,"b":1},{"a":1,"b":2},{"a":2,"b":1},{"a":2,"b":2}]
result = client['数据库名']['表名'].insert_many(datas)
1
2
# 更新一条数据
result = client['数据库名']['表名'].update_one( { "username": "nina"},{ "$set": { "phone": "123456789"} } )
1
2
3
4
5
# 搜索数据
mask = {"_id": 0, "a": 1, "b": 1, "c": 1}
query = {"a": [条件a], "b": [条件b]}
result = client['数据库名']['表名'].find(query, mask)
return list(result)

3.2.spark+mongo

  • 架构图 image-20220606135415828
  • 具体操作:坑比较多,python对spark的数据格式支持还不是很好。以后再写

4.架构

4.1.复制集

  • 原理:mongodb要求奇数个(通常是3)节点来保证投票机制,主节点负责读写,从节点无写入能力,只能读或者从主节点把数据同步过来。当一个修改操作到达主节点时,对数据的操作将被记录下来(经过一些必要的转换),这些记录称为oplog。从节点通过在主节点上打开一个tailable游标不断获取新进入主节点的oplog,并在自己的数据上回放,以此保持跟主节点的数据一致

  • 实现:具有投票权的节点之间两两互相发送心跳,当5次心跳未收到时判断为节点失联。如果失联的是主节点,从节点会发起选举,选出新的主节点;如果失联的是从节点则不会产生新的选举。选举基于RAFT一致性算法实现,选举成功的必要条件是大多数投票节点存活。复制集中最多可以有50个节点,但具有投票权的节点最多7个

  • 选举条件

    • 大多数节点存活
    • 新的主节点
      • 能与多数节点建立连接
      • oplog较新
      • 优先级较高(如果有配置)
  • 复制集节点常见选配项
    • 是否具有投票权(V参数)
    • 优先级(priority参数):越高越好,0不能成为主节点
    • 隐藏(hidden参数):复制数据,但对应用不可见。可以具有投票仅,但优先级必须为0
    • 延迟(slaveDelay参数):复制n秒之前的数据,保持与主节点的时间差
  • 增加节点不会提高写操作性能

4.2.分片集

  • 需要多种不同类型节点 image-20220602023731069
    • mongos路由节点,提供唯一访问入口。三台是做了高可用冗余,实际一台也行
    • config配置节点,分片集元数据,包括集群分片的索引映射等。三台是做了高可用冗余
    • 每个分片必须是一个复制集
  • 应用透明、自动均衡、动态扩容、提供三种分片方式
    • 基于范围:范围查询性能好,但数据分布可能不均匀
    • 基于哈希:数据分布均匀,写优化,但范围查询效率低。适用于日志,物联网等高并发场景
    • 基于Tag:自定义
  • 额外消耗比较大,管理复杂
  • 分片数的计算:取最大值
    • 总数据量 / 硬盘(<2T)
    • 热数据+索引 / 内存 * 0.6
    • 总并发量 / 并发量 * 0.7

4.3.索引

  • 索引的结构 image-20220603095944708
  • 建议优先使用过滤性强的字段作为索引
  • 查询时后面加上.explain(true)可以打印详细的检索信息
  • 精确匹配写在最前面,排序字段写中间,范围匹配放在最后来建索引
  • 地理位置索引 image-20220605004751983
  • 部分索引 image-20220605005029396
  • 全文索引 image-20220605005118540

4.4.性能监控

  • mongostat,监控过去1秒内所有资源使用情况。脏数据超过20%或内存占用超过物理内存的60%的95%时阻塞新请求
  • mongotop,排查是哪个表的读写消耗性能
  • pip install mtools,使用python可视化展示性能情况
    • mplotqueries [日志文件],将所有慢查询通过图表形式展现
    • mloginfo --queries [日志文件],总结所有慢查询的模式和出现次数、消耗时间等

4.5.全球集群

  • 架构图 image-20220605232341879
  • 可以做到多个主节点同时读写
  • 模型中增加区域字段、分片中加区域标签、给每个区域指定分片块范围


参考链接

  1. MongoDB官网下载

本博客所有文章除特别声明外均为原创,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!