1. 简介
1.1 定义
- MongoDB (名称来自「humongous (巨大无比的)」), 是一个可扩展的高性能,开源,模式自由,面向文档的NoSQL,由 C++ 语言编写,设计之初旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
- MongoDB属于内存型数据库, 使用的是内存映射存储引擎,它会把磁盘IO操作转换成内存操作,如果是读操作,内存中的数据起到缓存的作用,如果是写操作,内存还可以把随机的写操作转换成顺序的写操作,大幅度提升性能。
- MongoDB 既拥有Key-Value存储方式的高性能,也拥有传统的RDBMS系统的丰富的功能,集两者的优势于一身。 介于关系数据库和NoSQL之间,也是功能最丰富、最像关系数据库的的NoSQL。
MongoDB中文文档:https://www.mongodb.org.cn/
MongoDB中文社区:http://www.mongoing.com
1.2 应用场景
1. 适用场景
网站数据
: 适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。可以配置集群缓存
: 由于性能很高,也适合作为信息基础设施的缓存层。在系统重启之后,搭建的持久化缓存可以避免下层的数据源过载。一般使用redis数据类型更丰富大尺寸、低价值的数据
: 使用传统的关系数据库存储一些数据时可能会比较贵,在此之前,很多程序员往往会选择传统的文件进行存储。高伸缩性的场景
: 非常适合由数十或者数百台服务器组成的数据库。用于对象及JSON数据的存储
: MongoDB的BSON数据格式非常适合文档格式化的存储及查询。
2. 不适用的场景
高度事务性的系统
: 例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。传统的商业智能应用
: 针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。- `需要使用SQL语句解决的场景: MongoDB不支持SQL语句。
3. 商业应用
京东
: 使用MongoDB存储商品信息,支持比价和关注功能.百度云
: 使用MongoDB管理百度云盘中500亿条关于文件源信息的记录.CERN
: 著名的粒子物理研究所,欧洲核子研究中心大型强子对撞机的数据使用MongoDB存储。The New York Times
: 世界领先的在线新闻门户网站之一,使用MongoDB作为内容存储。sourceforge.net
: 资源网站查找,创建和发布开源软件免费,使用MongoDB做后端存储。
2. 使用
2.1 安装
mongodb具有两种安装方式:命令安装 或 源码安装
- 命令安装:适应于各种有界面的操作系统(有网)
- 源码安装:适合公司部署服务器-无界面(无网)
1. 命令安装
Ubuntu18.04
sudo apt-get install -y mongodb-org
官方文档 :https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
Mac
# 首先tap一个仓库
brew tap mongodb/brew
# 安装社区版
brew install mongodb-community
centos
yum -y install mongodb mongodb-devel mongodb-server
2. 源码安装(没有网)
1.选择相应版本和操作系统并下载
https://www.mongodb.com/download-center/community?jmp=docs
2.解压
tar -zxvf mongodb-linux-x86_64-ubuntu1804-4.0.3.tgz
3.移动到/usr/local/目录下
sudo mv -r mongodb-linux-x86_64-ubuntu1804-4.0.3/ /usr/local/mongodb
4.在shell的初始化脚本.bashrc中添加mongodb可执行文件到环境变量PATH中
a. 进入.bashrc文件中
cd ~
sudo vi .bashrc
b. 在.bashrc文件的最后添加:
export PATH=/usr/local/mongodb/bin:$PATH
2.2 配置
- 默认端口:
27017
- 默认配置文件的位置:
/etc/mongod.conf
- 默认日志的位置:
/var/log/mongodb/mongod.log
2.3 启动
1. 服务端启动
mongodb服务端启动分别两种方式:
- 本地测试方式的启动(只具有本地数据增删改查的功能)
- 生产环境启动(具有完整的全部功能)
1.1 本地测试方式
- 启动:
sudo service mongod start (sudo service mongod start)
- 停止:
sudo service mongod stop
- 重启:
sudo service mongod restart
1.2 生产环境方式
启动: sudo mongod [–auth --dbpath=dbpath --logpath=logpath --append --fork] [-–f logfile ]
- 只以
sudo mongod
命令启动时,默认将数据存放在了 /data/db 目录下,需要提前手动新建再设置chmod 77 /data/db
--dbpath
: 指定数据库的存放路径--logpath
: 指定日志的存放路径--append
: 或–logappend 设置日志的写入形式为追加模式--fork
: 或-fork 开启新的进程运行mongodb服务--f
: 或-f 配置文件路径(可以将上述配置信息写入文件然后通过该文件中的参数进行加载启动)--auth
: 以权限认证的方式启动
1.3 查看是否启动成功
ps aux | grep mongod
2. 客户端启动
- 启动本地客户端:
mongo
- 查看帮助:
mongo –help
- 退出:
exit
或者ctrl+c
3. 数据类型
3.1 常见类型
Object ID
: 文档ID/数据的ID,数据的主键String
: 字符串,最常用,必须是有效的UTF-8Boolean
: 存储一个布尔值,true
或false
Integer
: 整数可以是32位或64位,这取决于服务器
Double
: 浮点数Arrays
: 数组/列表Object
: mongodb中的一条数据/文档,即文档嵌套文档Null
: 存储null值Timestamp
: 时间戳,表示从1970-1-1到现在的总秒数Date
: 存储当前日期或时间的UNIX时间格式
3.2 注意点
- 每个文档都有一个属性,为
_id
,保证每个文档的唯一性,mongodb默认使用_id作为主键- 可以手动设置
_id
的值,如果没有提供,那么MongoDB为每个文档提供了一个独特的_id
, 类型为objectID
- 可以手动设置
- objectID是一个12字节的十六进制数,每个字节两位,一共是24位的字符串:
- 前4个字节为当前时间戳
- 接下来3个字节的机器ID
- 接下来的2个字节中MongoDB的服务进程id
- 最后3个字节是简单的增量值
4. 操作
开启mongodb server的情况下,在进入mongo shell后,就可以做简单的使用了
4.1 数据库
- 查看当前的数据库:
db
(没有切换数据库的情况下默认使用test数据库,没有集合不会显示
) - 查看所有的数据库:
show dbs /show databases
切换数据库:use db_name(新建并切换,没有集合不会显示
,有集合才会创建)db_name
为show dbs
后返回的数据库名
- 删除当前的数据库:
db.dropDatabase()
test不存在内容不会被删除
4.2 集合
- 无需手动创建集合: 向不存在的集合中第一次添加数据时,集合会自动被创建出来
- 手动创建集合:
db.createCollection(name,options)
- ·db.createCollection(“stu”)·
db.createCollection("sub", { capped : true, size : 10 } )
- 参数
capped
:默认值为false表示不设置上限,值为true表示设置上限,相当于redismaxmemory
- 参数
size
:集合所占用的字节数。 当capped值为true时,需要指定此参数,表示上限大小,当文档达到上限时, 会将之前的数据覆盖,单位为字节
- 参数
- 查看集合:
show collections
- 删除集合:
db.集合名称.drop(字典形式)
,返回true - 检查集合是否设定上限:
db.集合名.isCapped()
,默认false
简单练习:在mongo shell中输入下列命令,查看结果
show dbs
use test
show collections
db
db.stu.insert({'name':'郭靖', 'age':22})
show dbs
show collections
db.stu.find()
db.stu.drop()
show collections
db.dropDatabase()
show dbs
exit
4.4 文档/数据
前提,必须进入数据库
use 数据库名
1. 插入
插文档时,如果不指定_id
参数,MongoDB会为文档自动分配一个唯一的ObjectId
命令:db.集合名称.insert(document)
# 单数据插入
db.stu.insert({name:'curry', gender: man})
db.stu.insert(_id:"20200520", name:"kobe", number:"24")
# 多条数据插入
data = [
{"name": "james"},
{"number": 23},
{"team": "lakers"}
]
db.stu.insertMany(data)
2. 保存
如果文档的_id已经存在则修改,如果_id不存在则添加
命令:db.集合名称.save(document)
db.stu.save({_id:'20200520', name:'curry', gender:female})
db.stu.find()
3. 更新
db.集合名称.update({query}, {update}, {multi: boolean})
- 参数query:查询条件
- 参数update:更新操作符
- 参数multi:可选,默认是false,表示只更新找到的第一条数据,值为true表示把满足条件的数据全部更新
默认更新是整体更新,局部更新使用 $set,默认更新是一条数据,如果需要批量更新添加第三个参数设置更新方式使用 multi:true
# 覆盖式更新
db.stu.update(
# 更新条件
{
age:18
},
# 更新内容
{
name:"余欢水"
}
)
# 局部更新
db.stu.update(
# 更新条件
{
age:18
},
# 更新内容
{
$set:{
name:"林有有"
}
}
)
# 批量更新
db.stu.update(
# 更新条件
{
hometown:"大理"
},
# 更新内容
{
$set:{
name:"黑马程序员"
}
},
# 设置更新方式
{
multi:true
}
)
注意:"multi update only works with $ operators"
- multi参数必须和$set一起使用!
4. 删除
db.stu.remove(
# 删除条件
{
"hometown":"大理"
},
# 删除一条,不加全删
{
justOne:true
}
)
# 清空数据
db.stu.remove({})
注意:db.stu.remove({}) 删除所有里面括号不能少
5. 查询
5.1 简单查询
命令:db.集合名称.find()
db.stu.insert([{"name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true },
{"name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false },
{"name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false },
{"name" : "黄药师", "hometown" : "桃花岛", "age" : 40, "gender" : true },
{"name" : "段誉", "hometown" : "大理", "age" : 16, "gender" : true },
{"name" : "段王爷", "hometown" : "大理", "age" : 45, "gender" : true },
{"name" : "洪七公", "hometown" : "桃花岛", "age" : 50, "gender" : true }])
- 方法find()/find({}): 查询所有
db.集合名称.find({条件文档})
- 方法findOne():查询,只返回第一个
db.集合名称.findOne({条件文档})
- 方法pretty(): 将结果格式化;不能和findOne()一起使用!
db.集合名称.find({条件文档}).pretty()
5.2 高级查询
1. 比较运算符
-
等于: 默认是等于判断, 没有运算符
-
小于:
$lt (less than)
-
小于等于:
$lte (less than equal)
-
大于:
$gt (greater than)
-
大于等于:
$gte
-
不等于:
$ne
# 查询年龄大于18的所有英雄
db.stu.find({age:{$gt:18}})
2. 逻辑运算符
逻辑运算符主要指与、或逻辑
- and:在json中写多个条件即可
# 查询年龄大于或等于18, 并且性别为true的英雄
db.stu.find({age:{$gte:18},gender:true})
- 逻辑或: 使用$or, 值为数组, 数组中每个元素为json
# 查询年龄大于18, 或性别为false的英雄
db.stu.find({$or:[{age:{$gt:18}},{gender:false}]})
3. 范围运算符
- 使用
$in
,$nin
判断数据是否在某个数组内
# 查询年龄为18、 40的英雄
db.stu.find({age:{$in:[18, 40]}})
4. 支持正则表达式
- 使用
$regex
编写正则表达式
# 查询name以'黄'开头的英雄
db.stu.find({name:{$regex:'^黄'}})
5. skip和limit
- 方法limit(): 用于读取指定数量的文档
命令:db.集合名称.find().limit(NUMBER)
# 查询2条学生信息
db.stu.find().limit(2)
- 方法skip(): 用于跳过指定数量的文档
命令:db.集合名称.find().skip(NUMBER)
# 跳过前两条
db.stu.find().skip(2)
- 同时使用
db.stu.find().skip(2).limit(2)
4.5 其他
1. 投影
在查询到的返回结果中, 只选择必要的字段
命令:db.集合名称.find({},{字段名称:1,...})
- 参数为字段与值, 值为1表示显示, 值为0不显
注意:
- 对于_id列默认是显示的, 如果不显示需要明确设置为0
- 对于其他不显示的字段不能设置为0
# id不显示,只显示name和age字段
db.stu.find({},{_id:0,name:1,age:1})
2. 排序
方法sort()
, 用于对查询结果按照指定的字段进行排序
命令:db.集合名称.find().sort({字段:1,...})
参数1为升序排列 参数-1为降序排列
# 根据年龄降序
db.stu.find().sort({age:-1})
3. 统计个数
方法count()
用于统计结果集中文档条数
命令1:db.集合名称.find({条件}).count()
命令2:db.集合名称.count({条件})
# 查询年龄为18岁的数据一共有多少条
1.db.stu.find({age:18}).count()
2.db.stu.count({age:18})
4.6 聚合
1. 简介
1.1 定义
聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。
1.2 语法
db.集合名称.aggregate([
{管道名称1 : {表达式1}},
{管道名称2 : {表达式2}},
{管道名称3 : {表达式3}},
...
])
1.3 原理
1.4 管道
$group
: 按照某个字段进行分组,依次对于每个组进行统计计算。$match
: 匹配出符合条件的数据,和find
操作一致$project
: 和投影
作用一样,可以控制字段不显示$sort
: 将输出文档排序
后输出$limit
:限制
聚合管道返回的文档数$skip
:跳过
指定数量的文档, 并返回余下的文档
1.5 表达式
表达式:处理输出文档并输出 语法:表达式:'$列名'
常用表达式:
$sum
: 计算总和, $sum:1 表示以1累加计数相当于count统计数量$avg
: 计算平均值$min
: 获取最小值$max
: 获取最大值$push
:把分组后的内容放入到列表中,关键词$$ROOT
整条文档数据
2. 管道命令
2.1 $group
- 按照某个字段进行分组,依次对每个组进行统计计算。
_id
表示按照某个字段进行分组,但是内部的字段名称前面必须添加$字符,id
设置为null
表示把所有数据作为一组看待。
练习
# 统计家乡数量
db.stu.aggregate([
{
$group: {
_id: "$hometown", # 按照家乡分组
"家乡数量统计":{$sum: 1}, # 统计数量 “家乡数量统计”
}
}
])
# 设置分组字段: _id: "$hometown"
# 编写统计表达式: "家乡数量统计":{$sum: 1},
# $sum 统计总数表达式
# 统计平均年龄
db.stu.aggregate([{
$group: {
_id: null, # 不分组
"平均年龄": {$avg: "$age"} # 对年龄字段计算平均值
}
}])
# 家乡分组的平均年龄
db.stu.aggregate([{
$group: {
_id: "$hometown",
"平均年龄": {$avg: "$age"}
}
}])
# 家乡分组求最大年龄
# "最大年龄":{$max:"$age"},
db.stu.aggregate([{
$group: {
_id: "$hometown",
"最大年龄": {$max: "$age"}
}
}])
# 家乡分组求最小年龄
db.stu.aggregate([{
$group: {
_id: "$hometown",
"最小年龄": {$min: "$age"}
}
}])
# "最小年龄":{$min:"$age"}
# "姓名":{$push:"$name"},
其中注意点:
db.db_name.aggregate
是语法,所有的管道命令都需要写在其中_id
表示分组的依据,按照哪个字段进行分组,需要使用$gender
表示选择这个字段进行分组$sum:1
表示把每条数据作为1进行统计,统计的是该分组下面数据的条数
数据透视
正常情况在统计的不同性别的数据的时候,需要知道所有的name,需要逐条观察,如果通过某种方式把所有的name放到一起,就可以理解为数据透视
$push
: 将分组后的数据装入列表
# 以hometown分组,统计每一组中的名字以列表的形式展示
db.stu.aggregate([{
$group: {
_id: "$hometown",
"name_list":{$push: "$name"}
}
}])
返回结果:
{ "_id" : "大理", "name_list" : [ "段誉", "段王爷" ] } { "_id" : "桃花岛", "name_list" : [ "黄蓉", "黄药师", "洪七公" ] } { "_id" : "蒙古", "name_list" : [ "郭靖", "华筝" ] }
$$ROOT
:可以将整条文档放入列表中
db.stu.aggregate([{
$group: {
_id: "$hometown",
"name_list":{$push: "$$ROOT"}
}
}])
2.2 $match
$match
用于进行数据过滤,是在能够在聚合操作中使用的命令- 和
find
区别在于$match
操作可以把结果交给下一个管道处理,而find
不行
使用示例如下:
- 查询年龄大于20的数据
db.stu.aggregate({
$match: {
age:{$gt:20}
}
})
- 查询年龄大于20, 以hometown分组, 统计数量
db.stu.aggregate([
{
$match:{
age:{$gt:20}
}
},
{
$group: {
_id: "$hometown",
"age_count": {$sum: 1}
}
}
])
2.3 $project
$project
类似于投影操作用于修改文档的输出结构,控制字段的显示
使用示例如下:
- 查询英雄的年龄和姓名,仅输出年龄和姓名
db.stu.aggregate(
{$project:{_id:0,name:1,age:1}}
)
- 通过性别分组,统计人数,但最后只展示统计人数
db.stu.aggregate([
{
$group:{
_id: "$gender",
gender_count: {$sum:1}
}
},
{
$project:{_id:0, gender_count:1}
}
])
2.4 $sort
$sort
用于将输入的文档排序后输出
使用示例如下
- 查询男性英雄信息,按照年龄降序排序
db.stu.aggregate([
{$match: {gender:true}},
{$sort: {age:-1}}
])
2.5 $skip和 $limit
-
$limit
限制返回数据的条数 -
$skip
跳过指定的文档数,并返回剩下的文档数
使用示例如下: -
查询前两条英雄信息
db.stu.aggregate({
$limit:2
})
查询从第三条开始的英雄信息/跳过前两条
db.stu.aggregate({
$skip:2
})
- 统计男女生人数,按照人数降序,返回
第二条
数据
db.stu.aggregate([
{$group:{
_id: "$gender",
gender_count:{$sum:1}
}},
{$sort: {gender_count: -1}},
{$skip: 1},
{$limit:1}
])
4.7 索引
1. 作用
- 加快查询速度
- 进行数据去重
创建索引前后查询速度对比
测试:插入10万条数据到数据库中
插入数据:
for(i=0;i<100000;i++){db.t1.insert({name:'test'+i,age:i})}
创建索引前:
db.t1.find({name:'test10000'})
db.t1.find({name:'test10000'}).explain('executionStats') # 显示查询操作的详细信息
创建索引:
db.t1.ensureIndex({name:1})
创建索引后:
db.t1.find({name:‘test10000’}).explain(‘executionStats’)
前后速度对比
2. 操作
2.1 创建简单的索引
- 语法:
db.集合名.ensureIndex({属性:1})
,1表示升序, -1表示降序
2.2 查看索引
默认情况下_id是集合的索引 查看方式:db.集合名.getIndexes()
2.3 删除索引
语法:db.集合名.dropIndex({'索引名称':1})
db.t1.dropIndex({name:1})
db.t1.getIndexes()
2.4 创建唯一索引
在默认情况下mongdb的索引域的值是可以相同的,创建唯一索引之后,数据库会在插入数据的时候检查创建索引域的值是否存在,如果存在则不会插入该条数据,创建索引仅仅能够提高查询速度,但是会降低数据库的插入速度。
- 添加唯一索引的语法:
db.集合名.ensureIndex({"字段名":1}, {"unique":true})
- 利用唯一索引进行数据去重
根据唯一索引指定的字段的值,如果相同,则无法插入数据
db.t1.ensureIndex({"name":1}, {"unique":true})
db.t1.insert({name: 'test10000'})
2.5 建立复合索引
在进行数据去重的时候,可能用一个域来保证数据的唯一性,这个时候可以考虑建立复合索引来实现。
例如:抓全贴吧信息,如果把帖子的名字作为唯一索引对数据进行去重是不可取的,因为可能有很多帖子名字相同
建立复合索引的语法:
db.collection_name.ensureIndex({字段1:1,字段2:1})
3. 注意点
- 根据需要选择是否需要建立唯一索引
- 索引字段是升序还是降序在单个索引的情况下不影响查询效率,但是带复合索引的条件下会有影响
- 数据量巨大并且数据库的读出操作非常频繁的时候才需要创建索引,如果写入操作非常频繁,创建索引会影响写入速度
例如:在进行查询的时候如果字段1需要升序的方式排序输出,字段2需要降序的方式排序输出,那么此时复合索引的建立需要把字段1设置为1,字段2设置为-1
5. Python交互
5.1 pymongo模块
pymongo
提供了mongdb和python交互的所有方法 安装命令:
pip install pymongo
5.2使用
注意: 任何数据库变更操作,数据库和集合能够自动创建
无需权限认证的方式
# 导入模块
from pymongo import MongoClient
# 如果是本地连接host,port参数可以省略
# client = MongoClient()
# 1.连接ubantu的mongodb客户端
client = MongoClient(host="192.168.243.130")
# 支持[]和.语法
# collection = client[数据库名][集合名]
# 2.获取集合对象
collection = client.数据库名.集合名
1. 添加数据
- 添加一条数据: insert_one()
- 添加多条数据: insert_many()
也可以使用 insert() 将要过期
添加一条数据
collection.insert_one({"name": "curry30"})
添加多条数据
collect.insert_many([{"name": "harden"},
{"name": "james"},
{"name": "kobe"}])
2. 查询数据
- 查找一条数据:
find_one()
- 查找全部数据:
find()
查找一条数据
ret = collect.find_one()
print(ret)
查找全部数据
ret = collect.find()
# find函数默认返回的结果是Cursor对象,是一个可迭代对象
data_list = list(ret)
print(data_list)
注意:
返回所有满足条件的结果
如果条件为空,则返回全部 结果是一个Cursor游标对象,是一个可迭代对象,但是只能够进行一次读取,我们可以强制类型转换成列表
3. 更新数据
语法
collection.update(
{条件},
{'$set':{更新的数据键值对}},
multi=False/True,
upsert=False/True)
multi
参数:默认为False,表示更新一条; multi=True则更新多条; multi参数必须和$set一起使用upsert
参数:默认为False; upsert=True则先查询是否存在,存在则更新;不存在就插入$set
表示指定字段进行更新
覆盖式更新
collect.update({"name": "郭靖"}, {"name": "黑马程序员"})
批量更新
# 旧语法
collect.update({"name": "kobe"}, {"$set": {"name": "kobe24"}}, multi=True)
# 新语法,没有multi=True参数
collect.update_many({"name": "kobe24"}, {"$set": {"name": "kobe_24"}})
4. 删除数据
delete_one()删除一条数据
collect.delete_one({"name": "harden"})
delete_many()删除全部数据
collect.delete_many({"name": "james"})
5.3 权限管理
pymongo模块其他api:https://pymongo.readthedocs.io/en/stable/
需要权限认证的方式
from pymongo import MongoClient
from urllib.parse import quote_plus
user = 'python' # 账号
password = 'python' # 密码
host = '127.0.0.1' # ip地址
port = 27017 # 端口
# quote_plus函数:对url进行编码
# uri = mongodb://python:python@127.0.0.1
uri = "mongodb://%s:%s@%s" % (quote_plus(user),
quote_plus(password),
host)
# 链接客户端
client = MongoClient(uri, port=port)
# 获取集合对象
collection = client.数据库名称.集合名