1. Mongo的介绍
1.1 NoSQL的介绍
“NoSQL”一词最早于1998年被用于一个轻量级的关系数据库的名字
随着web2.0的快速发展,NoSQL概念在2009年被提了出来
NoSQL在2010年风生水起,现在国内外众多大小网站,如facebook、google、淘宝、京东、百度等,都在使用 nosql 开发高性能的产品
对于一名程序员来讲,使用 nosql 已经成为一条必备的技能
NoSQL最常见的解释是“non-relational”,“Not Only SQL”也被很多人接受,指的是非关系型的数据库
1.2 关系型和非关系型的介绍
Relational:关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差,大数据下IO压力大,表结构更改困难
MongoDB:易扩展,大数据量高性能,灵活的数据模型,更可用
1.3 MongDB的优势
- 易扩展:NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系型数据库的关系型特性。数据之间无关系,这样就非常容易扩展
- 大数据量,高性能:NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于他的无关系性,数据库的结构简单
- 灵活的数据模型:NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦
2. MongoDB安装和启动
2.1 Ubuntu下安装MongoDB
sudo apt-get install -y mongodb-org
2.2 其他linux系统下安装MongoDB
- 解压
tar -zxvf mongodb-linux-x86_64-ubuntu1604-3.4.0.tgz # 有的命令为 tar -xf
- 移动到 /usr/local/ 目录下
sudo mv -r mongodb-linux-x86_64-ubuntu1604-3.4.0/ /usr/local/mongodb
- 将可执行文件添加到 PATH 路径中
sudo vi /etc/profile # 配置环境变量
export PATH=/usr/local/mongodb/bin:$PATH
2.3 服务端MongoDB启动
查看帮助 | mongod –help |
---|---|
启动 | sudo service mongod start |
停止 | sudo service mongod stop |
重启 | sudo service mongod restart |
查看是否启动成功 | ps ajx|grep mongod |
配置文件的位置 | /etc/mongod.conf |
默认端口 | 27017 |
日志的位置 | /var/log/mongodb/mongod.log |
2.4 客户端Mongo
启动本地客户端 | Mongo |
---|---|
查看帮助 | mongo –help |
退出 | exit或者ctrl+c |
3. MongoDB基本操作
3.1 关于 database 的基础命令
查看当前的数据库 | db |
---|---|
查看所有的数据库 | show dbs /show databases |
切换数据库 | use db_name |
删除当前的数据库 | db.dropDatabase() # db指代当前 use 的数据库 |
3.2 关于集合的基础命令
不手动创建集合:向不存在的集合中第一次加入数据时,集合会被创建出来
手动创建集合:db.createCollection(name, options)
db.createCollection(“stu”)
db.createCollection(“sub”, {capped: true, size: 10})
参数capped:默认值为 false 表示不设置上限,值为 true 表示设置上限
参数size:当 capped 值为 true 时,需要指定此参数,表示上限大小,当文档达到上限时,会将之前的数据覆盖,单位为字节
查看集合:show collections
删除集合:db.集合名称.drop()
3.3 数据类型
Object ID | 文档ID |
---|---|
String | 字符串。最常用,必须是有效的UTF-8 |
Boolean | 存储一个布尔值,true或false |
Integer | 整数可以是32位或64位,这取决于服务器 |
Double | 存储浮点值 |
Arrays | 数组或列表,多个值存储到一个键 |
Object | 用于嵌入式的文档,即一个值为一个文档 |
Null | 存储Null值 |
Timestamp | 时间戳,表示从1970-1-1到现在的秒数 |
Date | 存储当前日期或时间的UNIX时间格式 |
注意点
- 创建日期语句如下:参数的格式为YYYY-MM-DD new Date(“2017-12-20”)
- 每个文档都有一个属性,为 _id,保证每个文档的唯一性
- 可以自己去设置 _id 插入文档,如果没有提供,那么 MongoDB 为每个文档提供了一个独特的 _id,类型为 objectID
- objectID 是一个12字节的十六进制数:
- 前四个字节为当前时间戳
- 接下来3个字节的机器ID
- 接下来的2个字节中MongoDB的服务进程id
- 最后3个字节是简单的增量值
4. MongoDB数据查询
4.1 MongoDB的增删改查
4.1.1 插入
db.集合名称.insert(document)
db.stu.insert({name: “gj”, gender: 1})
db.stu.insert({_id: “20170101”, name: “gj”, gender: 1})
插入文档时,如果不指定_id 参数,MongoDB会为文档分配一个唯一的ObjectId
4.1.2 保存
db.集合名称.save(document)
如果文档的_id 已经存在则修改,如果文档的 _id 不存在则添加
4.1.3 简单查询
- db.集合名称.find()
4.1.4 更新
- db.集合名称.update(<query>, <update>, {multi: <boolean>})
- 参数query:查询条件
- 参数update:更新操作符
- 参数multi:可选,默认是false,表示只更新找到的第一条记录,值为 true 表示把满足条件的文档全部更新
db.stu.update({name: "hr"}, {$set: {name: "hys"}}) # 更新一条
db.stu.update({}, {$set{gender: 0}}, {multi: true}) # 更新全部
注意:multi update only works with $ operators
4.1.5 删除
db.集合名称.remove(<query>, {justOne: <boolean>})
参数query:可选,删除的文档的条件
参数justOne:可选,如果设为 true 或1,则只删除一条,默认 false,表示删除多条
4.1.6 高级查询
方法find():查询
db.集合名称.find({条件文档})
方法findOne():查询,只返回第一个
db.集合名称.findOne({条件文档})
方法pretty():将结果格式化
db.集合名称.find({条件文档}).pretty()
比较运算符
- 等于:默认是等于判断,没有运算符
- 小于:$lt (less than)
- 小于等于:$lte (less than equal)
- 大于:$gt (greater than)
- 大于等于:$gte
- 不等于:$ne
db.stu.find({age: {$gte: 18}}) # 查询年龄大于等于18岁的数据
范围运算符
使用“$in”,“$nin”判断是否在某个范围内
查询年龄为18、28的学生:
db.stu.find({age: {$in: [18, 28, 38]}})
逻辑运算符
and:在json中写多个条件即可
查询年龄大于或等于18,并且性别为true的学生
db.stu.find({age: {$gte: 18}, gender: true})
or:使用$or,值为数组,数组中每个元素为json
查询年龄大于18,或性别为false的学生
db.stu.find({$or: [{age: {$gt: 18}}, {gender: false}]})
- 查询年龄大于18或性别为男生,并且姓名是郭靖
db.stu.find({$or: [{age: {$gte: 18}}, {gender: true}], name: "gj"})
支持正则表达式
使用 // 或 $regex 编写正则表达式
db.products.find({sku: /^abc/}) # abc为开头的字符
db.products.find({sku: {$regex: "789$"}}) # 789为结尾的字符
limit和skip
方法limit():用于读取指定数量的文档
db.集合名称.find().limit(NUMBER)
查询2条学生信息:
db.stu.find().limit(2)
方法skip():用于跳过指定数量的文档
db.集合名称.find().skip(NUMBER)
db.stu.find().skip(2)
- 同时使用
db.stu.find().limit(4).skip(5)
# 或者
db.stu.find().skip(5).limit(4)
注意:当数据量比较大的时候,先skip跳过,然后再limit,这样效率高一些
自定义查询
使用 $where 后面写一个函数,返回满足条件的数据
查询年龄大于30的学生
db.stu.find({
$where: function(){
return this.age>30;
}
})
MongoDB的终端就是一个JS的终端,我们可以在里面写JS语句
投影
在查询到的返回结果中,只选择必要的字段
db.集合名称.find({}, {必要的字段名称: 1,….})
参数为字段与值,值为1表示显示,值为0不显
特殊:对于 _id 列默认是现实的,如果不显示需要明确设置为0
db.stu.find({}, {_id: 0, name: 1,gender: 1})
排序
方法sort(),用于对集合进行排序
db.集合名称.find().sort({字段: 1….})
参数1为升序排列
参数-1为降序排列
根据性别降序,再根据年龄升序
db.stu.find().sort({gender: -1, age: 1})
统计个数
方法count(),用于统计结果集中文档条数
db.集合名称.find({条件}).count()
db.集合名称.count({条件})
db.stu.find({gender: true}).count()
db.stu.count({age: {$gt: 20}, gender: true})
消除重复
方法distinct(),对数据进行去重
db.集合名称.distinct(“去重字段”, {条件})
db.stu.distinct("hometown", {age: {$gt: 18}})
5. MongoDB聚合
5.1 聚合 aggregate
聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。
db.集合名称.aggregate({管道: {表达式}})
5.1.1 常用管道
在MongoDB中,文档处理完毕后,通过管道进行下一次处理
常用管道如下:
$group | 将集合中的文档分组,可用于统计结果 |
---|---|
$match | 过滤数据,只输出符合条件的文档 |
$project | 修改输入文档的结构,如重命名、增加、删除字段、创建计算结果 |
$sort | 将输入文档排序后输出 |
$limit | 限制聚合管道返回的文档数 |
$skip | 跳过指定数量的文档,并返回余下的文档 |
$unwind | 将数组类型的字段进行拆分 |
5.2.2 表达式
处理输入文档并输出
语法:表达式:”$列名”
常用表达式:
$sum | 计算总和,$sum: 1 表示以一倍计数 |
---|---|
$avg | 计算平均值 |
$min | 获取最小值 |
$max | 获取最大值 |
$push | 在结果文档中插入值到一个数组中 |
$first | 根据资源文档的排序获取第一个文档数据 |
$last | 根据资源文档的排序获取最后一个文档数据 |
5.2 聚合之group
- 将集合中的文档分组,可用于统计结果
- _id 表示分组的依据,使用某个字段的格式为“$字段”
- 例1:统计男生、女生的总人数
db.stu.aggregate(
{$group:
{
_id: "$gender", # 指定_id表示按照gender进行分组
counter: {$sum: 1} # 统计人数
}
}
)
Group by null
- 将集合中所有文档分为一组
- 例2:求学生总人数、平均年龄
db.stu.aggregate(
{$group:
{
_id: null,
counter: {$sum: 1},
avgAge: {$avg: "$age"}
}
}
)
5.3 聚合之project
- 修改输入文档的结构,如重命名、增加、删除字段、创建计算结果
- 例1:查询学生的姓名、年龄
db.stu.aggregate(
{$project: {_id: 0, name: 1, age: 1}} # 显示所有学生的姓名,年龄,不显示id
)
- 例2:查询男生、女生人数,输出人数
db.stu.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}}}, # 通过性别分组,输出每组的人数
{$project: {_id: 0, counter: 1}} # 不显示id,显示人数
)
- 例3:按照gender进行分组,获取不同组数据的个数和平均年龄
db.stu.aggregate(
{$group: {_id: "$gender", count: {$sum: 1}, avg_age: {$avg: "$age"}}},
{$project: {gender: "$_id", count: 1, avg_age: 1, _id: 0}}
)
5.4 聚合之$match
- 用于过滤数据,只输出符合条件的文档
- 使用MongoDB的标准查询操作
- 例1:查询年龄大于20的学生,观察男性和女性有多少人
db.stu.aggregate(
{$match: {$age: {$gt: 20}}},
{$group: {_id: "$gender", count: {$sum: 1}}},
{$project: {_id: 0, gender: "$_id", count: 1}}
)
5.5 练习题
db.tv3.aggregate(
{$group: {_id: {country: "$country", province: "$province", userid: "userid"}}},
{$group: {_id: {country: "$_id.country", province: "$_id.province"}, count: {$sum: 1}}},
{$project: {country: "$_id.country", province: "$_id.rpovince", count: 1, _id: 0}}
)
$group的注意点
- $group 对应的字典中有几个键,结果中就有几个键
- 分组依据需要放到 _id 后面
- 取不同的字段的值需要使用,”gender, $age”
- 取字典嵌套的字典中的值的时候 “$_id.country”
- 能够同时按照多个值进行分组 “{$group: {_id: {country: “$country”, province: “$province”}}}”
- 结果是:”{_id: {country: “ “, province: “ “,…..}}”
5.6 聚合之$sort
- 将输入文档排序后输出
- 例1:查询学生信息,按年龄升序
db.stu.aggregate({$sort: {age: 1}})
- 例2:查询男生、女生人数,按人数排序
db.stu.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}}},
{$sort: {counter: -1}}
)
5.7 聚合之$limit和$skip
$limit
- 限制聚合管道返回的文档数
- 例1:查询2条学生信息
db.stu.aggregate(
{$limit: 2}
)
$skip
- 跳过指定数量的文档,并返回余下的文档
- 例2:查询从第3条开始的学生信息
db.stu.aggregate(
{$skip: 2}
)
- 例3:统计男生、女生人数,按人数升序,取第2条数据
db.stu.aggregate(
{$group: {_id: "$gender", counter: {$sum: 1}}},
{$sort: {counter: 1}},
{$skip: 1},
{$limit: 1}
)
- 注意顺序,先写 skip,再写 limit
5.8 聚合之$unwind
将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值
语法:db.集合名称.aggregate({$unwind: “$字段名称”})
db.t2.insert({_id: 1, "item": "t-shirt", "size": ["S", "M", "L"]})
db.t2.aggregate({$unwind: "$size"})
结果如下:
db.t2.insert(
{_id: 1, "item": "t-shirt", "size": "S"},
{_id: 1, "item": "t-shirt", "size": "M"},
{_id: 1, "item": "t-shirt", "size": "L"},
)
属性值为 false 表示丢弃属性值为空的文档
属性 preserveNullAndEmptyArrays 值为 true 表示保留属性值为空的文档
用法:
db.inventory.aggregate(
{$unwind: {
path: "$字段名称",
preserveNUllAnd EmptyArrays: # 防止数据丢失
}}
)
$unwind练习
数据库中有一条数据:{“username”: “Alex”, “tags”: [“C#”, “Java”, “C++”]},如何获取该 tags 列表的长度?
db.t2.aggregate(
{$match: {username: "Alex"}},
{$unwind: "$tags"},
{$group: {_id: null, sum: {$sum: 1}}}
)
6. 索引和备份
6.1 创建索引
- 索引:以提升查询速度
- 测试:插入10万条数据到数据库中
for(i=0;i<100000;i++){db.t255.insert({name: "t est" + i, age: i})}
建立索引:
- 语法:db.集合.ensureindex({属性: 1}),1表示升序,-1表示降序
- 具体操作:db.t255.ensureIndex({name: 1})
索引
在默认情况下索引字段的值可以相同
创建唯一索引(索引的值是唯一的):
db.t1.ensureIndex({"name": 1}, {"unique": true})
- 建立联合索引(什么时候需要联合索引):
db.t1.ensureIndex({name: 1, age: 1})
- 查看当前集合的所有索引:
db.t1.getIndexes()
- 删除索引:
db.t1.dropIndex({"索引名称": 1})
练习
- 统计t1中所有的name的出现的次数
- 统计t1中所有的name的出现的次数中次数大于4的name
- 统计t1中所有的name的出现的次数中次数大于4的次数(只显示次数)
db.t1.aggregate(
{$group: {_id: "$name", count: {$sum: 1}}}
)
db.t1.aggregate(
{$group: {_id: "$name", count: {$sum: 1}}},
{$match: {$gt: 4}}
)
db.t1.aggregate(
{$group: {_id: "$name", count: {$sum: 1}}},
{$match: {$gt: 4}},
{$project: {_id: 0, count: 1}}
)
7. MonogoDB与Python交互
1. 安装pymongo
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pymongo
2. 使用pymongo
注意:在使用之前,一定要注意先提前打开mongodb服务
from pymongo import MongoClient # 导入pymongo
client = MongoClient(host="127.0.0.1", port=27017) # 实例化client 连接mongodb
db = client["test"] # 选择数据库
collection = db["t1"] # 选择集合
collection.insert({"_id": 10011, "name": "xiaowang", "age": 10}) # 插入一条数据
2.1 插入数据
如果需要取集合内的对应的id,以上面的代码为例:
ret = collection.insert({"_id": 10011, "name": "xiaowang", "age": 10}) # 插入一条数据
print(ret)
插入多条数据:
data_list = [{"name": "test{}".format(i)} for i in range(10)] # 生成多条数据 类型必须是列表
collection.insert_many(data_list) # insert_many插入多条数据
2.2 查询数据
查询一个记录:
t = collection.find_one({"name": "xiaowang"})
print(t)
查询多个记录:
t = collection.find({"name": "xiaowang"})
for i in t:
print(i)
注意:.find()方法生成的是一个游标对象,和 python 中的文件操作一样,读取一次不能再读取第二次。必须调整游标位置才能进行第二次读取。
具体可以 print 一下 .find方法生成的对象:
t = collection.find({"name": "xiaowang"})
print(t)
输出结果是这样子的:
也可以做一次强制类型转换来方便数据的提取:
t = collection.find({"name": "xiaowang"})
print(list(t))
2.3 修改数据
修改一条数据:
collection.update_one({"name": "test1005"}, {"$set": {"name": "new_test1005"}})
修改多条(全部)数据:
collection.update_many({"name": "test1005"}, {"$set": {"name": "new_test1005"}})
2.4删除数据
删除一条数据:
collection.delete_one({"name": "test10010"})
删除所有满足条件的数据:
collection.delete_many({"name": "test10010"})
3. 练习
- 使用 python 向集合 t3 中插入1000条文档,文档的属性包括 _id、name
- _id 的值为0、1、2、3….999
- name 的值为 “py0”、”py1”….
- 查询显示出 _id 为100的整倍数的文档,如100、200、300….,并将 name 输出
1.
from pymongo import MongoClient # 导入pymongo
client = MongoClient(host="127.0.0.1", port=27017) # 建立连接
collection = client["test"]["practice"] # 选择数据库后选择集合
data_list = [{"_id": i, "name": "py{}".format(i)} for i in range(1000)] # 利用列表推导式生成上面第一题要求的数据列表
collection.insert_many(data_list) # 插入多条数据
2.
from pymongo import MongoClient # 导入pymongo
client = MongoClient(host="127.0.0.1", port=27017) # 建立连接
collection = client["test"]["practice"] # 选择数据库后选择集合
ret = collection.find() # 查找多条数据
data_list = list(ret) # 将游标对象转换成列表
data_list = [i for i in data_list if i["_id"]%100==0 and i["_id"]!=0] # 利用列表推导式生成上面第二题要求的数据列表
print(data_list)
PS:Redis 和 MongoDB很像,和MySQL不一样,不需要特意去断开连接。因为 Redis 和 MongoDB 会自动帮我们断开连接