自学数据库- MongoDB
NoSQL
NoSQL 指的是非关系数据库。NoSQL 使用的最多的就是 key-value 存储,当然还有 xml 等类型。NoSQL 对于传统关系数据库的优点有:
- 能够提供对数据库高并发的读写
- 能够满足对海量数据的高效率存储和访问的需求
- 有较高的可扩展性和可用性
MongoDB 介绍
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 json 的 bjson 格式,因此可以存储比较复杂的数据类型。MongoDB 最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。它是一个面向集合的,模式自由的文档型数据库。
- 面向集合 (Collenction-Orented)
意思是数据被分组存储在数据集中,被成为一个集合 (Collenction)。每个集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库里的表 (table),不同的是它不需要定义任何模式 (schema) - 模式自由 (schema-free)
意味着对于存储在 MongoDB 数据库中的文件,我们不需要知道它的任何结构定义。例如,下面两个记录可以存在于同一个集合里面:{'welcome" : “Beijing”} {“age” : 25 } - 文档型
意思是我们存储的数据是键-值对的集合。键是字符串,值可以是数据类型集合里的任意类型,包括数组和文档。这个数据格式称作 “BSON” (Binary Serialized document Notation)
MongoDB 的下载和安装
据说 7.0 和之前版本不兼容了,也有很多变化,本文是基于5.0的版本,应该和6.0兼容。
基于 linux
mongodb 无法使用包管理器安装,所以需要从官网找到连接然后使用 wget
下载安装。需要注意的是,官网默认提供的是 .deb
文件,是 Debian 的安装包,所以最好选择下载 tgz
压缩包。
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.6.tgz
下载完成后,解压缩压缩包
tar -zxvf mongodb-linux-x86_64-ubuntu2204-7.0.6.tgz
然后将解压出来的目录移动到指定目录中,也可以重命名
mv mongodb-linux-x86_64-ubuntu2204-7.0.6.tgz/ ~/mongodb
进入该目录,创建存储数据库和log文件的目录,并设置权限
cd ~/mongodb
mkdir -p data/db
mkdir -p data/log
chown `id -u`:`id -g` data/db
chown `id -u`:`id -g` data/log
配置启动文件
创建配置文件
mkdir conf
vim conf/mongod.conf
在配置文件中输入
storage:
dbPath:~/mongodb/db
systemLog:
destination: file
logAppend: true
path: ~/mongodb/log/mongod.log
net:
bindIp: 0.0.0.0
port: 27017
security:
authorization: disabled
使用此配置文件运行 mongo 服务,则可以远程不经过验证访问了。
配置访问权限
使用客户端登录数据库,然后可以创建管理员用户,然后可以创建普通用户并赋予管理员权限
use admin
db.createUser({
user: "root",
pwd: "root",
roles: [{ role: "root", db: "admin" }]
})
然后可以将配置文件中 authorization
项的 disabled
改为 enabled
,再重启服务,就可以使用访问权限登录了
设置为系统服务,自动执行
首先需要自定义服务文件 /etc/systemd/system/mongodb.service
[Unit]
Description=MongoDB Database Service
After=network.target
[Service]
User=root
Group=root
ExecStart=~/mongodb/bin/mongod --config ~/mongodb/conf/mongod.conf
ExecStop=/bin/kill -s SIGTERM $MAINPID
Restart=always
[Install]
WantedBy=multi-user.target
然后重新加载 systemd 的配置,并启动mongodb服务
systemctl daemon-reload
systemctl enable mongodb.service
systemctl start mongodb.service
基于 windows
MongoDB 是绿色的,官网提供的安装包其实是将一些配置信息在安装阶段运行了。如果使用压缩文件,解压后需要进行配置。
设置文件
可以创建一个文本文件,名称任意取。文件中的参数以 " 键 = 值 " 的格式进行存储,例如
#数据库数据存放目录
dbpath="D:\MongoDB\db"
#数据库日志存放目录
logpath="D:\MongoDB\logs\mongodb.log"
主运行文件和简单的运行参数
文件夹 bin 下的 mongod.exe 为主运行文件,在命令行里输入 mongod.exe -h
或 mongod.exe --help
则能看到参数及说明。
运行 MongoDB 时,必须添加的参数就是数据库路径了。通常还会添加一个日志文件路径:
mongod.exe --dbpath ./db --logpath ./logs/mogondb.log
执行完成后可以开个浏览器进行测试:在浏览器中输入http://localhost:27017/可看到返回信息:
It looks like you are trying to access MongoDB over HTTP on the native driver port.
如果看到这个信息说明程序正常运行。如果需要其他配置可以参考参数说明。
以本地服务方式运行
在命令行界面输入以下命令即可安装为服务,记得添加包括数据库路径在内的其他参数。
mongod.exe --serviceName MongoDB --install
安装完成后,可以在服务中看到,也可以手动运行服务 net start MongoDB
或手动停止服务 net stop MongoDB
如果不需要服务了,则可以进行卸载
mongod.exe --remove
MongoDB 的使用
MongoDB 与 Sql 的概念对比
SQL | sql说明 | MongoDB | mongodb说明 |
---|---|---|---|
database | 数据库 | database | 数据库 |
table | 数据表 | collection | 数据集合 |
row | 行 | document or BSON document | 文档 |
colunm | 列 | field | 字段 |
index | 索引 | index | 索引 |
primary key | 主键 | _id(auto set) | id(自动设置) |
MongoDB 数据类型
类型 | 名称 |
---|---|
Object ID | 对象ID |
String | 字符串,最常用,必须是有效的UTF-8 |
Boolean | 布尔 |
Integer | 整数 |
Double | 浮点 |
Arrays | 数组或列表,多个值存储到一个键 |
Object | 用于嵌入式的文档对象,即一个值为一个对象 |
Null | 存储Null值 |
Timestamp | 时间戳 |
Date | 当前日期或时间的UNIX时间格式 |
第一次连接到 MongoDB
在连接之前先要运行 MongoDB,无论是本地服务运行还是命令行运行都行。第一次运行不要加 --auth 即不要进行登录验证。
运行完 MongoDB 就可以连接到 MongoDB 了。使用 bin 目录下的 mongo.exe (据说新版本的 MongoDB 客户端改成了 mongosh.exe)进行连接,如果没有过多的设置直接运行就行了,如果有设置 IP 和端口号,则需要以参数形式传入,例如 mongo 127.0.0.1:27017
。
设置超级管理员账号
第一次连接上数据库,需要做的就是建立超级管理员账号。
use admin // 使用 admin 数据库
db.createUser({
user: 'admin', // 用户名(自定义)
pwd: 'Abc123++', // 密码(自定义)
roles:[{
role: 'root', // 使用超级用户角色
db: 'admin' // 指定数据库
}]
})
设置完成,可以通过指令 show users
查看是否设置成功。然后关闭 MongoDB 服务程序,以需要验证模式重新部署服务即可使用权限设置了。
如果忘记了超级管理员账号密码,可以以不需要认证的形式部署服务。这时连接到数据库不需要账号密码,使用客户端连接到数据库。
use admin // 使用 admin 数据库
db.system.users.find() // 查看当前用户
db.system.users.remove({}) // 删除现有的用户
db.createUser({user:"admin",pwd:"Admin@123",roles:["root"]}) // 创建超级管理员账号
然后再开启验证重新部署服务就可以了。
shell 下使用账号登录
使用客户端连接到数据库后
use admin
db.auth('admin','Admin@123')
MongoDB 的操作
数据库实例的操作
- 查看数据库
列出所有物理上存在的数据库
show dbs
- 切换/创建数据库
如果数据库不存在,则指向数据库,但不创建。直到插入数据或创建集合时数据库才被创建
use 数据库名
- 删除数据库
删除当前指向的数据库,如果数据库不存在,则什么也不做
use 数据库名
db.dropDatabase()
集合的操作
集合相当于 sql 里的表
- 创建集合 (实际操作中,可以绕过此步骤,直接在集合中存储数据,这样会自动创建相应集合)
db.createCollection(集合名)
db.createCollection(集合名,{capped:true,size:num})
参数 capped 默认是 false,表示不设置上限,如果使用 true,则要在 size 中输入该集合的最大数据数量限制
- 查看集合
show tables
show collections
- 删除集合
db.集合名称.drop()
数据的操作
首先需要明确的是 MongoDB 中数据是以 文档(document) 形式存在,相当于 sql 里的 row。通常 document 是以字典形式存在。在 MongDB 中,唯一的主键是 _id
,它是数据创建时自动生成的(除非主动创建)。另外 MongoDB 是 没有表结构 一说的,上一条数据和下一条数据可以完全没有相同的字段,两个数据可以没有任何关联(除了都有_id
)。
- 新增数据: 新增数据可以使用 insert 和 save 两种方式,数据以字典或字典列表形式存在。
insert 和 save 都可以新增数据,区别在于 insert 不会遍历检测数据,而是直接插入,如果有重复数据则报错。save 会遍历检测数据,有重复数据则更新。
db.集合名称.insert(document)
db.集合名称.save(document)
增加多条数据使用字典列表:
db.集合名称.insert([{k1:v1},{k2,v2}]
insert
方法将在未来弃用,改为使用 insertOne()
方法,使用方式是一样的。相对于 insertOne()
,还有 insertMany()
方法。相同 save()
方法在将来也会被弃用。
- 修改数据:修改数据使用 update 命令,其有3个参数:query 是查询条件,类似 where;update 是更新的数据,类似 set;multi 可选,默认false ,是否更新全部满足条件的文档(false则只更新第一条)
需注意的是 MongoDB 是全文档更新的,如果参数 update 部分没有的字段,即使原数据有也会丢失。所以通常会将 update 部分增加 $set ,表示只修改更新部分,不修改文档结构。
db.集合名称.update({query},{update},{multi:false})
# 只更新找到的第一条数据,且会修改结构,结果
db.person.update({name:"zs"},{age:16})
# 只更新第一条数据,不修改文档结构
db.person.update({name:"zs"},{$set:{age:123}})
# 更新所有所有符合查询条件的数据
db.person.update({name:"zs"},{$set:{age:40}},{multi:true})
update()
方法在未来将被弃用,改为使用 updateOne()
方法
- 删除数据:删除数据使用 remove 命令,其参数为查询条件,如果没有参数则是不进行查询删除全部。
db.集合名称.remove(查询条件)
# 删除所有匹配数据
db.person.remove({name:"zs"})
# 只删除第一条匹配数据
db.person.remove({name:"zs"},{justOne:true})
# 不计条件删除全部
db.person.remove({})
remove()
方法在未来将被弃用,可以使用 deleteOne()
,deleteMany()
,findOneAndDelete()
方法替代
- 查找数据:查找数据使用 find 或 findOne 命令,区别在于查询全部还是查询第一条符合条件的数据。参数为查询条件,如果没有参数则是查询全部数据。
db.集合名称.find(查询条件)
# 查询所有 name 为 zs 的数据
db.person.find({name:"zs"})
# 查询所有数据
db.person.find()
另外在查询时使用 pretty()
方法,可以格式化输出,方便查看
db.集合名称.find().pretty()
查询
简单查询
Mongo 支持的运算符有:
语法 | 操作 | 格式 |
---|---|---|
$eq | 等于 | {key:value} |
$lt | 小于 | {key:{$lt:value}} |
$lte | 小于等于 | {key:{$lte:value}} |
$gt | 大于 | {key:{$gt:value}} |
$gte | 大于等于 | {key:{$gte:value}} |
$ne | 不等于 | {key:{$ne:value}} |
$or | 或 | {$or:[{条件1},{条件2}]} |
$and | 与 | {$and:[{条件1},{条件2}]} 或 {{条件1},{条件2}} |
$not | 非 | {$not:{条件}} |
$nor | 或非(非取反) | {$nor:[{条件1},{条件2}]} |
$exists | 键是否存在 | {key:{$exists:true|false}} |
$in | 在列表中 | {key:{$in:[value1,value2]}} |
$nin | 不在列表中 | {key:{$nin:[value1,value2]}} |
$mod | 数字模操作 | {key:{$mod:[num1,num2]}} |
# 查询所有 age 的值 大于16 的数据
db.person.find({age:{$gt:16}})
# 查询所有 age 大于等于18 或 name 等于 'zs' 的数据
db.person.find({$or:[{age:{$gte:18}},{name:"zs"}]})
另外还可以根据某一个字段(key)的值的数据类型进行查询,使用 $type,格式为 {key:{$type:数据类型值}}。数据类型和其值可以参考下表
类型 | 数字 | 备注 |
---|---|---|
Double | 1 | |
String | 2 | |
Object | 3 | |
Array | 4 | |
Binary data | 5 | |
Undefined | 6 | 已废弃 |
Object id | 7 | |
Boolean | 8 | |
Date | 9 | |
Null | 10 | |
Regular Expression | 11 | |
JavaScript | 13 | |
Symbol | 14 | |
JavaScript (with scope) | 15 | |
32-bit integer | 16 | |
Timestamp | 17 | |
64-bit integer | 18 | |
Min key | 255 | Query with -1. |
Max key | 127 |
# 这两种方式等价
db.person.find({name:{$type:2}})
db.person.find({mame:{$type:'string'}})
模糊查询
mongo 没有 like ,而是使用正则表达式。使用 // 或 $regex 可以编写正则表达式
db.person.find({name:/^zs/})
db.person.find({name:{$regex:'^zs'}})
自定义查询
可以使用 $where 后面跟一个函数(JS的语法),则可以返回满足条件的数据
db.person.find({$where:function(){return this.age>20}})
查询结果分页
可以使用 limit 将查询结果分页处理,用于读取指定数量的文档
db.集合名称.find(查询条件).limit(NUMBER)
跳过
使用 skip 跳过指定数量的文档,即前指定数量的文档在查询结果中不返回
db.集合名称.find(查询条件).skip(NUMBER)
排序
使用 sort 可以对查询结果集按照字段进行排序,参数1为升序 0 或 -1 为降序
db.集合名称.find().sort({key1:1|0,key2:1|0…})
统计
使用 count 可以对查询结果集中的文档数据量进行统计
db.集合名称.find(查询条件).count()
投影
使用 find()
方法查询到的数据是全部的字段,如果只需要其中部分字段,可以添加参数
db.集合名称.find(查询条件, 投影条件)
例如db.test.find({age:{$gt:18}}, {name: 1})
就是只显示 name 字段,1 表示显示,0为不显示,_id 默认是显示的
去重
使用 distinct()
方法可以去重查询,代替了 find()
方法。需注意的是,去重是根据筛选字段进行去重的
db.集合名称.distinct(字段名, 查询条件)
多级查询
当某一条数据某一的字段的值是字典时,可以进行使用点进行多级查询例如
# 文档为: {'name': {'first': 'Joe', 'last': 'Schome'}, 'age': 45}
# 查询姓名为 Joe Schmoe 的记录,以下2个方法是等价的
db.person.find({'name.first': 'Joe', 'name.last': 'Schome'})
db.person.find({'name':{'first': 'Joe', 'last': 'Schome'}})
聚合操作
使用 aggregate()
方法可以进行聚合操作
db.集合名称.aggregate({聚合管道:{表达式}})
常用的聚合管道有:
$group:
将集合中的⽂档分组, 可⽤于统计结果
$match:
过滤数据, 只输出符合条件的⽂档
$project:
修改输⼊⽂档的结构, 如重命名、增加、删除字段、创建计算结果
$sort:
将输⼊⽂档排序后输出
$limit:
限制聚合管道返回的⽂档数
$skip:
跳过指定数量的⽂档, 并返回余下的⽂档
$unwind:
将数组类型的字段进⾏拆分
常用表达式有:
$sum:
计算总和, $sum:1 表示以⼀倍计数
$avg:
计算平均值
$min:
获取最⼩值
$max:
获取最⼤值
$push:
在结果⽂档中插⼊值到⼀个数组中
$first:
根据资源⽂档的排序获取第⼀个⽂档数据
$last:
根据资源⽂档的排序获取最后⼀个⽂档数据
$group
$group
用于分组,_id
是分组依据,和数据里的_id
是不一样的,使用某个字段的格式为 “$字段”。需要注意的是分组中返回的字段均是分组管道中定义的,除了 _id
是分组条件,其他的可以自定义,可以是中文。
db.stu_info.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}}}
)
按照字段 gender 分组,并且统计数据
db.stu_info.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}, avg_age: {$avg: "$age"}}}
)
除了统计数据外,还根据 ‘age’ 字段取平均值
db.stu_info.aggregate(
{$group: {_id: null, counter: {$sum: 1}, avg_age: {$avg: "$age"}}}
)
_id: null
表示没有分组依据字段,即所有文档分为一组
$project
$project
用于修改文档的结构,例如:重命名、增加字段、删除字段、创建计算结果等
db.stu_info.aggregate(
{$project: {_id: 0, name: 1, age: 1}}
)
类似于投影,不显示 _id
,显示 name
,显示 age
。
多个聚合可以一起使用
db.stu_info.aggregate(
{$group: {_id: "$gender", count: {$sum: 1}, avg_age: {$avg: "$age"}}},
{$project: {"性别": "$_id", "人数统计": "$count", "平均年龄": "$avg_age", _id: 0}}
)
按 gender
分组查询后,将各字段名称按需求更改一下
$match
$match
用于过滤数据,只输出符合条件的文档,另外 match 是管道命令,能将结果交给下一个管道,find()
无法实现
db.stu_info.aggregate(
{$match: {age: {$gt: 20}}}
)
查询 age 字段大于 20 的文档,类似于 find()
方法的结果
db.stu_info.aggregate(
{$match: {$or:[{age: {$gt: 20}},{hometown: {$in: ["蒙古", "大理"]}}]}},
{$group: {_id: "$gender", counter: {$sum: 1}}},
{$project: {"性别": "$_id", "统计人数": "$counter", _id: 0}}
)
过滤后分组再重命名字段
$sort
$sort
可以将输入文档排序后输出
db.stu_info.aggregate(
{$sort: {age: 1}}
)
按 age 字段升序
db.stu_info.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}}},
{$sort: {counter: -1}}
)
按照 counter 降序排列
$limit 与 $skip
$limit
可以限制聚合返回的文档数,$skip
可以跳过指定数量的文档,两者结合起来就是分页查询
db.stu_info.aggregate({$skip: 5}, {$limit: 5})
返回从第6条开始的5条数据,即每页5条,取第2页