- mongodb的介绍和安装
- mongodb的简单使用
- mongodb的增删改查
- mongodb的聚合操作
- mongodb的索引操作
- mongodb的权限管理
- mongodb和python交互(pymongo模块)
mongodb文档:https://docs.mongodb.com/
mongodb简介
1、什么是mongodb
- mongodb 是一个功能最丰富的NoSQL非关系数据库。由 C++ 语言编写。
- mongodb 本身提供S端存储数据,即server;也提供C端操作处理(如查询等)数据,即client。
2、SQL和NoSQL的主要区别
- 在SQL中层级关系: 数据库>表>数据
- 而在NoSQL中则是: 数据库>集合>文档
3、数据之间无关联性
- SQL中如何需要增加外部关联数据的话,规范化做法是在原表中增加一个外键,关联外部数据表。
- NoSQL则可以把外部数据直接放到原数据集中,以提高查询效率。缺点也比较明显,对关联数据做更新时会比较麻烦。
- SQL中在一个表中的每条数据的字段是固定的。而NoSQL中的一个集合(表)中的每条文档(数据)的key(字段)可以是互不相同的。
数据库 —> SQL 和 NoSQL 的区别 - 蚂蚁吃大象、 - 博客园 (cnblogs.com)
4、mongodb作为非关系型数据库相较于关系型数据库的优势
易扩展: NoSQL数据库种类繁多, 但是一个共同的特点都是去掉关系数据库的关系型特性。 数据之间无关系, 这样就非常容易扩展
大数据量,高性能: NoSQL数据库都具有非常高的读写性能, 尤其在大数据量下表现优秀。 这得益于它的非关系性,数据库的结构简单
灵活的数据模型: NoSQL无需事先为要存储的数据建立字段, 随时可以存储自定义的数据格式。 而在关系数据库中, 增删字段是一件非常麻烦的事情。 如果是非常大数据量的表, 增加字段简直就是一个噩梦
安装和启动
官方安装教程:https://docs.mongodb.com/manual/introduction/
mongodb具有两种安装方式:命令安装 或 源码安装
命令安装
在ubuntu中使用apt-get工具安装
1 | sudo apt-get install -y mongodb-org |
或参考官方文档 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
源码安装
选择相应版本和操作系统并下载
1、解压
1 | tar -zxvf mongodb-linux-x86_64-ubuntu1804-4.0.3.tgz |
2、移动到/usr/local/目录下
1 | sudo mv -r mongodb-linux-x86_64-ubuntu1804-4.0.3/ /usr/local/mongodb |
3、在shell的初始化脚本.bashrc中添加mongodb可执行文件到环境变量PATH中
1 | sudo vi ~/.bashrc # a. 进入.bashrc文件中 |
服务端启动
- 默认端口:27017
- 默认配置文件的位置:
/etc/mongod.conf
- 默认日志的位置:
/var/log/mongodb/mongod.log
mongodb服务端启动分别两种方式:
- 本地测试方式的启动(只具有本地数据增删改查的功能)
- 生产环境启动(具有完整的全部功能)
测试方式启动
- 启动: sudo service mongod start (sudo service mongod start)
- 停止: sudo service mongod stop
- 重启: sudo service mongod restart
生产环境正式的启动方式
启动: sudo mongod [—auth —dbpath=dbpath —logpath=logpath —append —fork] [-–f logfile ]
- 只以 sudo mongod 命令启动时,默认将数据存放在了 /data/db 目录下,需要手动创建
- —dbpath: 指定数据库的存放路径
- —logpath: 指定日志的存放路径
- —append: 或—logappend 设置日志的写入形式为追加模式
- —fork: 或-fork 开启新的进程运行mongodb服务
- —f: 或-f 配置文件路径(可以将上述配置信息写入文件然后通过该文件中的参数进行加载启动)
- —auth: 以权限认证的方式启动,我们会在后边的课程中学习该内容
客户端启动
进入mongo shell
- 启动本地客户端: mongo
- 查看帮助:mongo –help
- 退出:exit或者ctrl+c
简单使用
开启mongodb server的情况下,在进入mongo shell后,就可以做简单的使用了
数据库的命令
- 查看当前的数据库:db(没有切换数据库的情况下默认使用test数据库)
- 查看所有的数据库:
show dbs /show databases
- 切换数据库:
use db_name
db_nam
e为show dbs
后返回的数据库名
- 删除当前的数据库:
db.dropDatabase()
集合的命令
- 无需手动创建集合:向不存在的集合中第一次添加数据时,集合会自动被创建出来
- 手动创建集合:
- db.createCollection(name,options)
- db.createCollection(“stu”)
- db.createCollection(“sub”, { capped : true, size : 10 } )
- 参数capped:默认值为false表示不设置上限,值为true表示设置上限
- 参数size:集合所占用的字节数。 当capped值为true时,需要指定此参数,表示上限大小,当文档达到上限时, 会将之前的数据覆盖,单位为字节
- 查看集合:show collections
- 删除集合:db.集合名称.drop()
- 检查集合是否设定上限: db.集合名.isCapped()
简单练习
1 | show dbs |
常见数据类型
常见类型
- Object ID: 文档ID/数据的ID,数据的主键
- String: 字符串,最常用,必须是有效的UTF-8
- Boolean: 存储一个布尔值,true或false
- Integer: 整数可以是32位或64位,这取决于服务器
- Double: 浮点数
- Arrays: 数组/列表
- Object: mongodb中的一条数据/文档,即文档嵌套文档
- Null: 存储null值
- Timestamp: 时间戳,表示从1970-1-1到现在的总秒数
- Date: 存储当前日期或时间的UNIX时间格式
注意点
- 每个文档都有一个属性,为
_id
,保证每个文档的唯一性,mongodb默认使用_id
作为主键- 可以手动设置
_id
的值,如果没有提供,那么MongoDB为每个文档提供了一个独特的_id
, 类型为objectID
- 可以手动设置
- objectID是一个12字节的十六进制数,每个字节两位,一共是24位的字符串:
- 前4个字节为当前时间戳
- 接下来3个字节的机器ID
- 接下来的2个字节中MongoDB的服务进程id
- 最后3个字节是简单的增量值
增删改查
插入数据
命令:db.集合名称.insert(document)
1 | db.stu.insert({name:'gj', gender:1}) # 插入一条数据 |
插文档时,如果不指定_id参数,MongoDB会为文档自动分配一个唯一的ObjectId
保存
命令:db.集合名称.save(document)
1 | db.stu.save({_id:'20170101', name:'gj', gender:2}) |
如果文档的_id
已经存在则修改,如果_id
不存在则添加
查询
命令:db.集合名称.find()
可以使用以下数据进行练习
1 | db.stu.insert([{"name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true }, |
简单查询
方法find(): 查询
db.集合名称.find({条件文档})
方法findOne():查询,只返回第一个
db.集合名称.findOne({条件文档})
方法pretty(): 将结果格式化;不能和findOne()一起使用!
db.集合名称.find({条件文档}).pretty()
比较运算符
- 等于: 默认是等于判断, 没有运算符
- 小于:
$lt (less than)
- 小于等于:
$lte (less than equal)
- 大于:
$gt (greater than)
- 大于等于:
$gte
- 不等于:
$ne
1 | 查询年龄大于18的所有学生 |
逻辑运算符
逻辑运算符主要指与、或逻辑
- and:在json中写多个条件即可
1 | 查询年龄大于或等于18, 并且性别为true的学生 |
- or:使用$or, 值为数组, 数组中每个元素为json
1 | 查询年龄大于18, 或性别为false的学生 |
范围运算符
使用$in
, $nin
判断数据是否在某个数组内
1 | 查询年龄为18、 28的学生 |
支持正则表达式
使用$regex编写正则表达式
1 | 查询name以'黄'开头的数据 |
自定义查询
mongo shell 是一个js的执行环境 使用$where 写一个函数, 返回满足条件的数据
1 | 查询年龄大于30的学生 |
skip和limit
方法limit(): 用于读取指定数量的文档,
db.集合名称.find().limit(NUMBER)
1
db.stu.find().limit(2) # 查询2条学生信息
方法skip(): 用于跳过指定数量的⽂档,
db.集合名称.find().skip(NUMBER)
1
db.stu.find().skip(2)
同时使用
1
2db.stu.find().limit(4).skip(5)
db.stu.find().skip(5).limit(4)
注意:先使用skip在使用limit的效率要高于前者
投影
在查询到的返回结果中, 只选择必要的字段
命令:db.集合名称.find({},{字段名称:1,...})
参数为字段与值, 值为1表示显示, 值为0不显 特别注意:
- 对于_id列默认是显示的, 如果不显示需要明确设置为0
- 对于其他不显示的字段不能设置为0,(0和1不能同时存在,
_id
有点例外)
1 | db.stu.find({},{_id:0,name:1,gender:1}) |
排序
方法sort(), 用于对查询结果按照指定的字段进行排序
命令:db.集合名称.find().sort({字段:1,...})
参数1为升序排列 参数-1为降序排列
1 | db.stu.find().sort({gender:-1,age:1}) # 根据性别降序, 再根据年龄升序 |
统计个数
方法count()用于统计结果集中文档条数
命令:db.集合名称.find({条件}).count()
命令:db.集合名称.count({条件})
1 | db.stu.find({gender:true}).count() |
更新
1 | db.集合名称.update({query}, {update}, {multi: boolean}) |
- 参数query:查询条件
- 参数update:更新操作符
- 参数multi:可选,默认是false,表示只更新找到的第一条数据,值为true表示把满足条件的数据全部更新
1 | db.stu.update({name:'hr'},{name:'mnc'}) # 全文档进行覆盖更新 |
注意:”multi update only works with $ operators”
- multi参数必须和$set一起使用!
删除
db.集合名称.remove({query}, {justOne: boolean})
- 参数query:可选,删除的⽂档的条件
- 参数justOne:可选, 如果设为true或1,则只删除一条,默认false,表示删除全部
聚合操作
聚合
聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。
语法:db.集合名称.aggregate({管道:{表达式}})
常用管道命令
在mongodb中,⽂档处理完毕后, 通过管道进⾏下⼀次处理 常用管道命令如下:
$group
: 将集合中的⽂档分组, 可⽤于统计结果$match
: 过滤数据, 只输出符合条件的⽂档$project
: 修改输⼊⽂档的结构, 如重命名、 增加、 删除字段、 创建计算结果$sort
: 将输⼊⽂档排序后输出$limit
: 限制聚合管道返回的⽂档数$skip
: 跳过指定数量的⽂档, 并返回余下的⽂档
常用表达式
表达式:处理输⼊⽂档并输出 语法:表达式:'$列名'
常⽤表达式:
$sum
: 计算总和, $sum:1 表示以⼀倍计数$avg
: 计算平均值$min
: 获取最⼩值$max
: 获取最⼤值$push
: 在结果⽂档中插⼊值到⼀个数组中
$group
分组
$group
是所有聚合命令中用的最多的一个命令,用来将集合中的文档分组,可用于统计结果
使用示例如下
1 | db.stu.aggregate( |
其中注意点:
db.db_name.aggregate
是语法,所有的管道命令都需要写在其中_id
表示分组的依据,按照哪个字段进行分组,需要使用$gender
表示选择这个字段进行分组$sum:1
表示把每条数据作为1进行统计,统计的是该分组下面数据的条数
group by null
当我们需要统计整个文档的时候,$group
的另一种用途就是把整个文档分为一组进行统计
使用实例如下:
1 | db.stu.aggregate( |
_id:null
表示不指定分组的字段,即统计整个文档,此时获取的counter
表示整个文档的个数
数据透视
正常情况在统计的不同性别的数据的时候,需要知道所有的name,需要逐条观察,如果通过某种方式把所有的name放到一起,那么此时就可以理解为数据透视
使用示例如下:
统计不同性别的学生
1
2
3
4
5
6
7
8db.stu.aggregate(
{$group:
{
_id:null,
name:{$push:"$name"}
}
}
)使用
$$ROOT
可以将整个文档放入数组中1
2
3
4
5
6
7
8db.stu.aggregate(
{$group:
{
_id:null,
name:{$push:"$$ROOT"}
}
}
)
示例
对于如下数据,需要统计出每个country/province下的userid的数量(同一个userid只统计一次)
1 | { "country" : "china", "province" : "sh", "userid" : "a" } |
参考答案
1 | db.tv3.aggregate( |
$match
$match
用于进行数据的过滤,是在能够在聚合操作中使用的命令,和find
区别在于$match
操作可以把结果交给下一个管道处理,而find
不行
使用示例如下:
查询年龄大于20的学生
1
2
3db.stu.aggregate(
{$match:{age:{$gt:20}}
)查询年龄大于20的男女学生的人数
1
2
3
4db.stu.aggregate(
{$match:{age:{$gt:20}}
{$group:{_id:"$gender",counter:{$sum:1}}}
)
$project
$project
用于修改文档的输入输出结构,例如重命名,增加,删除字段
使用示例如下:
查询学生的年龄、姓名,仅输出年龄姓名
1
2
3db.stu.aggregate(
{$project:{_id:0,name:1,age:1}}
)查询男女生人生,输出人数
1
2
3
4db.stu.aggregate(
{$group:{_id:"$gender",counter:{$sum:1}}}
{$project:{_id:0,counter:1}}
)
对于如下数据:统计出每个country/province下的userid的数量(同一个userid只统计一次),结果中的字段为{country:””,province:””,counter:””}
1 | { "country" : "china", "province" : "sh", "userid" : "a" } |
参考答案
1 | db.tv3.aggregate( |
$sort
$sort
用于将输入的文档排序后输出
使用示例如下:
查询学生信息,按照年龄升序
1
db.stu.aggregate({$sort:{age:1}})
查询男女人数,按照人数降序
1
2
3
4db.stu.aggregate(
{$group:{_id:"$gender",counter:{$sum:1}}},
{$sort:{counter:-1}}
)
$skip和$limit
$limit
限制返回数据的条数$skip
跳过指定的文档数,并返回剩下的文档数- 同时使用时先使用skip在使用limit
使用示例如下:
查询2条学生信息
1
2
3db.stu.aggregate(
{$limit:2}
)查询从第三条开始的学生信息
1
2
3db.stu.aggregate(
{$skip:3}
)统计男女生人数,按照人数升序,返回第二条数据
1
2
3
4
5
6db.stu.aggregate(
{$group:{_id:"$gender",counter:{$sum:1}}},
{$sort:{counter:-1}},
{$skip:1},
{$limit:1}
)
$unwind
1 | db.test3.aggregate({$unwind:{path:"$size", preserveNullAndEmptyArrays:true}}) |
索引操作
为什么mongdb需要创建索引
- 加快查询速度
- 进行数据的去重
创建索引
语法:db.集合名.ensureIndex({属性:1})
,1表示升序, -1表示降序
创建索引前后查询速度对比:
测试:插入10万条数据到数据库中
插入数据:
1 | for(i=0;i<100000;i++){db.t1.insert({name:'test'+i,age:i})} |
创建索引前:
1 | db.t1.find({name:'test10000'}) |
创建索引:
1 | db.t1.ensureIndex({name:1}) |
创建索引后:
1 | db.t1.find({name:'test10000'}).explain('executionStats') |
前后速度对比
唯一索引
在默认情况下mongdb的索引域的值是可以相同的,创建唯一索引之后,数据库会在插入数据的时候检查创建索引域的值是否存在,如果存在则不会插入该条数据,但是创建索引仅仅能够提高查询速度,同时降低数据库的插入速度。
1 | db.集合名.ensureIndex({"字段名":1}, {"unique":true}) |
利用唯一索引进行数据去重,根据唯一索引指定的字段的值,如果相同,则无法插入数据
1 | db.t1.ensureIndex({"name":1}, {"unique":true}) |
复合索引
在进行数据去重的时候,可能用一个域来保证数据的唯一性,这个时候可以考虑建立复合索引来实现。
例如:抓全贴吧信息,如果把帖子的名字作为唯一索引对数据进行去重是不可取的,因为可能有很多帖子名字相同
建立复合索引的语法:db.collection_name.ensureIndex({字段1:1,字段2:1})
注意事项
根据需要选择是否需要建立唯一索引
索引字段是升序还是降序在单个索引的情况下不影响查询效率,但是带复合索引的条件下会有影响
数据量巨大并且数据库的读出操作非常频繁的时候才需要创建索引,如果写入操作非常频繁,创建索引会影响写入速度
例如:在进行查询的时候如果字段1需要升序的方式排序输出,字段2需要降序的方式排序输出,那么此时复合索引的建立需要把字段1设置为1,字段2设置为-1
查看索引
默认情况下_id是集合的索引 查看方式:db.集合名.getIndexes()
删除索引
语法:db.集合名.dropIndex({'索引名称':1})
1 | db.t1.dropIndex({name:1}) |
权限管理
刚安装完毕的mongodb默认不使用权限认证方式启动,与MySQL不同,mongodb在安装的时候并没有设置权限,然而公网运行系统需要设置权限以保证数据安全,所以我们要学习mongodb的权限管理。
- MongoDB是没有默认管理员账号,所以要先添加管理员账号,并且mongodb服务器需要在运行的时候开启验证模式
- 用户只能在用户所在数据库登录(创建用户的数据库),包括管理员账号。
- 管理员可以管理所有数据库,但是不能直接管理其他数据库,要先认证后才可以。
超级管理员
1 | sudo mongod # 进入mongo shell |
创建成功会显示如下信息
1 | Successfully added user: { "user" : "python", "roles" : [ "root" ] } |
以权限认证的方式启动mongodb数据库:
1 | sudo mongod --auth |
启动之后在启动信息中会有如下信息,说明mongodb以权限认证的方式启动成功
1 | [initandlisten] options: { security: { authorization: "enabled" } } |
登录验证:此时再使用数据库各命令的时候会报权限错误,需要认证才能执行相应操作
1 | use admin |
- python用户是创建在admin数据库上的所以必须来到admin数据库上进行认证
- 认证成功会返回1,失败返回0
普通用户
1、在使用的数据库上创建普通用户
1 | use test1 # 选择需要创建用户的数据库 |
2、在admin用户数据库上创建普通用户
1 | use admin |
- 在admin上创建python1用户,python1用户的权限有两个,一个再dbname1上的只读,另一个是在dbname2上的读写
查看用户
1 | show users |
1 | { |
删除用户
1 | use db_name # 进入账号数据所在的数据库 |
pymongo
查看pymongo官方文档或源代码 PyMongo — MongoDB Drivers
pymongo
提供了mongdb和python交互的所有方法 安装方式: pip install pymongo
连接mongodb
导入pymongo并选择要操作的集合(数据库和集合能够自动创建)
1、无需权限认证的方式创建连接对象以及集合操作对象
1 | from pymongo import MongoClient |
2、需要权限认证的方式创建连接对象以及集合操作对象
1 | from pymongo import MongoClient |
还可以使用authenticate进行权限认证
1 | from pymongo import MongoClient |
添加数据
insert可以批量的插入数据列表,也可以插入一条数据
1 | collection.insert({一条数据}) |
添加一条数据:
返回插入数据的_id
1 | ret = collection.insert({"name":"test10010","age":33}) |
添加多条数据:
返回ObjectId对象构成的列表
1 | item_list = [{"name":"test1000{}".format(i)} for i in range(10)] |
查找数据
find_one()查找一条数据:
接收一个字典形式的条件,返回字典形式的整条数据 如果条件为空,则返回第一条
1 | ret = client.test.test.find_one({'name': 'test10001'}) |
find()查找全部数据:
返回所有满足条件的结果,如果条件为空,则返回全部 结果是一个Cursor游标对象,是一个可迭代对象,可以类似读文件的指针,但是只能够进行一次读取
1 | rets = collection.find({"name":"test10005"}), |
更新数据
update()更新数据(全文档覆盖或指定键值,更新一条或多条)
- 语法:collection.update({条件}, {‘$set’:{指定的kv或完整的一条数据}}, multi=False/True, upsert=False/True)
- multi参数:默认为False,表示更新一条; multi=True则更新多条; multi参数必须和$set一起使用
- upsert参数:默认为False; upsert=True则先查询是否存在,存在则更新;不存在就插入
- $set表示指定字段进行更新
更新一条数据;全文档覆盖;存在就更新,不存在就插入
1 | data = {'msg':'这是一条完整的数据1','name':'哈哈'} |
更新多条数据;全文档覆盖;存在就更新,不存在就插入
1 | data = {'msg':'这是一条完整的数据2','name':'哈哈'} # 该完整数据是先查询后获取的 |
更新一条数据;指定键值;存在就更新,不存在就插入
1 | data = {'msg':'指定只更新msg___1'} |
更新多条数据;指定键值;存在就更新,不存在就插入
1 | data = {'msg':'指定只更新msg___2'} |
删除数据
delete_one()删除一条数据
1 | collection.delete_one({"name":"test10010"}) |
delete_many()删除全部数据
1 | collection.delete_many({"name":"test10010"}) |