MongoDB
1. 简介
- MongoDB是为快速开发互联网Web应用而设计的数据库系统。
- 设计目标是极简、灵活、作为Web应用栈的一部分。
- 数据模型面向文档。所谓的文档是一种类似于JSON的结构,简单理解MongoDB这个数据库中存的是各种各样的JSON。(BSON -> 简单理解为二进制的JSON)
场景:社交、游戏、物流、物联网、视频直播等。
2. 概念
- 数据库(Database)
- 数据库是一个仓库,在仓库中可以存放集合。
- 集合(Collection)
- 集合类似于数组,在集合中可以存放文档。
- 文档(Document)
- 文档数据库中的最小单位,存储和操作的单位。
SQL与MongoDB概念对比:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
3. 安装
MongoDB的版本偶数版本为稳定版,奇数版本为开发版。对32位系统支持不佳,3.2版本后不支持。
默认端口:27017
mongod
启动MongoDB服务器
mongo
连接服务器
mongo.conf配置文件:
systemLog:
destination: file
#The path of the log file to which mongod or mongos should send all diagnostic logging information
path: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/log/mongod.log"
logAppend: true
storage:
journal:
enabled: true
#The directory where the mongod instance stores its data.Default Value is "/data/db".
dbPath: "D:/02_Server/DBServer/mongodb-win32-x86_64-2008plus-ssl-4.0.1/data"
net:
#bindIp: 127.0.0.1
port: 27017
setParameter:
enableLocalhostAuthBypass: false
减少内存占用
storage:
wiredTiger:
engineConfig:
cacheSizeGB: <number>
3.1. Docker安装
docker pull mongo
# --auth 需要密码才能登陆
docker run -itd --name mongo -p 27017:27017 mongo --auth
docker exec -it mongo mongo admin
# 创建一个名为 admin,密码为 123456 的用户。
> db.createUser({ user:'admin',pwd:'123456',roles:[ { role:'userAdminAnyDatabase', db: 'admin'},"readWriteAnyDatabase"]});
# 尝试使用上面创建的用户信息进行连接。
> db.auth('admin', '123456')
3.2. Windows
安装后配置环境变量:
path += ;mongodb下的\bin;
在根目录创建data/db文件夹 打开两个窗口分别输入mongo和mongod 测试网址:http://127.0.0.1:27017 出现“It looks like you are trying to access MongoDB over HTTP on the native driver port.”即连接成功。
3.3. Linux
下载压缩包后解压,在软件根目录下创建data/db
和log
文件夹,新建配置文件mongod.conf
,自行配置。
# 连接远程数据库服务器
mongo --host=<ip> --port=<port>
# 指定数据路径和端口启动数据库服务器
mongod --dbpath ./db --port 1234
4. 数据库(Database)
数据库也通过名字来标识。数据库名可以是满足以下条件的任意UTF-8字符串。
- 不能是空字符串("")。
- 不得含有' '(空格)、.、$、/、\和\0 (空字符)。
- 应全部小写。
- 最多64字节。
有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
- admin: 从权限的角度来看,这是"root"数据库。要是将一个用户添加到这个数据库,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。
- local: 这个数据永远不会被复制,可以用来存储限于本地单台服务器的任意集合。
- config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
# 列出所有数据库
show dbs;
show databases;
# 查看当前使用的数据库
db;
# 切换数据库
use <db_name>;
# 删除数据库
db.dropDatabase();
在MongoDB中,数据库和集合都不需要手动创建,需要创建文档时,如果文档所在的集合或者数据库不存在,则会自动创建数据库和集合。
5. 集合(Collection)
# 创建集合
db.createCollection("myCollection");
# 查看集合
show collections;
show tables;
# 删除集合
db.<集合名>.drop();
也可以隐式创建,在向一个集合中插入文档的时候,如果集合不存在,则会自动创建集合。
6. 文档(Document)
MongoDB 的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型。文档采用BSON格式进行存储。文档的数据结构和JSON基本一样。
主键:MongoDB提供了key为_id,字段类型时OBjectId或String,是Mongo主键的字段。
6.1. 基本的CRUD
插入文档
# 单个文档插入,使用insert或者save函数
db.collection.insert(
<document or array of documents>,
{
writeContern: <document>,
ordered: <boolean>
}
);
db.collection.save(
<document or array of documents>,
{
writeContern: <document>,
ordered: <boolean>
}
);
# 多个文档插入,使用insertMany函数
db.collection.insertMany(
[<document 1>, <document 2>, ...],
{
writeConcern: <document>,
ordered: <boolean>
}
)
# 批量插入时,如果某条数据插入失败,就会终止插入,但是已经插入的数据不会回滚。
# 可以使用try catch进行异常捕获处理。
try {
db.collection.insertMany(...);
} catch(e) {
print(e)
}
Parameter | Type | Description |
---|---|---|
document | document or array | 要插入到集合中的文档或文档数组。(JSON格式) |
writeConcern | document | 可选。性能级别,可靠级别。 |
ordered | boolean | 可选。如果为真,则按照顺序插入数组中的文档。如果其中一个文档出现错误,MongoDB将返回而不处理数组中的剩余文档。如果为假,则执行无序插入,如果其中一个文档出现错误,则继续处理数组中的主文档。在2.6+的版本中默认为true。 |
> db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null})
# 执行结果
WriteResult({ "nInserted" : 1 })
> db.comment.insertMany([{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}]);
{
"acknowledged" : true,
"insertedIds" : [
"1",
"2",
"3",
"4",
"5"
]
}
> db.comment.find()
{ "_id" : ObjectId("62e4f51c45db5692297ebcd7"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2022-07-30T09:08:44.036Z"), "likenum" : 10, "state" : null }
{ "_id" : "1", "articleid" : "100001", "content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。", "userid" : "1002", "nickname" : "相忘于江湖", "createdatetime" : ISODate("2019-08-05T22:08:15.522Z"), "likenum" : 1000, "state" : "1" }
{ "_id" : "2", "articleid" : "100001", "content" : "我夏天空腹喝凉开水,冬天喝温开水", "userid" : "1005", "nickname" : "伊人憔悴", "createdatetime" : ISODate("2019-08-05T23:58:51.485Z"), "likenum" : 888, "state" : "1" }
{ "_id" : "3", "articleid" : "100001", "content" : "我一直喝凉开水,冬天夏天都喝。", "userid" : "1004", "nickname" : "杰克船长", "createdatetime" : ISODate("2019-08-06T01:05:06.321Z"), "likenum" : 666, "state" : "1" }
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }
查询文档
# 单个文档的查询
db.collection.findOne(<query>, [projection])
# 多个文档的查询
db.collection.find(<query>, [projection])
Parameter | Type | Description |
---|---|---|
query | document | 可选。使用查询运算符指定选择筛选器。若要返回集合中的所有文档,忽略此参数或传递空文档({})。 |
projection | document | 可选。指定要在与查询筛选器匹配的文档中的返回的字段(投影)。若要返回匹配文档中的所有字段,忽略此参数。 |
> db.comment.find()
{ "_id" : ObjectId("62e4f51c45db5692297ebcd7"), "articleid" : "100000", "content" : "今天天气真好,阳光明媚", "userid" : "1001", "nickname" : "Rose", "createdatetime" : ISODate("2022-07-30T09:08:44.036Z"), "likenum" : 10, "state" : null }
> db.comment.find({userid:"1003"})
{ "_id" : "4", "articleid" : "100001", "content" : "专家说不能空腹吃饭,影响健康。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T08:18:35.288Z"), "likenum" : 2000, "state" : "1" }
{ "_id" : "5", "articleid" : "100001", "content" : "研究表明,刚烧开的水千万不能喝,因为烫嘴。", "userid" : "1003", "nickname" : "凯撒", "createdatetime" : ISODate("2019-08-06T11:01:02.521Z"), "likenum" : 3000, "state" : "1" }
> db.comment.findOne({articleid: "100001"});
{
"_id" : "1",
"articleid" : "100001",
"content" : "我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。",
"userid" : "1002",
"nickname" : "相忘于江湖",
"createdatetime" : ISODate("2019-08-05T22:08:15.522Z"),
"likenum" : 1000,
"state" : "1"
}
> db.comment.findOne({articleid: "100001"}, {articleid: 1});
{ "_id" : "1", "articleid" : "100001" }
# 不显示_id字段(一定要赋值为0)
> db.comment.findOne({articleid: "100001"}, {articleid: 1, _id: 0});
{ "articleid" : "100001" }
更新文档
db.collection.update(query, update, options);
# 或者
db.collection.update(
<query>,
<update>,
{
upsert: <boolean>,
multi: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [<filterdocument1>, ...],
hind: <document | string>
}
)
Parameter | Type | Description |
---|---|---|
query | document | 更新的选择条件。可以使用与find()方法相同的查询选择器,类似sql update查询内加入where后面的片段。在3.0版本中进行了更改:当使用upsert: true 执行update()时,如果查询使用点表示法在_id 字段上指定条件,则MongoDB将拒绝插入新文档。 |
update | document or pipeline | 要应用的修改。该值可以是:包含更新运算符表达式的文档,或仅包含<field1>: <value1> 对的替换文档,或在MongoDB 4.2中启动聚合管道。管道可以由以下阶段组成:$addFields 及其别名$set$project 及其别名$unset$replaceroot 及其别名$replaceWith 。换句话说:它是update的对象和一些更新的操作符(如$ , $inc … )等,也可以理解为sql update查询内容set后面的值。 |
upset | boolean | 可选。如果设置为true,则在没有与查询条件匹配的文档时创建新文档。默认值为false,如果找不到匹配项,则不会插入新文档。 |
multi | boolean | 可选。如果设置为true,则更新符合查询条件的多个文档。如果设置为false,则更新一个文档。默认值为false。 |
writeConcern | document | 可选。表示写问题的文档。抛出异常的级别。 |
collation | document | 可选。指定要用于操作的校对规则。校对规则允许用户为字符串比较指定特定于语言的规则,例如字母大小写和重音标记的规则。校对规则由以下语法: 校对规则: {区域设置: , caseLevel: , caseFirst: , 强度: , numericordering: , 替代: , 最大变量: , 向后: }. 指定校对规则时,区域设置字段是必须的;所有其他校对规则的字段都是可选的。如果未指定校对规则,但集合具有默认校对规则(参见db.createCollection()),则该操作将使用集合指定的校对规则。如果没有为集合或操作指定校对规则,MongoDB将使用以前版本中使用的简单二进制进行字符串比较。不能为一个操作指定多个校对规则。例如,不能为每个字段指定不同的校对规则,或者如果使用排序执行查找,则不能将一个校对规则用于查找,另一个校对规则用于排序。3.4版本新增。 |
arrayFilters | array | 可选。一个筛选文档的数组,用于确定要为数组字段上的更新操作修改哪些数组元素。在更新文档中,使用$[] 筛选的位置运算符来定义标识符,然后在数组过滤器文档中引用。如果标识符未包含在更新文档中,则不能由标识符的数组筛选器文档。也就是说,不能为同一标识符指定多个数组筛选器文档。3.6版新增。 |
hint | document or string | 可选。指定用于支持查询谓词的索引的文档或字符串。如果指定的索引不存在,则说明操作错误。 |
# 覆盖更新
# 将_id为1的记录,likenum改为1001,但是其他的字段都会不见
db.comment.update({_id: "1"}, {likenum: NumberInt(1001)});
# 局部修改
db.comment.update({_id: "2"}, {$set: {likenum: NumberInt(889)}});
# 批量修改
# 默认只会修改第一条数据
db.comment.update({userid: "1003"}, {$set: {nickname: "凯撒2"}});
# 修改所有符合条件的数据
db.comment.update({userid: "1003"}, {$set: {nickname: "凯撒2"}}, {multi: true});
# 列值增长的修改
# 如果要实现对某列值在原有值的基础上进行增加或减少,可以使用$inc运算符来实现
db.comment.update({_id: "3"}, {$inc: {nicknum: NumberInt(1)}});
# 无条件更新某个字段
db.comment.update({ }, { '$set': { a: 'b' } }, false, true);
# 批量修改字段名
db.comment.update({查询条件},{$rename:{field:'field2'}},{multi:true});
# 批量增加字段并赋予初始值
db.comment.updateMany({查询条件},{$set:{field:1}})
删除文档
db.collection.remove(<条件>);
# 删除全部的文档
db.collection.remove({});
# 删除id为1的记录
db.collection.remove({_id: "1"});
6.2. 分页查询
统计查询
db.collection.count(query, options)
Parameter | Type | Description |
---|---|---|
query | document | 查询选择条件。 |
options | document | 可选。用于修改计数的额外选项。 |
# 统计comment集合下所有记录数量
db.comment.count();
# 按照条件统计记录
db.comment.count({userid: "1002"});
分页列表查询
db.collection.find().limit(NUMBER).skip((NUMBER));
使用limit方法来读取指定数量的数据,使用skip()方法来跳过指定数量的数据。limit的默认值为20,skip的默认值为0.
排序查询
db.collection.find().sort({KEY: 1});
# 或
db.collection.find().sort(排序方式);
使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的顺序,并使用1和-1来指定排序的方式,1为升序排序,-1为降序排序。
# 对userid降序排序,并对likenum进行升序排序
db.comment.find().sort({userid: -1, likenum: 1})
当skip(), limit(), sort() 三个命令一起执行的时候,执行顺序是sort(), skip(), limit(),和命令的编写顺序没有关系。
6.3. 更多查询
正则的复杂条件查询
MongoDB的模糊查询是通过正则表达式的方式实现的。
db.collection.find({field: /正则表达式/});
其中,正则表达式是js的语法,直接量的写法。
# 查询评论内容包含"开水"的所有文档
db.comment.find({content: /开水/});
# 查询评论内容以"专家"开头的文档
db.comment.find({content: /^专家/})
比较查询
# >
db.collection.find({field: {$gt: value}});
# <
db.collection.find({field: {$lt: value}});
# >=
db.collection.find({field: {$gte: value}});
# <=
db.collection.find({field: {$lte: value}});
# !=
db.collection.find({field: {$ne: value}});
# 查询点赞数量大于700的记录
db.comment.find({likenum: {$gt: NumberInt(700)}});
包含查询
# in
db.collection.find({field: {$in: [value1, value2]}});
# not in
db.collection.find({field: {$nin: [value1, value2]}});
# 查询评论集合中userid字段包含1003或1004的文档
db.comment.find({userid: {$in: ["1003", "1004"]}});
条件连接查询
# $and: [{}, {}, {}]
db.collection.find({$and: [{}, {}, {}]});
# $or: [{}, {}, {}]
db.collection.find({$or: [{}, {}, {}]});
# 查询评论集合中likenum>=700且小于2000的文档
db.comment.find({$and: [{likenum: {$gte: NumberInt(700)}}, {likenum: {$lt: NumberInt(2000)}}]});
$type操作符
$type操作符是基于BSON类型来检索集合中匹配的数据类型,并返回结果。
db.document.find({"title": {$type: 2}});
db.document.find({"title": {$type: "string"}});
类型 | 数字 | 备注 |
---|---|---|
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 |
聚合
MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。
db.collection.aggregate(AGGREGATE_OPERATION)
表达式 | 描述 | 实例 |
---|---|---|
$sum | 计算总和。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : "$likes"}}}]) |
$avg | 计算平均值 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$avg : "$likes"}}}]) |
$min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}]) |
$max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$max : "$likes"}}}]) |
$push | 将值加入一个数组中,不会判断是否有重复的值。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$push: "$url"}}}]) |
$addToSet | 将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。 | db.mycol.aggregate([{$group : {_id : "$by_user", url : {$addToSet : "$url"}}}]) |
$first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}]) |
$last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}]) |
$lookup | 联表查询 | { $lookup: { from: localField: foreignField: as: |
6.4. 注意点
- 数据库中的数字默认存储double类型,如果要存储整型数据,需要使用函数
NumberInt(val)
。 - 插入当前日期使用
new Date()
。 - 插入数据的时候如果没有指定
_id
,则会自动生成主键值。 - 如果某字段没值,可以赋值为null,或者不写该字段。
- 文档中的键/值对是有序的。
- MongoDB区分类型和大小写。
- 文档的键是字串,可以使用任意的UTF-8编码的字符。
- 不能以
\0
结尾,MongoDB是cpp写的,在cpp中\0
是字串结束符。 .
和$
有特别意义,在特定环境才能用。- 以下划线开头的键是保留的。
7. 索引
如果没有索引,MongoDB会执行全集合扫描,扫描集合中的每个文档,以选择与查询语句匹配的文档。数据量大时效率十分低下。
索引时特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果。
与MySQL索引的区别:MongoDB使用B-Tree索引,MySQL使用B+Tree。
7.1. 索引类型
- 单字段索引。支持升序/降序。
- 复合索引。支持多个字段的排序,字段之间需要指定顺序。
- 地理空间索引。返回结果时使用平面几何的二位索引和返回结果时使用球面几何的二维球面索引。
- 文本索引。支持在集合中搜索字符串的内容。这些文本索引不存储特定于语言的停止词(例如“the”, “a”, “or”),而将集合中的词作为词干,只存储根词。
- 哈希索引。为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。
查看索引
db.collection.getIndexes();
默认_id
索引。在MongoDB创建集合的过程中,在_id
字段上创建一个唯一的索引,默认的名字为_id
,该索引可防止客户端插入两个具有相同值的文档,不能在_id
字段上删除此索引。
该索引时唯一索引,因此值不能重复。在分片集群中,通常使用_id
作为片键。
创建索引
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。
db.collection.createIndex(keys, options)
Parameter | Type | Description |
---|---|---|
keys | document | 包含字段和值对的文档,其中字段是索引键,值描述该字段的索引类型。1为升序索引,-1为降序索引。 |
options | document | 可选。包含一组控制索引创建的选项文档。 |
options(更多选项)列表:
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引的过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加“background”可选参数。默认值为false。 |
unique | Boolean | 建立的索引是否唯一。指定为true创建唯一索引。默认值为false。 |
name | string | 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称,如name_-1. |
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 | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为language。 |
在3.0.0版本前创建索引的方法为db.collection.ensureIndex()
,之后的版本使用了db.collection.createIndex()
方法,ensureIndex()
还能用,但只是createIndex()
的别名。
> db.comment.createIndex({userid: 1});
{
"createdCollectionAutomatically" : false,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.comment.getIndexes();
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "articledb.comment"
},
{
"v" : 2,
"key" : {
"userid" : 1
},
"name" : "userid_1",
"ns" : "articledb.comment"
}
]
移除索引
# 删除某个索引
db.collection.dropIndex(index);
# 删除所有索引(除了_id)
db.collection.dropIndexes();
Parameter | Type | Descrition |
---|---|---|
index | string or document | 指定要删除的索引。可以通过索引名称或者索引规范文档指定索引。若要删除文本索引,需要指定索引名称。 |
# 删除userid字段上的升序索引
db.comment.dropIndex({userid: 1});
7.2. 索引的使用
执行计划
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的二十件、是否基于索引查询等。
db.collection.find(query, options).explain(options);
# 就是在执行的语句后面加上.explain(options)
> db.comment.find({"userid": "1003"}).explain();
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "articledb.comment",
"indexFilterSet" : false,
"parsedQuery" : {
"userid" : {
"$eq" : "1003"
}
},
"queryHash" : "37A12FC3",
"planCacheKey" : "7FDF74EC",
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"userid" : 1
},
"indexName" : "userid_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"userid" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"userid" : [
"[\"1003\", \"1003\"]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "localhost.localdomain",
"port" : 27017,
"version" : "4.4.6",
"gitVersion" : "72e66213c2c3eab37d9358d5e78ad7f5c1d0d0d7"
},
"ok" : 1
}
stage:
- COLLSCAN,表示全集合扫描。
- IXSCAN,基于索引扫描。
涵盖查询
涵盖查询(Covered Queries)。当查询条件和查询的投影仅包含索引字段的时候,直接从索引返回结果,而不扫描任何文档或将文档带入内存。
# 当score为索引的时候,以下语句检索条件和投影仅包含score,故会直接从索引返回结果,为涵盖查询
db.users.find({score: {"$lt": 30}}, {score: 1, _id: 0});
注意
在复合唯一索引的情况下进行批量插入时,如因索引发生重复key值异常,则插入成功的数据入库,发生异常后,后面的数据不入库。MongoDB不支持事务。
7.3. 整合Java
mongodb-driver
mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。
SpringDataMongoDB
Spring Data家族成员之一,用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。
导依赖pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency>
配置文件application.yml
spring: #数据源配置 data: mongodb: # 主机地址 host: 192.168.1.104 # 数据库 database: articledb # 默认端口是27017 port: 27017 #也可以使用uri连接 #uri: mongodb://192.168.1.104:27017/articledb
写实体类
/** * 文章评论实体类 */ //把一个java类声明为mongodb的文档,可以通过collection参数指定这个类对应的文档。 //@Document(collection="mongodb 对应 collection 名") // 若未加 @Document ,该 bean save 到 mongo 的 comment collection // 若添加 @Document ,则 save 到 comment collection @Document(collection="comment")//可以省略,如果省略,则默认使用类名小写映射集合 //复合索引,一般是在数据库中写好 @CompoundIndex( def = "{'userid': 1, 'nickname': -1}", unique=true) // 出现E11000 duplicate key error collection则表示唯一聚合索引出现重复字段(unique=true)。 public class Comment implements Serializable { //主键标识,该属性的值会自动对应mongodb的主键字段"_id",如果该属性名就叫“id”,则该注解可以省略,否则必须写 // @Id private String id;//主键 //该属性对应mongodb的字段的名字,如果一致,则无需该注解 @Field("content") private String content;//吐槽内容 private Date publishtime;//发布日期 //添加了一个单字段的索引 @Indexed private String userid;//发布人ID private String nickname;//昵称 private LocalDateTime createdatetime;//评论的日期时间 private Integer likenum;//点赞数 private Integer replynum;//回复数 private String state;//状态 private String parentid;//上级ID private String articleid; // getter, setter, toString ... }
编写dao
public interface CommentRepository extends MongoRepository<Comment,String> { Page<Comment> findByParentid(String parentid,Pageable pageable); }
编写Service
@Service public class CommentService { @Autowired private CommentRepository commentRepository; @Autowired private MongoTemplate mongoTemplate; /** * 保存一个评论 * @param comment */ public void saveComment(Comment comment){ //如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键 //设置一些默认初始值。。。 //调用dao commentRepository.save(comment); } /** * 更新评论 * @param comment */ public void updateComment(Comment comment){ //调用dao commentRepository.save(comment); } /** * 根据id删除评论 * @param id */ public void deleteCommentById(String id){ //调用dao commentRepository.deleteById(id); } /** * 查询所有评论 * @return */ public List<Comment> findCommentList(){ //调用dao return commentRepository.findAll(); } /** * 根据id查询评论 * @param id * @return */ public Comment findCommentById(String id){ //调用dao return commentRepository.findById(id).get(); } public Page<Comment> findCommentListByParentid(String parentid,int page,int size) { return commentRepository.findByParentid(parentid,PageRequest.of(page-1,size)); } public void updateCommentLikenum(String id){ // 查询条件 Query query = Query.query(Criteria.where("_id").is(id)); // 更新条件 Update update = new Update(); update.inc("likenum"); mongoTemplate.updateFirst(query,update,Comment.class); } }
8. 数据类型
数据类型 | 描述 |
---|---|
String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
Boolean | 布尔值。用于存储布尔值(真/假)。 |
Double | 双精度浮点值。用于存储浮点值。 |
Min/Max keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。 |
Array | 用于将数组或列表或多个值存储为一个键。 |
Timestamp | 时间戳。记录文档修改或添加的具体时间。 |
Object | 用于内嵌文档。 |
Null | 用于创建空值。 |
Symbol | 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。 |
Date | 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。 |
Object ID | 对象 ID。用于创建文档的 ID。 |
Binary Data | 二进制数据。用于存储二进制数据。 |
Code | 代码类型。用于在文档中存储 JavaScript 代码。 |
Regular expression | 正则表达式类型。用于存储正则表达式。 |
9. 开发规范
9.1. 库的设计
mongodb数据库命名规范:dbxxxx 禁止使用任何 " "(下划线)外的特殊字符 禁止使用数字打头的库名 数据库名最多为 64字符
9.2. 集合的设计
mongodb集合命名规范:txxxx 集合名全部小写 禁止使用任何 " "(下划线) 以外的特殊字符 禁止使用数字打头的集合名称 集合名称最多为 64字符
9.3. 文档的设计
文档中的 key 禁止使用任何 " _ "(下划线)以外的特殊字符 禁止使用 _id ,如:向 _id 中写入自定义内容中写入自定义内容 尽量不要让数组字段成为查询条件 尽量存放统一了大小写后的数据
9.4. 索引的设计
优先使用覆盖索引 尽量遵循最左前缀原则 索引名称长度不要超过 128 字符 尽可能的将单列索引并入组合索引以降低数量
10. MongoDB集群和安全
todo…
11. 遇到的问题
QueryExceededMemoryLimitNoDiskUseAllowed
.allowDiskUse(true)
mongo的skip方法不会使用索引,会造成资源占用过高和检索速度慢的问题。