赞
踩
MongoDB是一个文档数据库(以 JSON 为数据模型),由C++语言编写,旨在为WEB应用提供可扩展的 高性能数据存储解决方案。
文档来自于“JSON Document”,并非我们一般理解的 PDF,WORD 文档。
MongoDB是非关系数据库当中功能最丰富,最像 关系数据库的。它支持的数据结构非常松散,数据格式是BSON,一种类似JSON的二进制形式的存储格 式,简称Binary JSON ,和JSON一样支持内嵌的文档对象和数组对象,因此可以存储比较复杂的数据类 型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几 乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。原则上 Oracle 和 MySQL 能做的事情,MongoDB 都能做(包括 ACID 事务)。
MongoDB在数据库总排名第5,仅次于Oracle、MySQL等RDBMS,在NoSQL数据库排名首位。从诞生 以来,其项目应用广度、社区活跃指数持续上升。
MongoDB概念与关系型数据库(RDBMS)非常类似:
SQL概念 | MongoDB概念 |
---|---|
数据库(database) | 数据库(database) |
表(table) | 集合(collection) |
行(row) | 文档(document) |
列(column) | 字段(field) |
索引(index) | 索引(index) |
主键(primary key) | _id(字段) |
视图(view) | 视图(view) |
表连接(table joins) | 聚合操作($lookup) |
尽管这些概念大多与SQL标准定义类似,但MongoDB与传统RDBMS仍然存在不少差异,包括:
MongoDB基于灵活的JSON文档模型,非常适合敏捷式的快速开发。与此同时,其与生俱来的高可用、 高水平扩展能力使得它在处理海量、高并发的数据应用时颇具优势。
横向扩展力,轻松支持TB-PB数量级
JSON 结构和对象模型接近,开发代码量低
JSON的动态模型意味着更容易响应新的业务需求
复制集提供99.999%高可用
分片架构支持海量数据和无缝扩容
下载MongoDB Community Server
下载地址: https://www.mongodb.com/try/download/community
重命名
mv mongodb-linux-x86_64-rhel70-4.4.15 mongodb
添加环境变量
修改/etc/profile
,添加环境变量,方便执行MongoDB命令
export MONGODB_HOME=/qijingjing/resource/mongodb
PATH=$PATH:$MONGODB_HOME/bin
使环境变量立即生效
source /etc/profile
# 创建一些存放数据、日志、配置的文件夹
mkdir -p data log conf
# 创建一个日志文件
touch log/mongodb.log
# 进行启动
bin/mongod --port=27017 --dbpath=data --logpath=log/mongodb.log --fork
名词解释
利用配置文件启动服务
编辑mongodb/conf/mongo.conf
文件,内容如下
systemLog:
destination: file
path: /qijingjing/resource/mongodb/log/mongod.log # 日志文件的全路径
logAppend: true
storage:
dbPath: /qijingjing/resource/mongodb/data # 存放数据文件夹的的全路径
engine: wiredTiger #存储引擎
journal: #是否启用journal日志
enabled: true
net:
bindIp: 0.0.0.0 #开启可以使外网访问
port: 27017 # 端口号
processManagement:
fork: true
注意一定要yaml格式
启动mongod(没有关闭要先关闭mongod,然后再启动)
mongod -f conf/mongo.conf
关闭MongoDB服务
方式一:
mongod --port=27017 --dbpath=data --shutdown
方式二:
执行mongo
命令进入shell界面
use admin;
db.shutdownServer();
mongo是MongoDB的交互式JavaScript Shell界面,它为系统管理员提供了强大的界面,并为开发人员 提供了直接测试数据库查询和操作的方法。
mongo --port=27017
–port:指定端口,默认为27017
–host: 连接的主机地址,默认为127.0.0.1
JavaScript支持
mongo shell是基于JavaScript语法的,MongoDB使用了SpiderMonkey作为其内部的JavaScript解释器 引擎,这是由Mozilla官方提供的JavaScript内核解释器,该解释器也被同样用于大名鼎鼎的Firefox浏览 器产品之中。SpiderMonkey对ECMA Script标准兼容性非常好,可以支持ECMA Script 6。可以通过下 面的命令检查JavaScript解释器的版本:
mongo shell 常用命令
命令 | 说明 |
---|---|
show dbs|show databases | 显示数据库列表 |
use 数据库名 | 切换数据库,如果不存在创建数据库 |
db.dropDatabase() | 删除数据库 |
show collections|show tables | 显示当前数据库的集合列表 |
db.集合名.stats() | 查看集合详情 |
db.集合名.drop() | 删除集合 |
show users | 显示当前数据库的用户列表 |
show roles | 显示当前数据库的角色列表 |
show profile | 显示最近发生的操作 |
load(“xxx.js”) | 执行一个JavaScript脚本文件 |
exit | quit()退出当前shell |
help | 查看mongodb支持哪些命令 |
db.help() | 查询当前数据库支持的方法 |
db.集合名.help() | 显示集合的帮助信息 |
db.version() | 查看数据库版本 |
#查看所有库
show dbs
# 切换到指定数据库,不存在则创建
use test
# 删除当前数据库
db.dropDatabase()
#查看集合
show collections
#创建集合
db.createCollection("emp")
#删除集合
db.emp.drop()
创建集合语法
db.createCollection(name,options)
options参数
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔 | (可选)如果为true,则会创建固定集合,当达到最大值时,它会自动覆盖最早的文档 |
size | 数值 | (可选)为固定集合指定一个最大值(以字节计)。如果capped为true,也需要指定该字段 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量 |
注意:当集合不存在时,向集合中插入文档也会创建集合
# 设置管理员用户名密码需要切换到admin库
use admin
#创建管理员
db.createUser({user:"root",pwd:"root",roles:["root"]})
# 查看所有用户信息
show users
#删除用户
db.dropUser("root")
权限名 | 描述 |
---|---|
read | 允许用户读取指定数据库 |
readWrite | 允许用户读写指定数据库 |
dbAdmin | 允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统 计或访问system.profile |
dbOwner | 允许用户在指定数据库中执行任意操作,增、删、改、查等 |
userAdmin | 允许用户向system.users集合写入,可以在指定数据库里创建、删 除和管理用户 |
clusterAdmin | 只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管 理权限 |
readAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读权限 |
readWriteAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的读写权限 |
userAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 |
dbAdminAnyDatabase | 只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限 |
root | 只在admin数据库中可用。超级账号,超级权限 |
用户认证,返回1表示认证成功
> use admin
switched to db admin
> db.auth("root","root")
1
use appdb
db.createUser({user:"123456",pwd:"123456",roles:["dbOwner"]})
默认情况下,MongoDB不会启用鉴权,以鉴权模式启动MongoDB
mongod -f conf/mongo.conf --auth
启用鉴权之后,连接MongoDB的相关操作都需要提供身份认证。
mongo -u 123456 -p 123456 --authenticationDatabase=appdb
3.2版本之后新增了db.collection.insertOne()
和db.collection.insertMany()
.
新增单个文档
writeConcern
db.collection.insertOne(
<document>,
{
writeConcern: <document>
}
)
writeConcern 决定一个写操作落到多少个节点上才算成功。writeConcern 的取值包括: 0:发起写操作,不关心是否成功;
1~集群最大数据节点数:写操作需要被复制到指定节点数才算成功;
majority:写操作需要被复制到大多数节点上才算成功
insert: 若插入的数据主键已经存在,则会抛 DuplicateKeyException 异常,提示主键重复,不保存当前数据
save: 如果_id主键存在则更新数据,如果不存在就插入数据。
> db.emp.insert({name:"张三",age:12})
WriteResult({ "nInserted" : 1 })
> db.emp.save({name:"李四",age:3})
WriteResult({ "nInserted" : 1 })
> db.emp.insertOne({sex:1,age:2})
{
"acknowledged" : true,
"insertedId" : ObjectId("62c410ed65d7649befbb697a")
}
> db.emp.find()
{ "_id" : ObjectId("62c410ac65d7649befbb6978"), "name" : "张三", "age" : 12 }
{ "_id" : ObjectId("62c410b465d7649befbb6979"), "name" : "李四", "age" : 3 }
{ "_id" : ObjectId("62c410ed65d7649befbb697a"), "sex" : 1, "age" : 2 }
>
批量新增文档
insertMany
: 向指定集合中插入多条文档数据db.collection.insertMany(
[ <document 1> , <document 2>, ... ],
{
writeConcern: <document>,
ordered: <boolean>
}
)
writeConcern:写入策略,默认为 1,即要求确认写操作,0 是不要求。
ordered:指定是否按顺序写入,默认 true,按顺序写入。
> db.emp.insert([{x:5,y:7},{x:3,y:9}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.emp.save([{x:50,y:70},{x:30,y:90}])
BulkWriteResult({
"writeErrors" : [ ],
"writeConcernErrors" : [ ],
"nInserted" : 2,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
> db.emp.insertMany([{x:550,y:750},{x:350,y:950}])
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("62c4143565d7649befbb6980"),
ObjectId("62c4143565d7649befbb6981")
]
}
>
测试: 批量插入50条随机数据
编辑脚本book.js
var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){
var typeIdx = Math.floor(Math.random()*types.length);
var tagIdx = Math.floor(Math.random()*tags.length);
var favCount = Math.floor(Math.random()*100);
var book = {
title: "book-"+i,
type: types[typeIdx],
tag: tags[tagIdx],
favCount: favCount,
author: "xxx"+i
};
books.push(book)
}
db.books.insertMany(books);
进入mongo shell ,执行
load("book.js")
读取:
db.books.find()
有分页,输入it进入下一页
find查询集合中的若干文档。语法格式如下:
db.collection.find(query, projection)
如果查询返回的条目数量较多,mongo shell则会自动实现分批显示。默认情况下每次只显示20条,可 以输入it命令读取下一批。
findOne查询集合中的第一个文档。语法格式如下:
db.collection.findOne(query, projection)
条件查询
#查询带有nosql标签的book文档:
db.books.find({tag:"nosql"})
#按照id查询单个book文档:
db.books.find({_id:ObjectId("61caa09ee0782536660494d9")})
#查询分类为“travel”、收藏数超过60个的book文档:
db.books.find({type:"travel",favCount:{$gt:60}})
查询条件对照表
SQL | MQL |
---|---|
a =1 | {a: 1} |
a <> 1 | {a: {$ne: 1}} |
a > 1 | {a: {$gt: 1}} |
a >= 1 | {a: {$gte: 1}} |
a < 1 | {a: {$lt: 1}} |
a <=1 | {a: {$lte: 1}} |
查询逻辑对照表
SQL | MQL |
---|---|
a = 1 AND b = 1 | {a: 1, b: 1}或{$and: [{a: 1}, {b: 1}]} |
a = 1 OR b = 1 | {$or: [{a: 1}, {b: 1}]} |
a IS NULL | {a: {$exists: false}} |
a IN (1, 2, 3) | {a: {$in: [1, 2, 3]}} |
查询逻辑运算符
排序分页
在MongoDB中使用sort()
方法对数据进行排序
#指定按收藏数(favCount)降序返回
db.books.find({type:"travel"}).sort({favCount:-1})
skip
用于指定跳过记录数,limit
则用于限定返回结果数量
db.books.find().skip(8).limit(4)
正则表达式匹配查询
MongoDB使用$regex
操作符来设置匹配字符串的正则表达式
//使用正则表达式查找type包含 so 字符串的book
db.books.find({type:{$regex:"so"}})
//或者
db.books.find({type:/so/})
可以用update命令对指定的数据进行更新,命令的格式如下:
db.collection.update(query,update,options)
更新操作符
操作符 | 格式 | 描述 |
---|---|---|
$set | {$set:{field:value}} | 指定一个键并更新值,若键不存在则创 建 |
$unset | {$unset : {field : 1 }} | 删除一个键 |
$inc | {$inc : {field : value } } | 对数值类型进行增减 |
$rename | {$rename : {old_field_name : new_field_name } } | 修改字段名称 |
$push | { $push : {field : value } } | 将数值追加到数组中,若数组不存在则 会进行初始化 |
$pushAll | {$pushAll : {field : value_array }} | 追加多个值到一个数组字段内 |
$pull | {$pull : {field : _value } } | 从数组中删除指定的元素 |
$addToSet | {$addToSet : {field : value } } | 添加元素到数组中,具有排重功能 |
$pop | {$pop : {field : 1 }} | 删除数组的第一个或最后一个元素 |
更新单个文档
某个book文档被收藏了,则需要将该文档的favCount字段自增
db.books.update({_id:ObjectId("61caa09ee0782536660494d9")},{$inc:{favCount:1}})
更新多个文档
默认情况下,update命令只在更新第一个文档之后返回,如果需要更新多个文档,则可以使用multi选项。
将分类为’novel’的文档增加发布时间(publishedDate)
db.books.update({type:"novel"},{$set:{publishedDate:new Date()}},{"multi":true})
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件 查出来多条记录全部更新
update命令的选项配置较多,为了简化使用还可以使用一些快捷命令:
使用upsert命令
upsert是一种特殊的更新,其表现为如果目标文档不存在,则执行插入命令
db.books.update(
{title:"my book"},
{$set:{tags:["nosql","mongodb"],type:"none",author:"lili"}},
{upsert:true}
)
nMatched、nModified都为0,表示没有文档被匹配及更新,nUpserted=1提示执行了upsert动作
实现replace语义
update命令中的更新描述(update)通常由操作符描述,如果更新描述中不包含任何操作符,那么 MongoDB会实现文档的replace语义
db.books.update(
{title:"my book"},
{justTitle:"my first book"}
)
如果没有使用操作符的话,这个替换,是对文档的一个替换
findAndModify命令
findAndModify兼容了查询和修改指定文档的功能,findAndModify只能更新单个文档
//将某个book文档的收藏数(favCount)加1
db.books.findAndModify({
query:{_id:ObjectId("62c41650a6a10cd974bac894")},
update:{$inc:{favCount:1}}
})
该操作会返回符合查询条件的文档数据,并完成对文档的修改。
默认情况下,findAndModify会返回修改前的旧数据,如果希望返回修改后的数据,则可以指定new选项
db.books.findAndModify({
query:{_id:ObjectId("62c41650a6a10cd974bac894")},
update:{$inc:{favCount:1}},
new: true
})
与findAndModify语义相近的命令如下:
示例:
db.user.remove({age:28})// 删除age 等于28的记录
db.user.remove({age:{$lt:25}}) // 删除age 小于25的记录
db.user.remove( { } ) // 删除所有记录
db.user.remove() //报错
remove命令会删除匹配条件的全部文档,如果希望明确限定只删除一个文档,则需要指定justOne参 数,命令格式如下
db.collection.remove(query,justOne)
例如: 删除满足type:novel条件的首条记录
db.books.remove({type:"novel"},true)
使用delete删除文档
官方推荐使用 deleteOne()
和 deleteMany()
方法删除文档,语法格式如下
db.books.deleteMany ({}) //删除集合下全部文档
db.books.deleteMany ({ type:"novel" }) //删除 type等于 novel 的全部文档
db.books.deleteOne ({ type:"novel" }) //删除 type等于novel 的一个文档
注意: remove、deleteMany等命令需要对查询范围内的文档逐个删除,如果希望删除整个集合,则使 用drop命令会更加高效
返回被删除文档
remove、deleteOne等命令在删除文档后只会返回确认性的信息,如果希望获得被删除的文档,则可以 使用findOneAndDelete命令
db.books.findOneAndDelete({type:"novel"})
除了在结果中返回删除文档,findOneAndDelete命令还允许定义“删除的顺序”,即按照指定顺序删除找 到的第一个文档
db.books.findOneAndDelete({type:"novel"},{sort:{favCount:1}})
remove、deleteOne等命令只能按默认顺序删除,利用这个特性,findOneAndDelete可以实现队列的 先进先出。
1、打开navicat
2、点击密码验证
3、填写相关信息
4、成功连接
1、引入依赖
<!--spring data mongodb-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
2.配置yml
spring:
data:
mongodb:
uri: mongodb://root:root@101.34.254.161:27017/appdb?authSource=admin
#uri等同于下面的配置
#database: appdb
#host: 101.34.254.161
#port: 27017
#username: root
#password: root
#authentication-database: admin
连接配置参考文档:https://docs.mongodb.com/manual/reference/connection-string/
3、在测试类注入mongoTemplate
@Autowired
MongoTemplate mongoTemplate;
@Test
void contextLoads() {
boolean exists = mongoTemplate.collectionExists("emp");
// 删除集合
if (exists) {
mongoTemplate.dropCollection("emp");
}
// 创建集合
mongoTemplate.createCollection("emp");
}
相关注解
创建实体
/**
* 对应emp集合中的一个文档
*/
@Document("emp")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
/**
* 映射文档中的_id
*/
@Id
private Integer id;
@Field("username")
private String name;
@Field
private int age;
@Field
private Double salary;
@Field
private Date birthday;
}
添加文档
insert方法返回值是新增的Document对象,里面包含了新增后id的值。如果集合不存在会自动创建集 合。通过Spring Data MongoDB还会给集合中多加一个class的属性,存储新增时Document对应Java中 类的全限定路径。这么做为了查询时能把Document转换为Java类型
添加一条记录:
@Test
void addDocTest(){
Employee employee = new Employee(1,"小明",12,23.23,new Date());
mongoTemplate.insert(employee);
}
查看数据库发现多了一个字段,是为了我们获取的时候可以装换为Java类型
批量添加
@Test
void addDocTest() {
Employee employee = new Employee(1, "小明", 12, 23.23, new Date());
mongoTemplate.insert(employee);
// sava: _id存在时更新数据
//mongoTemplate.save(employee);
// insert: _id存在抛出异常 支持批量操作
List<Employee> list = Arrays.asList(
new Employee(2,"张三",15,232.0,new Date()),
new Employee(3,"李四",35,122.0,new Date()),
new Employee(4,"王五",45,232.0,new Date()),
new Employee(5,"赵六",55,2432.0,new Date()),
new Employee(6,"张七",75,232.0,new Date()),
new Employee(7,"王八",65,2343.0,new Date())
);
mongoTemplate.insert(list,Employee.class);
}
Criteria是标准查询的接口,可以引用静态的Criteria.where的把多个条件组合在一起,就可以轻松地将 多个方法标准和查询连接起来,方便我们操作查询语句。
查询操作
@Test
void testSelect(){
System.out.println("=====查询所有文档=====");
//查询所有文档
List<Employee> employeeList = mongoTemplate.findAll(Employee.class);
employeeList.forEach(System.out::println);
System.out.println("=====根据id查询=====");
// 根据id查询
Employee employee = mongoTemplate.findById(1, Employee.class);
System.out.println(employee);
System.out.println("=====findOne返回第一个文档=====");
// 返回第一个文档
Employee one = mongoTemplate.findOne(new Query(), Employee.class);
System.out.println(one);
System.out.println("=====条件查询=====");
// new Query()表示没有条件
// 查询年龄大于等于12的员工
mongoTemplate.find(new Query(Criteria.where("age").gte(12)),Employee.class).forEach(System.out::println);
// 查询年龄大于12小于50的员工
new Query(Criteria.where("age").gt(12).lt(50));
// 正则查询(模糊查询) java中正则不需要有//
new Query(Criteria.where("name").regex("张"));
// and or 多条件查询
// and 查询年龄大于25且姓名包含张的员工
Criteria criteria = new Criteria();
new Query(criteria.andOperator(Criteria.where("age").gt(25),Criteria.where("name").regex("张")));
// or
new Query(criteria.orOperator(Criteria.where("age").gt(25),Criteria.where("name").regex("张")));
// sort排序
new Query().with(Sort.by(Sort.Order.desc("age")));
// skip limit分页
new Query().with(Sort.by(Sort.Order.desc("salary")))
// 跳过记录数
.skip(0)
// 显示记录数
.limit(4);
}
@Test
void testJson() {
// 使用json字符串方式查询
// 等值查询
// String json = "{name:'张三'}";
// 多条件查询
String json = "{$or:[{age:{$gt:25}},{salary:{$gte:13}}]}";
Query query = new BasicQuery(json);
// 结果
mongoTemplate.find(query,Employee.class).forEach(System.out::println);
}
在Mongodb中无论是使用客户端API还是使用Spring Data,更新返回结果一定是受行数影响。如果更新后的结果和更新前的结果是相同,返回0。
@Test
void testUpdate(){
// query设置查询条件
Query query = new Query(Criteria.where("age").gte(23));
System.out.println("======更新前======");
mongoTemplate.find(query,Employee.class).forEach(System.out::println);
Update update = new Update();
// 设置更新属性
update.set("age",23);
// updateFirst()只更新满足条件的第一条记录
// UpdateResult updateResult = mongoTemplate.updateFirst(query, update, Employee.class);
// updateMulti()更新所有满足
// UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);
//upsert()没有符合条件的记录则插入数据
//update.setOnInsert("id",11); // 如果是插入的话则指定id
UpdateResult updateResult = mongoTemplate.upsert(query, update,
Employee.class);
// 返回修改的记录数
System.out.println(updateResult.getModifiedCount());
System.out.println("======更新后======");
mongoTemplate.find(query,Employee.class).forEach(System.out::println);
}
/**
* 删除文档
*/
@Test
public void testDelete() {
//删除所有文档
//mongoTemplate.remove(new Query(),Employee.class);
//条件删除
Query query = new Query(Criteria.where("salary").gte(10000));
mongoTemplate.remove(query, Employee.class);
}
聚合操作处理数据记录并返回计算结果(诸如统计平均值,求和等)。聚合操作组值来自多个文档,可以对 分组数据执行各种操作以返回单个结果。聚合操作包含三类:单一作用聚合、聚合管道、MapReduce。
MongoDB提供 db.collection.estimatedDocumentCount()
, db.collection.count()
, db.collection.distinct()
这类单一作用的聚合函数。 所有这些操作都聚合来自单个集合的文档。虽然这 些操作提供了对公共聚合过程的简单访问,但它们缺乏聚合管道和map-Reduce的灵活性和功能
函数 | 描述 |
---|---|
db.collection.estimatedDocumentCount() | 忽略查询条件,返回集合或视图中所有文档的计数 |
db.collection.count() | 返回与find()集合或视图的查询匹配的文档计数 。 等同于 db.collection.find(query).count()构造 |
db.collection.distinct() | 在单个集合或视图中查找指定字段的不同值,并在 数组中返回结果。 |
#检索books集合中所有文档的计数
db.books.estimatedDocumentCount()
#计算与查询匹配的所有文档
db.books.count({favCount:{$gt:50}})
#返回不同type的数组
db.books.distinct("type")
#返回收藏数大于90的文档不同type的数组
db.books.distinct("type",{favCount:{$gt:90}})
注意:在分片群集上,如果存在孤立文档或正在进行块迁移,则db.collection.count()没有查询谓词可能 导致计数不准确。要避免这些情况,请在分片群集上使用 db.collection.aggregate()方法
什么是 MongoDB 聚合框架
MongoDB 聚合框架(Aggregation Framework)是一个计算框架,它可以:
从效果而言,聚合框架相当于 SQL 查询中的GROUP BY、 LEFT OUTER JOIN 、 AS等。
管道(Pipeline)和阶段(Stage)
整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的, 每个管道:
聚合管道操作语法
pipeline = [$stage1, $stage2, ...$stageN];
db.collection.aggregate(pipeline, {options})
常用的管道聚合阶段
阶段 | 描述 | SQL等价运算符 |
---|---|---|
$match | 筛选条件 | WHERE |
$project | 投影 | AS |
$lookup | 左外连接 | LEFT OUTER JOIN |
$sort | 排序 | ORDER BY |
$group | 分组 | GROUP BY |
$group | 分页 | |
$unwind | 展开数组 | |
$graphLookup | 图搜索 | |
f a c e t / facet/ facet/bucket | 分面搜索 |
文档:[[Aggregation Pipeline Stages)](Aggregation Pipeline Stages — MongoDB Manual)
数据准备
准备数据集,执行脚本
vim book2.js
进行编写,然后利用load("book2.js")
进行加载
var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){
var typeIdx = Math.floor(Math.random()*types.length);
var tagIdx = Math.floor(Math.random()*tags.length);
var tagIdx2 = Math.floor(Math.random()*tags.length);
var favCount = Math.floor(Math.random()*100);
var username = "xx00"+Math.floor(Math.random()*10);
var age = 20 + Math.floor(Math.random()*15);
var book = {
title: "book-"+i,
type: types[typeIdx],
tag: [tags[tagIdx],tags[tagIdx2]],
favCount: favCount,
author: {name:username,age:age}
};
books.push(book)
}
db.books2.insertMany(books);
$project
投影操作, 将原始字段投影成指定名称, 如将集合中的 title 投影成 name
db.books2.aggregate([{$project:{name:"$title"}}])
$project 可以灵活控制输出文档的格式,也可以剔除不需要的字段
db.books2.aggregate([{$project:{name:"$title",_id:0,type:1,author:1}}])
从嵌套文档中排除字段(author里面又是一个新的文档)
db.books2.aggregate([
{$project:{name:"$title",_id:0,type:1,"author.name":1}}
])
或者
db.books2.aggregate([
{$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])
$match
m a t c h 用于对文档进行筛选,之后可以在得到的文档子集上做聚合, match用于对文档进行筛选,之后可以在得到的文档子集上做聚合, match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,match可以使用除了地理空间之 外的所有常规查询操作符,在实际应用中尽可能将 m a t c h 放在管道的前面位置。这样有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果再投射和分组之前执行 match放在管道的前面位置。这样有两个好处:一是 可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果再投射和分组之前执行 match放在管道的前面位置。这样有两个好处:一是可以快速将不需要的文档过滤掉,以减少管道的工作量;二是如果再投射和分组之前执行match,查询 可以使用索引。
db.books2.aggregate([{$match:{type:"technology"}}])
筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段,这样可以减少后续管道操作符要操作的 文档数,提升效率
$count
计数并返回与查询匹配的结果数
db.books2.aggregate([
{$match:{type:"technology"}}, {$count: "type_count"}
])
$match阶段筛选出type匹配technology的文档,并传到下一阶段;
$count阶段返回聚合管道中剩余文档的计数,并将该值分配给type_count
$group
按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个_id字 段,该字段按键包含不同的组。 输出文档还可以包含计算字段,该字段保存由$group的_id字段分组的一些accumulator表达式的值。 $group不会输出具体的文档而只是统计信息。
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ...
} }
accumulator操作符
名称 | 描述 | 类比sql |
---|---|---|
$avg | 计算均值 | avg |
$first | 返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的 存储的顺序的第一个文档。 | limit 0 ,1 |
$last | 返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认 的存储的顺序的最后个文档。 | |
$max | 根据分组,获取集合中所有文档对应值得最大值 | max |
$min | 根据分组,获取集合中所有文档对应值得最小值 | min |
$push | 将指定的表达式的值添加到一个数组中 | |
$addToSet | 将表达式的值添加到一个集合中(无重复值,无序) | |
$sum | 计算总和 | sum |
$stdDevPop | 返回输入值的总体标准偏差(population standard deviation) | |
$stdDevSamp | 返回输入值的样本标准偏差(the sample standard deviation) |
g r o u p 阶段的内存限制为 100 M 。默认情况下,如果 s t a g e 超过此限制, group阶段的内存限制为100M。默认情况下,如果stage超过此限制, group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group将产生错误。但是,要 允许处理大型数据集,请将allowDiskUse选项设置为true以启用$group操作以写入临时文件。
book的总数,收藏总数和收藏平均值
db.books2.aggregate([
{$group:{_id:null,count:{$sum:1},pop:{$sum:"$favCount"},avg:
{$avg:"$favCount"}}}
])
统计每个作者的book收藏总数
db.books2.aggregate([
{$group:{_id:"$author.name",pop:{$sum:"$favCount"}}}
])
统计每个作者的每本book的收藏数
db.books2.aggregate([
{$group:{_id:{name:"$author.name",title:"$title"},pop:{$sum:"$favCount"}}}
])
每个作者的book的type合集
db.books2.aggregate([
{$group:{_id:"$author.name",types:{$addToSet:"$type"}}}
])
$unwind
可以将数组拆分为单独的文档
v3.2+支持如下语法:
{
$unwind:
{
#要指定字段路径,在字段名称前加上$符并用引号括起来。
path: <field path>,
#可选,一个新字段的名称用于存放元素的数组索引。该名称不能以$开头。
includeArrayIndex: <string>,
#可选,default :false,若为true,如果路径为空,缺少或为空数组,则 $unwind输出文档
preserveNullAndEmptyArrays: <boolean>
} }
姓名为xx006的作者的book的tag数组拆分为多个文档
db.books2.aggregate([
{$match:{"author.name":"xx006"}},
{$unwind:"$tag"}
])
每个作者的book的tag合集
db.books2.aggregate([
{$unwind:"$tag"},
{$group:{_id:"$author.name",types:{$addToSet:"$tag"}}}
])
案列
示例数据
db.books2.insert([
{
"title" : "book-51",
"type" : "technology",
"favCount" : 11,
"tag":[],
"author" : {
"name" : "ll",
"age" : 28
}
},{
"title" : "book-52",
"type" : "technology",
"favCount" : 15,
"author" : {
"name" : "ll",
"age" : 28
}
},{
"title" : "book-53",
"type" : "technology",
"tag" : [
"nosql",
"document"
],
"favCount" : 20,
"author" : {
"name" : "ll",
"age" : 28
}
}])
测试
# 使用includeArrayIndex选项来输出数组元素的数组索引
db.books2.aggregate([
{$match:{"author.name":"ll"}},
{$unwind:{path:"$tag", includeArrayIndex: "arrayIndex"}}
])
发现:只输出tag不为null的数据,并且多了个数组加下标的字段
# 使用preserveNullAndEmptyArrays选项在输出中包含缺少size字段,null或空数组的文档
db.books2.aggregate([
{$match:{"author.name":"ll"}},
{$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}
])
发现:无论是tag是否为null,全部可以进行输出。
$limit
限制传递到管道中下一阶段的文档数
db.books2.aggregate([
{$limit : 5 }
])
此操作仅返回管道传递给它的前5个文档。 $limit对其传递的文档内容没有影响。
注意:当 s o r t 在管道中的 sort在管道中的 sort在管道中的limit之前立即出现时,$sort操作只会在过程中维持前n个结果,其中n是指 定的限制,而MongoDB只需要将n个项存储在内存中。
$skip
跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段
db.books2.aggregate([
{$skip : 5 }
])
$sort
对所有输入文档进行排序,并按排序顺序将它们返回到管道
语法:
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
要对字段进行排序,请将排序顺序设置为1或-1,以分别指定升序或降序排序,如下例所示:
db.books.aggregate([
{$sort : {favCount:-1,title:1}}
])
$lookup
Mongodb 3.2版本新增,主要用来实现多表关联查询, 相当关系型数据库中多表关联查询。每个输入 待处理的文档,经过$lookup 阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名 新key )。数组列存放的数据是来自被Join集合的适配文档,如果没有,集合为空(即 为[ ])
语法:
db.collection.aggregate([{
$lookup: {
from: "<collection to join>",
localField: "<field from the input documents>",
foreignField: "<field from the documents of the from collection>",
as: "<output array field>"
}
})
属性 | 作用 |
---|---|
from | 同一个数据库下等待被Join的集合 |
localField | 源集合中的match值,如果输入的集合中,某文档没有 localField这个Key (Field),在处理的过程中,会默认为此文档含有 localField:null的键值对 |
foreignField | 待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处 理的过程中,会默认为此文档含有 foreignField:null的键值对 |
as | 为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉 |
注意:null = null 此为真
其语法功能类似于下面的伪SQL语句
SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
FROM <collection to join>
WHERE <foreignField>= <collection.localField>);
案例
数据准备
db.customer.insert({customerCode:1,name:"customer1",phone:"13112345678",address:
"test1"})
db.customer.insert({customerCode:2,name:"customer2",phone:"13112345679",address:
"test2"})
db.order.insert({orderId:1,orderCode:"order001",customerCode:1,price:200})
db.order.insert({orderId:2,orderCode:"order002",customerCode:2,price:400})
db.orderItem.insert({itemId:1,productName:"apples",qutity:2,orderId:1})
db.orderItem.insert({itemId:2,productName:"oranges",qutity:2,orderId:1})
db.orderItem.insert({itemId:3,productName:"mangoes",qutity:2,orderId:1})
db.orderItem.insert({itemId:4,productName:"apples",qutity:2,orderId:2})
db.orderItem.insert({itemId:5,productName:"oranges",qutity:2,orderId:2})
db.orderItem.insert({itemId:6,productName:"mangoes",qutity:2,orderId:2})
关联查询(customer与order)
db.customer.aggregate([
{$lookup: {
from: "order", #关联的表
localField: "customerCode", #自己的关联表字段
foreignField: "customerCode", # 关联的表的表字段
as: "customerOrder" #别名
}
}
])
关联查询(order与orderItem与customer)
db.order.aggregate([
{$lookup: {
from: "customer",
localField: "customerCode",
foreignField: "customerCode",
as: "curstomer"
}
},
{$lookup: {
from: "orderItem",
localField: "orderId",
foreignField: "orderId",
as: "orderItem"
}
}
])
聚合操作示例1
统计每个分类的book文档数量
db.books.aggregate([
{$group:{_id:"$type",total:{$sum:1}}},
{$sort:{total:-1}}
])
标签的热度排行,标签的热度则按其关联book文档的收藏数(favCount)来计算
db.books.aggregate([
{$match:{favCount:{$gt:0}}},
{$unwind:"$tag"},
{$group:{_id:"$tag",total: {$sum:"$favCount"}}},
{$sort:{total:-1}}
])
聚合操作示例二
导入邮政编码数据集:https://media.mongodb.org/zips.json
复制下来,放入记事本并重命名为zips.json即可。
使用mongoimport工具导入数据[[Download MongoDB Tools](Download MongoDB Command Line Database Tools | MongoDB)
下载完成后,打开bin目录进行cmd
mongoimport -h 101.34.254.161 -d appdb -u root -p root --authenticationDatabase=admin -c zips --file C:\Users\YLi_Jing\Desktop\zips.json
h,–host :代表远程连接的数据库地址,默认连接本地Mongo数据库;
-port : 远程端口,默认为27017
-u,–username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号
-p,–password:代表连接数据库的账号对应的密码
-d,–db:代表连接的数据库;
-c,–collection:代表连接数据库中的集合
-f, --fields:代表导入集合中的字段
–type : 代表导入的文件类型,包括csv和json,tsv文件,默认json格式
–file:导入的文件名称
–headerline:导入csv文件时,指明第一行是列名,不需要导入
数据成功导入。
返回人口超过1000万的州
db.zips.aggregate( [
{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
{ $match: { totalPop: { $gt: 10*1000*1000 } } }
] )
这个聚合操作的等价SQL是:
SELECT state, SUM(pop) AS totalPop
FROM zips
GROUP BY state
HAVING totalPop >= (10*1000*1000)
返回各州平均城市人口
db.zips.aggregate( [
{ $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" }
} },
{ $group: { _id: "$_id.state", avgCityPop: { $avg: "$cityPop" } } },
{ $sort:{avgCityPop:-1}}
] )
按州返回最大和最小的城市
db.zips.aggregate( [
{ $group:
{
_id: { state: "$state", city: "$city" },
pop: { $sum: "$pop" }
}
},
{ $sort: { pop: 1 } },
{ $group:
{
_id : "$_id.state",
biggestCity: { $last: "$_id.city" },
biggestPop: { $last: "$pop" },
smallestCity: { $first: "$_id.city" },
smallestPop: { $first: "$pop" }
}
},
{ $project:
{
_id: 0,
state: "$_id",
biggestCity: { name: "$biggestCity", pop: "$biggestPop" },
smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
}
},
{ $sort: { state: 1 } }
] )
MapReduce操作将大量的数据处理工作拆分成多个线程并行处理,然后将结果合并在一起。MongoDB 提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。
MapReduce具有两个阶段:
MapReduce的基本语法:
db.collection.mapReduce(
function() {emit(key,value);}, //map 函数
function(key,values) {return reduceFunction}, //reduce 函数
{
out: <collection>,
query: <document>,
sort: <document>,
limit: <number>,
finalize: <function>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>,
bypassDocumentValidation: <boolean>
}
)
统计type为travel的不同作者的book文档收藏数**
db.books.mapReduce(
function(){emit(this.author.name,this.favCount)},
function(key,values){return Array.sum(values)},
{
query:{type:"travel"},
out: "books_favCount"
}
)
支持的操作 | java接口 | 说明 |
---|---|---|
$project | Aggregation.project | 修改输入文档的结构 |
$match | Aggregation.match | 过滤数据 |
$limit | Aggregation.limit | 用来限制MongoDB聚合管道返回的文档数 |
$skip | Aggregation.skip | 跳过指定的文档 |
$unwind | Aggregation.unwind | 将文档中的某一个数组类型字段拆分成多条 |
$group | Aggregation.group | 分组,用于统计结果 |
$sort | Aggregation.sort | 将输入文档排序后输出 |
$geoNear | Aggregation.geoNear | 输出接近某一地理位置的有序文档 |
基于聚合操作Aggregation.group,mongodb提供的可选表达式
聚合表达式 | java接口 | 说明 |
---|---|---|
$sum | Aggregation.group().sum(“field”).as(“sum”) | 求和 |
$avg | Aggregation.group().avg(“field”).as(“avg”) | 求平均 |
$min | Aggregation.group().min(“field”).as(“min”) | 最小值 |
$max | Aggregation.group().max(“field”).as(“max”) | 最大值 |
$push | Aggregation.group().push(“field”).as(“push”) | 在结果文档中插入值到一个数组中 |
$addToSet | Aggregation.group().addToSet(“field”).as(“addToSet”) | 在结果文档中插入值到一个数组中,但不创建副本 |
$first | Aggregation.group().first(“field”).as(“first”) | 根据资源文档的排序获取第一个文档数据 |
$last | Aggregation.group().last(“field”).as(“last”) | 根据资源文档的排序获取最后一个文档数据 |
示例:以聚合管道示例2为例
实体结构
@Document("zips")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Zips {
/**
* 映射文档中的_id
*/
@Id
private String id;
@Field
private String city;
@Field
private Double[] loc;
private Integer pop;
@Field
private String state;
}
返回人口超过1000万的州
db.zips.aggregate( [
{ $group: { _id: "$state", totalPop: { $sum: "$pop" } } },
{ $match: { totalPop: { $gt: 10*1000*1000 } } }
] )
java实现
@Test
void test1(){
// $group
GroupOperation group = Aggregation.group("state")
.sum("pop")
.as("totalPop");
// $match
MatchOperation match = Aggregation
.match(Criteria.where("totalPop")
.gt(10*1000*1000));
// 按顺序组合每一个聚合步骤
TypedAggregation<Zips> zipsTypedAggregation = Aggregation.newAggregation(Zips.class, group, match);
// 执行聚合操作
AggregationResults<Map> aggregate = mongoTemplate.aggregate(zipsTypedAggregation, Map.class);
// 取出最终结果
List<Map> mappedResults = aggregate.getMappedResults();
for (Map mappedResult : mappedResults) {
System.out.println(mappedResult);
}
}
返回各州平均城市人口
db.zips.aggregate( [
{ $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" }
} },
{ $group: { _id: "$_id.state", avgCityPop: { $avg: "$cityPop" } } },
{ $sort:{avgCityPop:-1}}
] )
java实现
@Test
void test2() {
//$group
GroupOperation groupOperation = Aggregation.group("state","city")
.sum("pop").as("cityPop");
//$group
GroupOperation groupOperation2 = Aggregation.group("_id.state")
.avg("cityPop").as("avgCityPop");
//$sort
SortOperation sortOperation = Aggregation.sort(Sort.Direction.DESC,"avgCityPop");
// 按顺序组合每一个聚合步骤
TypedAggregation<Zips> typedAggregation = Aggregation.newAggregation(Zips.class,
groupOperation, groupOperation2,sortOperation);
//执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据
AggregationResults<Map> aggregationResults = mongoTemplate
.aggregate(typedAggregation, Map.class);
// 取出最终结果
List<Map> mappedResults = aggregationResults.getMappedResults();
for(Map map:mappedResults){
System.out.println(map);
}
}
按州返回最大和最小的城市
db.zips.aggregate( [
{ $group:
{
_id: { state: "$state", city: "$city" },
pop: { $sum: "$pop" }
}
},
{ $sort: { pop: 1 } },
{ $group:
{
_id : "$_id.state",
biggestCity: { $last: "$_id.city" },
biggestPop: { $last: "$pop" },
smallestCity: { $first: "$_id.city" },
smallestPop: { $first: "$pop" }
}
},
{ $project:
{
_id: 0,
state: "$_id",
biggestCity: { name: "$biggestCity", pop: "$biggestPop" },
smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
}
},
{ $sort: { state: 1 } }
] )
java实现
@Test
void test3() {
//$group
GroupOperation groupOperation = Aggregation.group("state", "city")
.sum("pop").as("pop");
//$sort
SortOperation sortOperation = Aggregation.sort(Sort.Direction.ASC, "pop");
//$group
GroupOperation groupOperation2 = Aggregation.group("_id.state")
.last("_id.city").as("biggestCity")
.last("pop").as("biggestPop")
.first("_id.city").as("smallestCity")
.first("pop").as("smallestPop");
//$project
ProjectionOperation projectionOperation = Aggregation
.project("biggestCity", "smallestCity", "state")
.andExclude("_id")
.andExpression(" { name: \"$biggestCity\", pop: \"$biggestPop\" }")
.as("biggestCity")
.andExpression("{ name: \"$smallestCity\", pop: \"$smallestPop\" }")
.as("smallestCity")
.and("_id").as("state");
//$sort
SortOperation sortOperation2 = Aggregation.sort(Sort.Direction.ASC, "state");
// 按顺序组合每一个聚合步骤
TypedAggregation<Zips> typedAggregation = Aggregation.newAggregation(Zips.class,
groupOperation, sortOperation, groupOperation2, projectionOperation,
sortOperation2);
//执行聚合操作,如果不使用 Map,也可以使用自定义的实体类来接收数据
AggregationResults<Map> aggregationResults = mongoTemplate
.aggregate(typedAggregation, Map.class);
// 取出最终结果
List<Map> mappedResults = aggregationResults.getMappedResults();
for (Map map : mappedResults) {
System.out.println(map);
}
}
索引是一种用来快速查询数据的数据结构。B+Tree就是一种常用的数据库索引数据结构,MongoDB 采用B+Tree 做索引,索引创建在colletions上。MongoDB不使用索引的查询,先扫描所有的文档,再 匹配符合条件的文档。 使用索引的查询,通过索引找到文档,使用索引能够极大的提升查询效率
MongoDB索引数据结构
思考:MongoDB索引数据结构是B-Tree还是B+Tree?
B-Tree说法来源于官方文档,然后就导致了分歧:有人说MongoDB索引数据结构使用的是B-Tree,有的 人又说是B+Tree。
MongoDB官方文档: https://docs.mongodb.com/manual/indexes/
索引的分类
与大多数数据库一样,MongoDB支持各种丰富的索引类型,包括单键索引、复合索引,唯一索引等一 些常用的结构。由于采用了灵活可变的文档类型,因此它也同样支持对嵌套字段、数组进行索引。通过 建立合适的索引,我们可以极大地提升数据的检索速度。在一些特殊应用场景,MongoDB还支持地理 空间索引、文本检索索引、TTL索引等不同的特性
创建索引
db.collection.createIndex(keys, options)
parameter | type | description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background可 指定以后台方式创建索引,即增加 “background” 可 选参数。 “background” 默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默 认值为false |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索 引的字段名和排序顺序生成一个索引名称 |
dropDups | Boolean | 3.0+版本已废弃。在建立唯一索引时是否删除重复记 录,指定 true 创建唯一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数需 要特别注意,如果设置为true的话,在索引字段中不 会查询出不包含对应字段的文档.。默认值为 false |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,完成 TTL设定,设定集 合的生存时间 |
v | index version | 索引的版本号。默认的索引版本取决于mongod创建 索引时运行的版本 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,表示该索引 相对于其他索引字段的得分权重 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的 规则的列表。 默认为英语 |
language_override | string | 对于文本索引,该参数决定了停用词及词干和词器的 规则的列表。 默认为英语 |
注意:3.0.0 版本前创建索引方法为 db.collection.ensureIndex()
# 创建索引后台执行
db.values.createIndex({open: 1, close: 1}, {background: true})
# 创建唯一索引
db.values.createIndex({title:1},{unique:true})
查看索引占用空间
db.collection.totalIndexSize([is_detail])
删除索引
#删除集合指定索引
db.col.dropIndex("索引名称")
#删除集合所有索引
db.col.dropIndexes()
单键索引(Single Field Indexes)
在某一个特定的字段上建立索引 mongoDB在ID上建立了唯一的单键索引,所以经常会使用id来进行查 询; 在索引字段上进行精确匹配、排序以及范围查找都会使用此索引
db.books.createIndex({title:1})
对内嵌文档创建索引:
复合索引(Compound Index)
复合索引是多个字段组合而成的索引,其性质和单字段索引类似。但不同的是,复合索引中字段的顺 序、字段的升降序对查询性能有直接的影响,因此在设计复合索引时则需要考虑不同的查询场景
db.books.createIndex({type:1,favCount:1})
多键索引(Multikey Index)
在数组的属性上建立索引。针对这个数组的任意值的查询都会定位到这个文档,既多个索引入口或者键值 引用同一个文档
准备inventory集合:
db.inventory.insertMany([
{ _id: 5, type: "food", item: "aaa", ratings: [ 5, 8, 9 ] },
{ _id: 6, type: "food", item: "bbb", ratings: [ 5, 9 ] },
{ _id: 7, type: "food", item: "ccc", ratings: [ 9, 5, 8 ] },
{ _id: 8, type: "food", item: "ddd", ratings: [ 9, 5 ] },
{ _id: 9, type: "food", item: "eee", ratings: [ 5, 9, 5 ] }
])
创建多键索引
db.inventory.createIndex( { ratings: 1 } )
多键索引很容易与复合索引产生混淆,复合索引是多个字段的组合,而多键索引则仅仅是在一个字段上 出现了多键(multi key)。而实质上,多键索引也可以出现在复合字段上
# 创建复合多键索引
db.inventory.createIndex( { item:1,ratings: 1} )
注意: MongoDB并不支持一个复合索引中同时出现多个数组字段
嵌入文档的索引数组
db.inventory.insertMany([
{
_id: 1,
item: "abc",
stock: [
{ size: "S", color: "red", quantity: 25 },
{ size: "S", color: "blue", quantity: 10 },
{ size: "M", color: "blue", quantity: 50 }
]
},
{
_id: 2,
item: "def",
stock: [
{ size: "S", color: "blue", quantity: 20 },
{ size: "M", color: "blue", quantity: 5 },
{ size: "M", color: "black", quantity: 10 },
{ size: "L", color: "red", quantity: 2 }
]
},
{
_id: 3,
item: "ijk",
stock: [
{ size: "M", color: "blue", quantity: 15 },
{ size: "L", color: "blue", quantity: 100 },
{ size: "L", color: "red", quantity: 25 }
]
}
])
在包含嵌套对象的数组字段上创建多键索引
db.inventory.createIndex( { "stock.size": 1, "stock.quantity": 1 } )
地理空间索引(Geospatial Index)
在移动互联网时代,基于地理位置的检索(LBS)功能几乎是所有应用系统的标配。MongoDB为地理空 间检索提供了非常方便的功能。地理空间索引(2dsphereindex)就是专门用于实现位置检索的一种特 殊索引。
案例:MongoDB如何实现“查询附近商家"?
假设商家的数据模型如下:
db.restaurant.insert({
restaurantId: 0,
restaurantName:"兰州牛肉面",
location : {
type: "Point",
coordinates: [ -73.97, 40.77 ]
}
})
创建一个2dsphere索引
db.restaurant.createIndex({location : "2dsphere"})
查询附近10000米商家信息
db.restaurant.find( {
location:{
$near :{
$geometry :{
type : "Point" ,
coordinates : [ -73.88, 40.78 ]
} ,
$maxDistance:10000
}
}
} )
全文索引(Text Indexes)
MongoDB支持全文检索功能,可通过建立文本索引来实现简易的分词检索
db.reviews.createIndex( { comments: "text" } )
t e x t 操作符可以在有 t e x t i n d e x 的集合上执行文本检索。 text操作符可以在有text index的集合上执行文本检索。 text操作符可以在有textindex的集合上执行文本检索。text将会使用空格和标点符号作为分隔符对检 索字符串进行分词, 并且对检索字符串中所有的分词结果进行一个逻辑上的 OR 操作。
全文索引能解决快速文本查找的需求,比如有一个博客文章集合,需要根据博客的内容来快速查找,则 可以针对博客内容建立文本索引。
案例
数据准备
db.stores.insert(
[
{ _id: 1, name: "Java Hut", description: "Coffee and cakes" },
{ _id: 2, name: "Burger Buns", description: "Gourmet hamburgers" },
{ _id: 3, name: "Coffee Shop", description: "Just coffee" },
{ _id: 4, name: "Clothes Clothes Clothes", description: "Discount clothing"
},
{ _id: 5, name: "Java Shopping", description: "Indonesian goods" }
]
)
创建name和description的全文索引
db.stores.createIndex({name: "text", description: "text"})
测试
通过$text操作符来查寻数据中所有包含“coffee”,”shop”,“java”列表中任何词语的商店
db.stores.find({$text: {$search: "java coffee shop"}})
MongoDB的文本索引功能存在诸多限制,而官方并未提供中文分词的功能,这使得该功能的应用场景 十分受限
Hash索引(Hashed Indexes)
不同于传统的B-Tree索引,哈希索引使用hash函数来创建索引。在索引字段上进行精确匹配,但不支持范 围查询,不支持多键hash; Hash索引上的入口是均匀分布的,在分片集合中非常有用
db.users. createIndex({username : 'hashed'})
通配符索引(Wildcard Indexes)
MongoDB的文档模式是动态变化的,而通配符索引可以建立在一些不可预知的字段上,以此实现查询 的加速。MongoDB 4.2 引入了通配符索引来支持对未知或任意字段的查询
案例
准备商品数据,不同商品属性不一样
db.products.insert([
{
"product_name" : "Spy Coat",
"product_attributes" : {
"material" : [ "Tweed", "Wool", "Leather" ],
"size" : {
"length" : 72,
"units" : "inches"
}
}
},
{
"product_name" : "Spy Pen",
"product_attributes" : {
"colors" : [ "Blue", "Black" ],
"secret_feature" : {
"name" : "laser",
"power" : "1000",
"units" : "watts",
}
}
},
{
"product_name" : "Spy Book"
}
])
创建通配符索引
db.products.createIndex( { "product_attributes.$**" : 1,"product_name":1 } )
测试
通配符索引可以支持任意单字段查询 product_attributes或其嵌入字段:
db.products.find( { "product_attributes.size.length" : { $gt : 60 } } )
db.products.find( { "product_attributes.material" : "Leather" } )
db.products.find( { "product_attributes.secret_feature.name" : "laser" } )
注意事项
# 通配符索引不能支持以下查询
db.products.find( {"product_attributes" : { $exists : false } } )
db.products.aggregate([
{ $match : { "product_attributes" : { $exists : false } } }
])
#通配符索引不能支持以下查询:
db.products.find({ "product_attributes.colors" : [ "Blue", "Black" ] } )
db.products.aggregate([{
$match : { "product_attributes.colors" : [ "Blue", "Black" ] }
}])
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。