mongodb 入门
前言
MongoDB 是一种专为快速开发互联网 Web 应用而设计的数据库,它的设计目标是简洁、灵活,并且能够作为 Web 应用开发栈的重要组成部分。在众多的非关系型数据库中,MongoDB 最接近关系型数据库的特性和表现,这使得它在处理复杂数据结构时,既具有非关系型数据库的灵活性,又不失关系型数据库的严谨性。
在接下来的文章中,我将从零开始,介绍 MongoDB 的基本使用,让各位读者能够顺利地入门这个强大的数据库。
为什么是 MongoDB
MongoDB 的优势很明显,它基于 Json 的数据模型十分贴合开发者的设计思维,灵活动态的 schema 能够极大简化数据库的设计流程。以下引用MongoDB 开发者团队的一次技术分享,你可以根据项目状况判断是否要选择 MongoDB。
如果你还在为是否应该使用 MongoDB 而发愁,不如做几个选择题来辅助决策:
- 应用不需要事务及复杂 join 支持新应用;
- 需求会变,数据模型无法确定,想快速迭代开发;
- 应用需要2000-3000以上的读写QPS(更高也可以);
- 应用需要TB甚至 PB 级别数据存储;
- 应用发展迅速,需要能快速水平扩展;
- 应用要求存储的数据不丢失;
- 应用需要99.999%高可用;
- 应用需要大量的地理位置查询、文本查询;
如果上述有1个 Yes,可以考虑 MongoDB,2个及以上的 Yes,选择 MongoDB 绝不会后悔。
Tips:本小节内容摘抄于 MongoDB 中文社区,原文链接。
安装和连接
本地部署
下载 MongoDB
在 MongoDB 官网下载系统对应的 Mongo 版本。
我使用的是 ubuntu-22.04 ,在页面上选择 ubuntu 22.04 x64
的版本,复制下载链接,通过 wget 将内容下载下来。
1 | cd && mkdir mongo && cd mongo |
解压安装包:
1 | tar -zxvf mongodb-linux-x86_64-ubuntu2204-6.0.15.tgz |
自行管理 mongodb 的可执行文件,可以建立短链到可执行文件夹中,也可以将 mongodb/bin 添加到 PATH 环境变量中。
启动 MongoDB 服务
首先需要创建 MongoDB 相关的文件夹和文件:
1 | mkdir log conf data |
将以下内容写入 conf/mongo.conf
1 | systemLog: |
准备工作做完了,就可以启动 MongoDB 了:
1 | mongod -f ~/mongo/conf/mongo.conf |
使用 docker 安装
1 | cd |
使用 mongosh 连接 mongo 服务
安装 mongosh
下载:
可以到 官网 下载各自系统的版本
1 | wget https://downloads.mongodb.com/compass/mongosh-2.2.5-linux-x64.tgz |
解压:
1 | tar -zxvf mongosh-2.2.5-linux-x64.tgz |
自行管理 mongosh 的可执行文件,可以建立短链到可执行文件夹中,也可以将 mongosh/bin 添加到 PATH 环境变量中。
建立连接
1 | mongosh --host 127.0.0.1 --port 27017 |
操作 mongodb
相关概念
database
,数据库,与关系型数据库的 数据库
对标;
collection
,集合,与关系型数据库的 表
对标;
document
,文档,与关系型数据库的 记录
对标;
在mongodb中,数据库和集合都不需要手动创建,在使用时若是不存在会自动创建
以下是一些基本的查看数据库表的相关命令:
1 | show dbs; // 数据库列表 |
CRUD 操作
插入文档
1 | db.<collection>.insert(doc) |
向集合中插入数据时若是没有指定 _id
, mongodb 会自动指定,作为该文档的唯一标识,可以使用Object()来生成一个。
查询文档
1 | db.<collection>.find(doc) // 查询集合中所有符合条件的文档,doc即为查询的条件,为空或不传标识查询所有,返回的是一个数组,使用.count()或length()可以查询符合条件的文档数量 |
修改文档
1 | db.<collection>.update(查询doc,新doc) |
update 默认会使用新对象来替换就对象,并不是部分修改,如果需要部分修改,需要使用 修改操作符 来完成
$set
:只修改指定的属性$unset
:删除指定的属性- 更多的操作符可到操作符文档查询
1 | // eg: |
删除文档
1 | db.<collection>.remove(doc) // 默认删除所有符合条件的文档 == deleteMany |
用户的创建修改和删除
用户角色与权限说明
分类 | role(角色) | 简要说明 |
---|---|---|
数据库用户角色(DB User Roles) | read readWrite |
为某个数据库创建一个用户, 分配该数据库的读写权力 |
数据库管理员角色(DB Admin Roles) | dbAdmin dbOwner userAdmin |
拥有创建数据库, 和创建用户的权力 |
集群管理角色(Culster Administration Roles) | clusterAdmin clusterManager clusterMonitor hostManager |
管理员组, 针对整个系统进行管理 |
备份还原角色(Backup and Restoration Roles) | backup restore |
备份数据库, 还原数据库 |
所有数据库角色(All-Database Roles) | readAnyDatabase readWriteAnyDatabase userAdminAnyDatabase dbAdminAnyDatabase |
拥有对admin操作的权限 |
Superuser Roles(超级管理员) | root dbOwner userAdmin userAdminAnyDatabase |
这几个角色角色提供了任何数据任何用户的任何权限的能力,拥有这个角色的用户可以在任何数据库上定义它们自己的权限 |
创建用户
在哪一个数据库中创建的用户,就是属于哪个数据库的用户,可以在这个数据库中进行授权认证。不过创建完的角色数据是保存在 admin 库中的。
如在 admin 库中创建的用户,就只能在 admin 库中进行认证,否则认证将会不通过。
1 | db.createUser({user:'test',pwd:'test',roles:[{role:'readWrite',db:'testDB'}]}) |
1 | use admin |
修改用户
1 | db.updateUser([用户名],{用户对象, 与创建时一样}) |
删除用户
1 | use testDB |
mongo中文档之间的关系
一对一
使用内嵌的方式表明关系
一对多
可以使用内嵌,也可以使用指定id的方式表明关系
多对多
使用id数组的方式表明关系
mongo的索引
创建索引
mongodb 的索引通过createIndex()来创建
db.
key为要创建索引的字段,1为指定按升序创建索引,-1为降序
可以指定多个字段来创建索引,同关系数据库中的复合索引
option可接受的参数如下
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。 |
dropDups | Boolean | **3.0+版本已废弃。**在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
加了索引不一定比不加索引快
索引相当于目录,新加了数据都要重新维护这个目录,在做增伤改的时候需要更多的耗时
什么时候加索引?应该给那些字段加索引?
-
给重复数据特别少的字段加索引
-
不要给经常发生改变的字段加索引
索引类型
所以可以简单分为两种:
- 普通索引:查询字段命中即会应用索引,不论升序还是降序
- 复合索引:需要满足
prefix 原则
,才会应用索引
prefix 原则:index fields的左前缀子集,如:
1 | // 符合索引如下 |
复合索引的顺序对查询结果是有影响的, sort 的顺序必须要和创建索引的顺序是一致的,一致的意思是不一定非要一样,排序的顺序必须要和索引一致,逆序之后一致也可以
{ “username” : 1, “date” : -1 } | { “username” : 1, “date” : 1 } | |
---|---|---|
sort( { username: 1, date: -1 } ) | 支持 | 不支持 |
sort( { username: -1, date: 1 } ) | 支持 | 不支持 |
sort( { username: 1, date: 1 } ) | 不支持 | 支持 |
sort( { username: -1, date: -1 } ) | 不支持 | 支持 |
mongo 聚合
聚合主要用于处理数据,如计算平均值,求和等等,并返回计算后的数据结果
mongo 的聚合通过 aggregate 方法来实现,该方法接收一个 pipeline 对象(即一个包含了聚合条件的数据)
1 | db.<COLLECTION>.aggregate(AGGREGATE_OPERATION) |
常用的聚合操作符有: $match
、$group
、$project
、$sort
、$limit
、$skip
,更多的聚合操作符及其使用,可以查阅官方文档
以下简单介绍常用的聚合操作符的使用:
1 | // 通过聚合筛选出符合条件的文档,并进行排序、分页、投影 |
mongo 高可用
副本集(Replica Set)
副本集是一种在多台机器同步数据的进程,副本集体提供了数据冗余,扩展了数据可用性。在多台服务器保存数据可以避免因为一台服务器导致的数据丢失。
也可以从硬件故障或服务中断解脱出来,利用额外的数据副本,可以从一台机器致力于灾难恢复或者备份。
MongoDB 的副本集不同于以往的主从模式。
在集群Master故障的时候,副本集可以自动投票,选举出新的Master,并引导其余的Slave服务器连接新的Master,而这个过程对于应用是透明的。可以说MongoDB的副本集是自带故障转移功能的主从复制。
一个副本集即为服务于同一数据集的多个 MongoDB 实例,其中一个为主节点,其余的都为从节点。主节点上能够完成读写操作,从节点仅能用于读操作。主节点需要记录所有改变数据库状态的操作,这些记录 保存在 oplog 中,这个文件存储在 local 数据库,各个从节点通过此 oplog 来复制数据并应用于本地,保持 本地的数据与主节点的一致。oplog 具有幂等性,即无论执行几次其结果一致,这个比 mysql 的二进制日志更好用。
集群中的各节点还会通过传递心跳信息来检测各自的健康状况。当主节点故障时,多个从节点会触发一次 新的选举操作,并选举其中的一个成为新的主节点(通常谁的优先级更高,谁就是新的主节点),心跳信息默认每 2 秒传递一次。
副本集包括三种节点:主节点、从节点、仲裁节点。
1)主节点负责处理客户端请求,读、写数据, 记录在其上所有操作的 oplog;
2)从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。默认情况下,从节点不支持外部读取,但可以设置;副本集的机制在于主节点出现故障的时候,余下的节点会选举出一个新的主节点,从而保证系统可以正常运行。
3)仲裁节点不复制数据,仅参与投票。由于它没有访问的压力,比较空闲,因此不容易出故障。由于副本集出现故障的时候,存活的节点必须大于副本集节点总数的一半,否则无法选举主节点,或者主节点会自动降级为从节点,整个副本集变为只读。因此,增加一个不容易出故障的仲裁节点,可以增加有效选票,降低整个副本集不可用的风险。仲裁节点可多于一个。也就是说只参与投票,不接收复制的数据,也不能成为活跃节点。
官方推荐MongoDB副本节点最少为3台, 建议副本集成员为奇数,最多12个副本节点,最多7个节点参与选举。
副本集的节点数最好为奇数,避免脑裂的发生。
副本集的工作流程
主节点负责处理客户端的读写请求,备份节点负责映射主节点的数据,保证与主节点的数据同步。
oplog(operation log),主节点操作记录,所有主节点的操作都会记录在这个文件中,这个文件存储在local数据库中的’oplog.rs’表中,从节点就是加载了这个oplog,然后执行它所记录的操作,从而达到同步主节点数据的作用。
关于oplog,有几个注意的地方:
- oplog只记录改变数据库状态的操作
- 存储在oplog中的操作并不是和主节点执行的操作完全一致,如"$inc"就会被转化成"$set"操作
- oplog 存储在固定集合中,当oplog的数量超过oplogSize,新的操作就会覆盖旧的操作
可以通过命令 “db.printReplicationInfo()” 查看oplog的信息
以下为该命令展示的信息中的字段说明:
configured oplog size: oplog 文件大小
log length start to end: oplog 日志的启用时间段
oplog first event time: 第一个事务日志的产生时间
oplog last event time: 最后一个事务日志的产生时间
now: 现在的时间可以通过命令 “db.printSlaveReplicationInfo()” 查看 slave 的状态
在副本集中有两种数据同步方式,一种是初始化,一种是复制。
- 初始化。这个过程发生在当副本集中创建一个新的数据库或其中某个节点刚从宕机中恢复,或者向副本集中添加新的成员的时候,默认的,副本集中的节点会从离 它最近的节点复制 oplog 来同步数据,这个最近的节点可以是 primary 也可以是拥有最新 oplog 副本的 secondary 节点。该操作一般会重新初始化备份节点,开销较大。
- 复制。在初始化后这个操作会一直持续的进行着,以保持各个 secondary 节点之间的数据同步。
当遇到无法同步的问题,可以使用一下两种方式进行初始化。
- 停止该节点,删除目录中的文件,重新启动该节点,这样这个节点就会执行初始化。这种方式的 sync 时间取决于数据量的大小,若是数据量太大,sync时间就会很长,可能会影响其他节点的工作
- 停止该节点,然后删除目录中的左右文件,找一个比较新的节点,把这个节点中的文件拷贝到停止节点的目录中,重启节点。
副本集的数据同步过程
当主节点完成数据操作后,从节点将会:
- 检查自己local库的oplog.rs集合,找出最后的时间戳
- 检查主节点的local库的oplog.rs集合,找出大于此时间戳的记录
- 将找到的记录插入自己的oplog.rs集合,并执行这些操作
mongodb事务
必要条件:版本4.0以上,必须有集群
writeConcern
事务使用 writeConcern
来提交写操作,通过其来决定写操作是否成功
1 | writeConcern:{ |
w标识数据写到多少个节点才算成功,默认值为 1
- 0:不需要确认,直接成功
- 1~n:数据成功写到n个节点才算成功
- majority:写入超过半数的节点数才算成功
- all:全部写入成功才算成功
j定义如何才算成功
- true:写操作落到journal文件中才算成功
- false:写操作到达内存即算成功
对重要的数据需要至少保证 writeConcern 为 majority
,保证数据安全性
不要将其设置为all,万一一个节点挂了整个应用就挂了
readPreference
事务使用 readPreference
决定使用哪一个节点来满足正在发起的读请求,可选值包括:
- primary:只选择主节点
- primaryPreferred:优先主节点,主节点不可用会选择从节点
- secondary:只选择从节点
- secondaryPreferred:优先从节点,从节点不可用才会选择主节点
- nearest:选择最近的节点
也可以使用 tag 来标记多个节点,控制节点作为某种用途
建议:对时效性高的使用primaryPreferred,对时效性没有要求的使用secondaryPreferred,生成报表使用secondary,将用户上传的图片发送到全世界,让用户就近读取可以使用nearest
事务使用示例
主节点开启事务并写数据:
1 | sess = db.getMongo().startSession(); |
从节点读取数据:
1 | db.students.find().readPref("secondary") |
使用 Golang 客户端操作 MongoDB
bson
GO Driver 提供了四种主要类型来处理BSON数据
- D :bson 文档(切片)的有序表示
- M:bson 文档(切片)的无序表示
- A :bson 数组的有序表示
- E :D类型中的单个元素
1 | bson.D{ // 这个是 D 类型,实质是一个 E 类型的切片 |
关于 bson 的详细解释可以参考官方文档
连接 mongodb
1 | // URL格式: mongodb://user:pass@host:port/?maxPoolSize=20&w=majority |
可以使用 ping() 来检测连接
连接时可以指定更多的连接参数,详细的参数描述可以在文档或者代码中查看:
1 | clientOpts := options.Client() |
获取数据库和集合
1 | // 数据库列表 |
查询操作
具有文字值的匹配条件使用以下格式:
1 | filter := bson.D{{"<field>", "<value>"}} |
带有查询运算符的匹配条件使用以下格式:
1 | filter := bson.D{{"<field>", bson.D{{"<operator>", "<value>"}}}} |
查询一条文档使用:
1 | func (coll *Collection) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) *SingleResult |
指定查询具体的字段(投影)可以使用:
1 | users.FindOne(ctx,filter,options.FindOne().SetProjection(bson.M{})) |
其他的参数选项参考官方文档
查询多条记录使用:文档
1 | func (coll *Collection) Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) (cur *Cursor, err error) |
查询符合条件的文档数量使用:文档
1 | func (coll *Collection) CountDocuments(ctx context.Context, filter interface{}, opts ...*options.CountOptions) (int64, error) |
跟查找相关的还有如下方法:
1 | FindOneAndDelete() |
插入文档
插入单个文档到集合中使用:
1 | func (coll *Collection) InsertOne(ctx context.Context, document interface{}, opts ...*options.InsertOneOptions) (*InsertOneResult, error) |
插入多个文档到集合中使用:
1 | func (coll *Collection) InsertMany(ctx context.Context, documents []interface{}, opts ...*options.InsertManyOptions) (*InsertManyResult, error) |
删除文档
删除单个文档使用:
1 | func (coll *Collection) DeleteOne(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*DeleteResult, error) |
删除多个文档使用:
1 | func (coll *Collection) DeleteMany(ctx context.Context, filter interface{}, opts ...*options.DeleteOptions) (*DeleteResult, error) |
options:
1 | Hint():用于扫描要删除的文档的索引。默认:nil |
更新文档
更新单个文档使用:
1 | func (coll *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*UpdateResult, error) |
更新多个文档使用:
1 | func (coll *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{}, opts ...*options.UpdateOptions) (*UpdateResult, error) |
关于更多的参数选项等可以参考官方文档以及api文档
结语
在本文中,我们对 MongoDB 进行了初步的探索,包括了如何下载、安装,如何使用 mongosh 进行数据库操作,以及如何创建索引、进行数据聚合、处理事务、mongo 副本集,以及如何利用 Golang 进行 MongoDB 操作等主题。由于本文的目标是帮助读者入门,对于以上的各个主题只进行了基础的讲解和示例演示,如果有更深层次的使用需求,可以查阅官方文档了解,也可以关注一下后续的文章。