MongoDB再入门
MongoDB 重新定义 OLTP 数据库
2018 4.x 开始支持分布式事务
MongoDB vs. 关系型数据库
- 多形性:同一个集合中可以包含不同字段(类型)的文档对象
- 动态性:线上修改数据模式,修改时应用与数据库均无须下线
- 数据治理:支持使用 JSON Schema 来规范数据模式。在保证模式 灵活动态的前提下,提供数据治理能力
JSON 模式的快速特性:
- 数据库引擎只需要在一个存储区读写
- 反范式、无关联的组织极大优化查询速度
- 程序API自然,开发快速
安装
https://www.mongodb.com/download-center/community
选择版本(Mongo 稳定版为双号版本,如4.2)及操作系统、软件包:以 CentOS 7.0为例:
1 2 3 4 5 6 |
sudo mkdir -p /data/db cd /data/ curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.2.3.tgz tar -zxvf mongodb-linux-x86_64-rhel70-4.2.3.tgz export PATH=$PATH:/data/mongodb-linux-x86_64-rhel70-4.2.3/bin # 可加入~/.bash_profile 等启动文件中 mongod --dbpath /data/db --port 27017 --logpath /data/db/mongod.log --fork # 添加 --bind_ip_all允许外网访问 |
Mongo 公有云:http://cloud.mongodb.com/,可使用免费账号测试集群,按照步骤操作即可,然后拷贝通过 Mongo Shell 连接的命令进行连接:
1 |
mongo "mongodb+srv://cluster0-xxxx.mongodb.net/xxx" --username xxx |
演示数据
1 2 3 |
curl -O -k https://raw.githubusercontent.com/tapdata/geektime-mongodb-course/master/aggregation/dump.tar.gz tar -zxvf dump.tar.gz mongorestore -h localhost:27017 |
常用操作
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 |
# 插入 db.fruit.insertOne({name: "apple"}) db.fruit.insertMany([ {name: "apple"}, {name: "pear"}, {name: "orange"} ]) # 查询 db.fruit.find() db.fruit.find({name: "apple"}) db.find({$and: [{"xx": "xx"}, {"xx": "xx"}]}) db.find({$or: [{"xx": "xx"}, {"xx": "xx"}]}) db.fruit.find({name: /^a/}) # 正则表达式 # 子文档查询 db.fruit.find({"from.country": "China"}) # 单个字段 db.getCollection('movies').find({ 'film_locations': { $elemMatch: {"city": "Rome", "Country": "USA"} } }) # 多个字段使用$elemMatch db.movies.find({"category": "action"}, {"_id":0, title:1}) # 返回指定字段:返回title,不返回_id,这称为projection # 删除 db.testcol.remove({a: 1}) # 删除 a 等于1 的记录 db.testcol.remove({a: {$lt: 5}}) # 删除 a 小于5的记录 db.testcol.remove({}) # 删除所有记录 db.testcol.drop() # 删除集合 use testdb; db.dropDatabase() # 删除数据库 # 更新 db.fruit.updateOne({name: "apple"}, {$set: {from: "China"}}) # 更新多条使用 updateMany,第一项为查询条件,第二项为更新内容,支持$set/$unset, $push/$pushAll/$pop, $pull/$pullAll, $addToSet |
SQL | MQL |
---|---|
a = 1 | {a: 1} |
a <> 1 | {a: {$ne: 1}} |
a > 1 | {a: {$gt: 1}} |
a >= 1 | {a: {$gte: 1}} |
a < 1 | {a: {$lt: 1}} |
a <= 1 | {a: {$lte: 1}} |
a = 1 AND b = 1 | {a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]} |
a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |
a IS NULL | {a: {$exists: false}} |
a IN (1, 2, 3) | {a: {$in: [1, 2, 3]}}} |
Python 操作 MongoDB
1 2 3 4 5 6 7 8 9 10 11 12 13 |
pip install pymongo # 安装驱动程序 # 代码示例 from pymongo import MongoClient uri = "mongodb://127.0.0.1:27017" client = MongoClient(uri) db = client['eshop'] user_col = db["users"] new_user = {"username": "nina", "password": "123456", "email": "abcd@gmail.com"} insert_result = user_col.insert_one(new_user) update_result = user_col.update_one({"username": "nina"}, {"$set": {"phone": "123456789"}}) |
URI的格式参考:mongodb://[username:password@]host1[:port1][,…hostN[:portN]][/[database][?options]]
MongoDB的聚合(Aggregation)运算
聚合运算过程称为管道(Pipeline),由多个步骤(Stage)组成
聚合运算的基本格式
1 2 3 4 5 |
pipeline = [$stage1, $stage2, ...$stageN]; db.<COLLECTION>.aggregate( pipeline, { options } ); |
常见步骤
步骤 | 作用 | SQL 等价运算符 |
---|---|---|
$match | 过滤 | WHERE |
$project | 投影 | AS |
$sort | 排序 | ORDER BY |
$group | 分组 | GROUP BY |
$skip/$limit | 结果限制 | SKIP/LIMIT |
$lookup | 左外连接 | LEFT OUTER JOIN |
$unwind | 展开数组 | N/A |
$graphLookup | 图搜索 | N/A |
$facet/$bucket | 分面搜索 | N/A |
常见步骤中的运算符
$match
- $eq/$gt/$gte/$lt/$lte
- $and/$or/$not/$in
- $geoWithin/$intersect
$project(用于选择需要的或排除不需要的字段)
- $map/$reduce/$filter
- $range
- $multiply/$divide/$substract/$add
- $year/$month/$dayOfMonth/$hour/$minute/$second
$group
- $sum/$avg
- $push/$addToSet
- $first/$last/$max/$min
测试库实操:
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 |
# 订单额汇总 db.orders.aggregate([ { $group:{ _id: null, total: {$sum: "$total"} } } ]) # 查询2019第一季度(1月1日~3月31日)已完成订单的订单总金额和订单总数 db.orders.aggregate([ {$match: {status: "completed", orderDate: { $gte: ISODate("2019-01-01"), $lt: ISODate("2019-04-01") }}}, {$group: { _id: null, total: {$sum: "$total"}, shippingFee: {$sum: "$shippingFee"}, count: {$sum: 1} }}, {$project: { grandTotal: {$add: ["$total", "$shippingFee"]}, count: 1, _id: 0 }} ]) |
复制集
MongoDB 复制集的主要意义在于实现高可用。同时还实现了几个附加作用:
- 数据分发:将数据从一个区域复制到另一个区域,减少另一个区域的读延迟
- 读写分离:不同类型的压力分别在不同的节点上执行
- 异地容灾:在数据中心故障时快速切换到异地
数据如何复制?
- 当一个修改操作(插入、更新或删除)到达主节点时,它对数据的操作被记录下来(经过一些必要的转换),这些记录称为 oplog
- 从节点通过在主节点打开一个 tailable 游标不断获取新进入主节点的 oplog,并在自己的数据上回放,以此保持跟主节点的数据一致
通过选举完成故障恢复
- 具有投票权的节点之间两两互相发送心跳
- 当5次心跳未收到时判断为节点失联
- 如果失联的是主节点,从节点会发起选举,选出新的主节点
- 如果失联的是从节点则不会产生新的选举
- 选择基于 RAFT 一致性算法实现,选举成功的必要条件是大多数投票节点存活
- 复制集中最多可以有50个节点,但具有投票权的节点最多有7个
复制集节点的常见选项:
- 是否具有投票权(v 参数)
- 优先级(priority 参数)
- 隐藏(hidden 参数)
- 延迟(slaveDelay 参数)
实操
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
mkdir -p /data/db{1,2,3} # db2, dh3配置文件参见 db1,修改日志路径、数据目录和端口为28018和28019 # /data/db1/mongod.conf systemLog: destination: file path: /data/db1/mongod.log logAppend: true storage: dbPath: /data/db1 net: bindIp: 0.0.0.0 port: 28017 replication: replSetName: rs0 processManagement: fork: true # 启动复制集节点 mongod -f db1/mongod.conf mongod -f db2/mongod.conf mongod -f db3/mongod.conf |
配置复制集
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 |
# 选定主节点 mongo localhost:28017 # 方法一 > rs.initiate()) # 查看状态:rs.status() > rs.add("HOSTNAME:28018") # 将 HOSTNAME 替换为你自己的主机名 > rs.add("HOSTNAME:28019") > rs.status() # 方法二 > rs.initiate({ _id: "rs0", members: [{ _id:0, host: "localhost:28017" },{ _id:1, host: "localhost:28018" },{ _id:0, host: "localhost:28019" }] }) # 连接从节点: mongo localhost:28018 > rs.slaveOk() # 执行此语句后方可在从节点上读取 |
MongoDB 全家桶
软件模块 | 描述 |
---|---|
mongod | MongoDB 数据库软件 |
mongo | MongoDB 命令行工具,管理 MongoDB 数据库 |
mongos | MongoDB 路由进程,分片环境下使用 |
mongodump/mongorestore | 命令行数据库备份与恢复工具 |
mongoexport/mongoimport | CSV/JSON 导入与导出,主要用于不同系统间数据迁移 |
Compass | MongoDB GUI 管理工具 |
Ops Manager(企业版) | MongoDB 集群管理软件 |
BI Connector(企业版) | SQL 解释器/BI 套接件 |
MongoDB Charts(企业版) | MongoDB 可视化软件 |
Atlas(付费及免费) | MongoDB 云托管服务,包括永久免费云数据库 |
1 2 |
mongodump -h 127.0.0.1:27017 -d test -c test mongorestore -h 127.0.0.1:27017 -d test -c test xxx.bson |
从熟练到精通的开发之路
MongoDB文档模型设计三部曲
1-1关系建模
基本原则
- 一对一关系以内嵌为主
- 作为子文档形式或者直接在顶级
- 不涉及到数据冗余
例外情况
- 如果内嵌后导致文档大小超过16MB
1-N 关系建模
基本原则
- 一对多关系同样以内嵌为主
- 用数组来表示一对多
- 不涉及到数据冗余
例外情况
- 内嵌后导致文档大小超过16MB
- 数组长度太大(数万或更多)
- 数组长度不确定
N-N 关系建模
基本原则
- 不需要映射表
- 一般用内嵌数组来表示一对多
- 通过冗余来实现 N-N
例外情况
- 内嵌后导致文档大小超过16MB
- 数组长度太大(数万或更多)
- 数组长度不确定
关联表查询示例
1 2 3 4 5 6 7 8 9 10 11 |
db.contacts.aggregate([ { $lookup: { from: "groups", # 关联表 localField: "group_ids", # 本地字段 foreignField: "group_id", # 目标表字段 as: "groups" } } ]) |
MongoDB 引用设计的限制
- MongoDB 对使用引用的集合之间并无主外键检查
- MongoDB 使用聚合框架的$lookup 来模仿关联查询
- $lookup 只支持 left outer join
- $lookup 的关联目标(from)不能是分片表
表现形式类 | 数据访问类 | 组织结构类 |
---|---|---|
列转行 | 子集 | 预聚合 |
文档版本 | 近似处理 | 分桶 |