1什么是MongoDB
MongoDB是一个以JSON为数据模型的文档数据库,所谓“文档”,就是“JSON Document”,并不是我们一般理解的pdf,word,excel文档。
和其他数据库的类型做一个类比:
- 关系型数据库管理系统,比如MySQL,Oracle,SQL Server,Postgresql等
- 键-值存储,比如大名鼎鼎的Redis,MemCached
- 文档存储,就是它MongoDB,还有我不了解的CouchDB,Couchbase
- 大数据存储系统,HBASE,Google Bigtable
- 基于Hadoop的数据分析系统,Hive,Spark
- 文本查询系统,比如Lucence,Solr,还有常用的Elasticsearch
直观的看一下,MongoDB存储的数据长这样:
MongoDB文档类型
有这么多可供选择的数据存储,我们为什么还要学习MongoDB呢?
- 高性能:MongoDB提供高性能的数据持久性。特别是对嵌入式数据模型的支持减少了数据库系统上的I/O活动。
- 高可用:MongoDB的副本集(replica set)可提供自动故障转移和数据冗余。
- 高扩展:MongoDB提供了水平可扩展性。分片将数据分布在一组集群的机器上。比如海量数据存储,服务能力可水平扩展。
- 丰富的查询支持:MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。
2快速上手
单节点安装
生产环境的服务器一般都是Linux系统的,我这里也用Linux虚拟机来模拟服务器环境,将MongoDB安装在Linux虚拟机上。
学习一个新东西最好的方式就是上官网,MongoDB的官网地址:
www.mongodb.com
but,这个网址很难打开(我基本上试了了10次能打开1次。。。),从官网上很容易找到下载地址。
安装极其简单,步骤:
代码语言:javascript复制# 创建MongoDB的目录
mkdir -p /usr/local/mongodb/data
mkdir -p /usr/local/mongodb/conf
mkdir -p /usr/local/mongodb/logs
# 下载MongoDB安装压缩文件
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.5.tgz
# 解压缩
tar -zxvf mongodb-linux-x86_64-rhel70-4.4.5.tgz
# 重命名(这是习惯问题,可以不用重命名)
mv mongodb-linux-x86_64-rhel70-4.4.5 mongodb-4.4.5
# 配置环境变量
vi /etc/profile
# 在文件末尾
export MONGODB_HOME=/usr/local/mongodb/mongodb-4.4.5
export PATH=$PATH:$MONGODB_HOME/bin
# 是配置文件生效
source /etc/profile
# 验证
[root@mongodb-standalone-99 mongodb]# mongo --version
MongoDB shell version v4.4.5
Build Info: {
"version": "4.4.5",
"gitVersion": "ff5cb77101b052fa02da43b8538093486cf9b3f7",
"openSSLVersion": "OpenSSL 1.0.1e-fips 11 Feb 2013",
"modules": [],
"allocator": "tcmalloc",
"environment": {
"distmod": "rhel70",
"distarch": "x86_64",
"target_arch": "x86_64"
}
}
MySQL中有备份、恢复相关的命令,MongoDB中也有对应的命令,不过需要我们安装一个工具。安装&使用方式:
- wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel70-x86_64-100.3.1.tgz
- tar -zxvf mongodb-database-tools-rhel70-x86_64-100.3.1.tgz
- mv mongodb-database-tools-rhel70-x86_64-100.3.1 mongodb-dabase-tools
- 配置环境变量 vi /etc/profile
- source /etc/profile
- 使用方式:
- 导入数据 mongorestore -h localhost:27017 -d collectionName --dir /mongodb/backupdata/
- 导出数据 mongodump -h localhost:27017 -d collectionName -o /mongodb/
基本命令
启动
可以直接使用命令:
代码语言:javascript复制mongod --dbpath /usr/local/mongodb/data --port 27017 --logpath /usr/local/mongodb/logs/mongod.log --fork
启动,也可以结合配置文件启动,一般我们采取配置文件的方式启动,配置文件内容:
代码语言:javascript复制systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb/logs/mongo.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
dbPath: "/usr/local/mongodb/data"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
pidFilePath: "/usr/local/mongodb/logs/mongod.pid"
net:
#服务实例绑定的IP,0.0.0.0,任何IP都可以访问
bindIp: 0.0.0.0
#绑定的端口
port: 27017
启动命令:
代码语言:javascript复制mongod -f /usr/local/mongodb/conf/mongod.conf
启动成功
注意,配置文件是yml格式的,对格式的要求很严格,有些时候,mongo启动不成功就是配置文件有问题,可以拿到idea里格式化一下。
连接
客户端连接MongoDB可以Shell连接,也可以使用工具(一般用MongoDB Compass)连接。
命令:
代码语言:javascript复制mongo
或者
代码语言:javascript复制mongo --host=127.0.0.1 --port=27017
其中,mongo
命令默认链接本地端口默认27017, --host=127.0.0.1 --port=27017可以配置。
TIP:MongoDB javascript shell 是一个基于javascript的解释器,所以支持js程序。比如,可以这样:
MongoDB Compass连接
到官网上下载MongoDB Compass这个软件就行了,很好用。
数据库
- 切换或创建数据库
> use dbname
如果数据库不存在则自动创建,如果存在,则切换到dbname数据库。
TIP:数据库的名称可以是满足以下条件的任意UTF-8字符串
代码语言:javascript复制1. 不能是空字符串
2. 不得含有' '(空格)和.和$和/和和 (空字符)
3. 应全部小写
4. 最多64字节
- 查看
> db
test
> show databases
admin 0.000GB
config 0.000GB
local 0.000GB
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
>
TIP1:db
命令用于查看当前操作的数据库,MongoDB默认连接的是test
数据库,如果没有选择其他数据库,集合默认存放在test数据库中。
再比如这个例子:
show dbs没有显示刚创建的数据库
可以看到,虽然 use noc
创建了数据库noc,但是在显示的时候并没有这个数据库。
TIP2:在MongoDB中,数据库/集合只有在内容插入后才会真正创建,上例中,要显示 noc
数据库,我们需要先插入一些数据。
插入数据后显示
- 删除数据库
> db.dropDatabase()
{ "dropped" : "noc", "ok" : 1 }
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
主要用来删除已经持久化的数据库。
- 修复数据库
mongod --repair --dbpath=/use/local/mongodb/data
修复数据库慎用,
慎用mongod repair
代码语言:javascript复制在没有其他选择的时候才用 mongod -repair 。
在修复过程中,该操作删除且不保存任何损坏的数据。
集合(Collection,对应MySQL的表)
集合的操作命令,上面已经举例的差不多了,
显示的创建一个集合:
代码语言:javascript复制db.createCollection(name)
db.createCollection("user")
隐式的创建集合:
代码语言:javascript复制db.user.insert({name: "张三"})
这一句创建了user集合,并向集合中插入一条文档。
删除集合:
代码语言:javascript复制> db.user.drop()
查看集合:
代码语言:javascript复制> show collections
文档(Document,对应MySQL表中的字段)
新增
- 单个文档
语法格式:
代码语言:javascript复制db.<集合>.insert(<JSON对象>)
db.<集合>.insertOne(<JSON对象>)
db.<集合>.save(<JSON对象>)
e.g.
代码语言:javascript复制db.shop.insert({name:"家电", price:4000})
- 多个文档
语法格式:
代码语言:javascript复制db.<集合>.insertMany([<JSON 1>,<JSON 2>,....<JSON n>])
e.g.
代码语言:javascript复制db.shop.insertMany([{name:"手机",price:3000},{name:"电脑",price:6000},{name:"日用百货",price:50}])
查询
- 通用查询
语法格式:
代码语言:javascript复制db.<collection>.find(<query>,[projection])
- query:可选,查询筛选器,JSON对象
- projection:可选,结果字段,JSON对象
e.g.
代码语言:javascript复制//查询所有
db.shop.find()
//查询所有,同上
db.shop.find({})
//单条件查询
db.shop.find({"name":"手机"})
//多条件查询,相当于 sql and查询
db.shop.find({"name":"手机","price":3000})
//多条件查询,同上
db.shop.find({$and:[{"name":"手机"},{"price":3000}]})
//多条件or查询,相当于sql or
db.shop.find({$or:[{"name":"手机"},{"price":4000}]})
//正则表达式查询
db.shop.find({"name":/^手/})
查询条件对照表:
SQL | MongoDB | |
---|---|---|
a = 1 | {a : 1} | 单属性字段完全匹配 |
a <> 1 | {a : {$ne : 1}} | $ne表示不存在或者存在但不等于 |
a > 1 | {a : {$gt : 1}} | $gt表示存在并且大于 |
a >= 1 | {a : {$gte : 1}} | $gte表示存在并且大于等于 |
a < 1 | {a : {$lt : 1}} | $lt表示存在并且小于 |
a <= 1 | {a : {$lte : 1}} | $lte表示存在并且小于等于 |
a = 1 and b = 1 | {a: 1, b: 1} 或者 {$and: [{a: 1}, {b: 1}]} | $and表示匹配全部条件 |
a = 1 or b = 1 | {$or: [{a: 1}, {b: 1}]} | $or表示匹配匹配两个或多个条件中的一个 |
a is null | {a: null}或者 {a: {$exists: null}} | $or表示匹配匹配两个或多个条件中的一个 |
TIP:find
搜索子文档的正确姿势
MongoDB鼓励内嵌文档,实现关联查询。
db.shop.insert({name:"电脑",category:{name:"联想",cpu:"i7"}})
要查询目录名称为联想的记录,正确的查询姿势:
db.shop.find({"category.name":"联想"})
不要这样查:
db.shop.find({"category":{"name":"联想"}})
find
搜索数组
find支持对数组中的元素进行搜索
db.shop.insert([{name:"联想",cpu:["i5","i7"]},{name:"戴尔",cpu:["i3","i7"]}])
db.shop.find({cpu:"i5"})
db.shop.find({$or:[{cpu:"i5"},{cpu:"i3"}]})
查询结果
find
搜索数组中的对象
db.shop.insert({name:"手机",brand:[{name:"华为",price:4000},{name:"小米",price:3000},{name:"苹果",price:8000}]})
db.shop.find({"brand.name": "华为"})
文档查询
TIP:当查询内嵌文档的某一个属性的时候,查询条件(字段名)一定要带上双引号,像这样{"brand.name": "华为"}
find
投影(projection)查询
如果要查询结果返回部分字段,则需要使用投影查询(不显示所有字段,只显示指定的字段),就好像MySQL中的as关键字的使用。
id字段必须明确指出不返回,否则每次默认返回:
# 查询所有文档记录,只返回name和_id字段
db.shop.find({},{"name":1})
# 不返回id字段
db.shop.find({}, {"name": 1, _id: 0})
查询指定字段
删除
代码语言:javascript复制语法格式:
db.<collection>.remove()
//删除name=手机的记录
db.shop.remove({name: "手机"})
//删除price<=3000的记录数
db.shop.remove({price: {$lte: 3000}})
//删除所有记录
db.shop.remove({})
//报错
db.shop.remove()
更新
代码语言:javascript复制语法格式:
db.<collection>.update(<查询条件>,<更新字段>)
其中条件和字段均为JSON对象
db.shop.insert([{name:"iphone12",price:8000},{name:"p40",price:5000},{name:"p30"}])
db.shop.updateOne({name:"iphone12"},{$set:{price:7500}})
db.shop.updateOne({name:"p30"},{$set:{price:3500}})
注意事项:
- db..update()同db..updateOne(),无论输入的条件匹配多少条记录,只更新第一条
- 使用db..updateMany(),输入条件匹配多少,就更新多少条
- update/updateOne/updateMany,要求更新条件部分,必须具有如下条件之一,否则报错
条件 | 含义 |
---|---|
$push | 增加一个对象到数组底部 |
$pushAll | 增加多个对象到数组底部 |
$pop | 从数组底部删除一个对象 |
$pull | 如果匹配指定的值,从数组中删除相应的对象 |
$pullAll | 如果匹配任意的值,从数据中删除相应的对象 |
$addToSet | 如果不存在则增加一个到数组 |
$set | 修改对象属性值 |
e.g.
代码语言:javascript复制db.shop.insert({name: "xiaomi", color: [1,2]})
//从底部新增
db.shop.updateOne({name: "xiaomi"}, {$push:{color:3}})
//删除
db.shop.updateOne({name: "xiaomi"}, {$pop: {color: 1}})
//错误用法
> db.shop.updateOne({name: "iphone12"}, {price: 9000})
uncaught exception: Error: the update operation document must contain atomic operators :
DBCollection.prototype.updateOne@src/mongo/shell/crud_api.js:565:19
@(shell):1:1
聚合
聚合操作:处理数据记录并返回计算结果。
聚合操作将多个文档中的值 分组 在一起,并可以对分组后的数据进行各种操作,以返回一个结果。
MongoDB中提供聚合的方法:
- 聚合管道(Aggregation Pipeline)
MongoDB的聚合框架是以数据处理流水线的概念为基础的。文档进入一个多阶段的流水线,将文档转化为一个聚合的结果。MongoDB的聚合框架是以数据处理流水线的概念为基础的。文档进入一个多阶段的流水线,将文档转化为一个聚合的结果。
举个例子,创建一个orders集合,并插入多条文档:
代码语言:javascript复制db.orders.insertMany([{cust_id:"A123",amount:500,status:"A"},{cust_id:"A123",amount:250,status:"A"},{cust_id:"B212",amount:200,status:"A"},{cust_id:"A123",amount:300,status:"B"}])
现要求查询所有status为A的文档,并按照cust_id分组计算出amount的和,下面用聚合查询实现:
代码语言:javascript复制db.orders.aggregate([{
$match: {
status: "A"
}
}, {
$group: {
_id: "$cust_id",
total: {
$sum: "$amount"
}
}
}])
查询过程:
聚合查询过程演示
聚合查询的常见阶段(步骤):
功能 | MQL | SQL |
---|---|---|
过滤 | $match | where |
投影(别名) | $project | as |
排序 | $sort | order by |
分组 | $group | group by |
结果多少 | limit | limit |
左外连接 | $lookup | left join |
展开数组 | $unwind | - |
图搜索 | $graphLookup | - |
分面搜索 | bucket | - |
limitlimit左外连接$lookupleft join展开数组$unwind-图搜索$graphLookup-分面搜索
bucket-
- 单一目的的聚合方法
统计集合文档总数:db.collection.count()
按文档某个字段去重:db.collection.distinct()
> db.orders.count()
4
> db.orders.distinct("cust_id")
[ "A123", "B212" ]
>
在Java中操作MongoDB
在了解了MongoDB的shell命令操作后,再实操下Java对应的API及U相当的简单了,关于普通的Java项目操作MongoDB、Spring操作MongoDB我整理了一个小练习项目:
Java连接
代码语言:javascript复制private static final String DATABASE_NAME = "test";
private static final String URL = "mongodb://192.168.242.99:27017/test";
/**
* 获取MongoDB连接
* @param collectionName
* @return
*/
public MongoCollection<Document> getMongoDB(String collectionName) {
ConnectionString conn = new ConnectionString(URL);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(conn)
.retryWrites(true)
.build();
MongoClient mongoClient = MongoClients.create(settings);
return mongoClient.getDatabase(DATABASE_NAME).getCollection(collectionName);
}
insert
代码语言:javascript复制@Test
public void testInsert() {
MongoCollection<Document> collection = getMongoDB("shop");
Document document = new Document();
document.append("name", "iphone");
document.append("price", 6799);
InsertOneResult insertOneResult = collection.insertOne(document);
System.out.println(JSON.toJSONString(insertOneResult, true));
}
更多的操作可以看项目: https://gitee.com/xblzer/mongodb
副本集(集群)
上面介绍了MongoDB单节点的使用,实际生产环境中使用的更多地是它的副本集,其实就是集群。
MongoDB的副本集是一组维护相同数据集的mongod进程。副本集提供了高可用性,是所有生产部署的基础。
副本集保证了在不同的数据库服务器上有多个数据副本,复制提供了一定程度的容错能力,防止单个数据库服务器的损失。
在某些情况下,副本集可以提供更高的读取能力,因为客户端可以向不同的服务器发送读取操作。
TIP:由于往副本集写数据通常是往primary节点上写,然后同步到各个secondary节点,有一定的网络开销,所以副本集对于写能力没有提升。
在不同的数据中心维护数据副本可以提高分布式应用的数据定位和可用性。
还可以专门用来做灾难恢复和备份。
也就是说,MongoDB副本集具有:
- 高可用
- 数据分发
- 读写分离-提升读的能力(千万别记错,写能力不仅没有提升,反而有些下降)
- 灾难恢复
副本集的成员
- 主节点(Primary)
主节点接收所有的写操作。
一个副本集只能有一个主节点,Primary将其数据集的所有变化记录在其操作日志中,即oplog(没错,就类似于MySQL的binlog)。
Oplog(operations log)是一个特殊的集合,记录所有的对于修改数据库(新增,修改,删除)的行为日志,这些日志,被称为Oplog。 MongoDB在主节点上数据库的操作,记录到oplog上,其他从节点通过异步的方式复制这些日志,所有从节点都包含主节点oplog的副本。 为了方便复制,所有副本集成员,都会向所有其他成员发送心跳(ping)。任何从节点,都可以从其他成员哪里导入oplog日志。 oplog操作是幂等的,也就是说,oplog作用在目标数据库上的行为,不管是一次还是多次,效果都一样。 oplog日志是有大小的,默认是物理磁盘的5%。 通过如下命令可以查看当前集群oplog的大小:
rs.printSlaveReplicationInfo()
通常情况下,oplog增长速度等同于主节点插入新文档速度,一旦超过阈值大小,旧的日志会被覆盖,所以一般情况下,oplog日志的大小要足够24小时新增的数量,一般都是保证72小时。 如果出现从节点无法同步主节点oplog情况,可以考虑手动同步数据。mongodb提供两种数据同步策略: 1-全量,新节点加入的方式 2-初始化后的所有复制同步,都是非全量的,保证每个oplog是一样的文件
- 从节点(Secondary)
从节点复制主节点的oplog,并将操作应用于它们的数据集。
如果主节点不可用,符合条件的从节点进行选举,产生新的主节点。
搭建副本集
副本集的搭建很简单,就是个体力活,开干。
1. 主机规划
IP地址 | 主机名称 | 角色 |
---|---|---|
192.168.242.103 | mongodb-103 | Primary |
192.168.242.104 | mongodb-104 | Secondary |
192.168.242.105 | mongodb-105 | Secondary |
2. 配置各节点主机名称
代码语言:javascript复制vi /etc/hosts
# 在文件末尾增加
192.168.242.103 mongodb-103
192.168.242.104 mongodb-104
192.168.242.105 mongodb-105
3. 各节点创建MongoDB配置文件mongod.conf
代码语言:javascript复制systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb/logs/mongo.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
dbPath: "/usr/local/mongodb/data"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
pidFilePath: "/usr/local/mongodb/logs/mongod.pid"
net:
#服务实例绑定的IP,0.0.0.0,任何IP都可以访问
bindIp: 0.0.0.0
#绑定的端口
port: 27017
replication:
replSetName: mongoReplSet
与单节点的配置文件不同的是就在末位加了一个replication的配置。
4. 启动各节点的MongoDB
代码语言:javascript复制mongd -f /usr/local/mongodb/conf/mongd.conf
5. 副本集配置
代码语言:javascript复制//在mongod-103机器上启动副本集
//首先使用mongo命令进入控制台
mongo
//开启副本集
rs.initiate()
//添加节点 rs.add("机器名:端口号")
rs.add("mongod-104:27017")
rs.add("mongod-105:27017")
这样一个MongoDB的副本集就搭起来了。
3MongoDB的使用场景
前面简单介绍了一下MongoDB的单节点、副本集的安装与使用,它相关的一些API我在GitHub上也总结了,有兴趣的朋友可以看看。
那么,MongoDB在哪些场景下适合使用呢?我当前的项目关于车辆GPS定位相关的使用的是MongoDB来做的,其实MongoDB在很多场景下都完全可以作为数据存储的技术选择,比如:
- 很多大型项目的商品文章内容评论
- 很多的物联网系统,共享电/单车
- 银行的金融数据中台
- 定位导航服务
最后,看个图:
MongoDB非常有市场!
首发公众号 行百里er ,欢迎老铁们关注阅读指正。代码仓库 GitHub https://gitee.com/xblzer/JavaJourney