一基础概念
官方文档 https://docs.mongodb.com/manual/indexes/
MongoDB术语/概念与mysql中的术语/概念对照如下:
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
1.1 MongoDB 创建数据库
1.1.1 语法
MongoDB 创建数据库的语法格式如下:
1 | use DATABASE_NAME |
如果数据库不存在,则创建数据库,否则切换到指定数据库。
1.1.2 实例
以下实例创建了数据库 youj:
1 | > use youj |
如果你想查看所有数据库,可以使用 show dbs 命令:
1 | > show dbs |
可以看到,刚创建的数据库 youj 并不在数据库的列表中, 要显示它,需要向 youj 数据库插入一些数据。
1 | > db.youj.insert({"name":"W3Cschool教程"}) |
MongoDB 中默认的数据库为 test,如果你没有创建新的数据库,集合将存放在 test 数据库中。
1.2 MongoDB 删除数据库
1.2.1 语法
MongoDB 删除数据库的语法格式如下:
1 | db.dropDatabase() |
删除当前数据库,默认为 test,你可以使用 db 命令查看当前数据库名。
1.2.1 实例
以下实例删除了数据库 youj。
首先,查看所有数据库:
1 | > show dbs |
接下来切换到数据库 youj:
1 | > use youj |
执行删除命令:
1 | > db.dropDatabase() |
最后,再通过 show dbs 命令数据库是否删除成功:
1 | > show dbs |
1.2.2 删除集合
集合删除语法格式如下:
1 | db.collection.drop() |
1.3 MongoDB 插入文档
文档的数据结构和JSON基本一样。
所有存储在集合中的数据都是BSON格式。
BSON是一种类json的一种二进制形式的存储格式,简称Binary JSON。
1.3.1 插入文档
MongoDB 使用 insert() 或 save() 方法向集合中插入文档,语法如下:
1 | db.COLLECTION_NAME.insert(document) |
1.3.2 实例
以下文档可以存储在 MongoDB 的 w3cschool.cn数据库 的 col集合中:
1 | >db.col.insert({title: 'MongoDB 教程', |
以上实例中 col 是集合名,前一章节已经创建过了,如果该集合不在该数据库中, MongoDB 会自动创建该集合并插入文档。
查看已插入文档:
1 | > db.col.find() |
也可以将数据定义为一个变量,如下所示:
1 | >document=({title: 'MongoDB 教程', |
执行后显示结果如下:
1 | { |
执行插入操作:
1 | > db.col.insert(document) |
插入文档你也可以使用 db.col.save(document) 命令。如果不指定 _id 字段 save() 方法类似于 insert() 方法。如果指定 _id 字段,则会更新该 _id 的数据。
1.4 MongoDB更新数据
本章节将开始学习如何更新MongoDB中的集合数据。
MongoDB数据更新可以使用update()函数。
db.collection.update( criteria, objNew, upsert, multi )
update()函数接受以下四个参数:
- criteria : update的查询条件,类似sql update查询内where后面的。
- objNew : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的
- upsert : 这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
- multi : mongodb默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
在本教程中使用的数据库名称为”myinfo”,集合名称为”userdetails”,以下为插入的数据:
1 | > document=({ |
1 | > db.userdetails.insert(document) |
1 | > document=({ |
1 | > db.userdetails.insert(document) |
1.4.1 update() 命令
如果想将”userdetails”集合中”user_id”为”QRSTBWN”的”password”字段修改为”NEWPASSWORD”,那么可以使用update()命令来实现(如下实例所示)。
如果criteria参数匹配集合中的任何一条数据,它将会执行替换命令,否则会插入一条新的数据。
以下实例将更新第一条匹配条件的数据:
1 | db.userdetails.update( |
1.4.2 查看集合中更新后的数据
可以使用以下命令查看数据是否更新:
1 | >db.userdetails.find(); |
1.4.3 更多实例
只更新第一条记录:
db.test0.update( { “count” : { $gt : 1 } } , { $set : { “test2” : “OK”} } );
全部更新:
db.test0.update( { “count” : { $gt : 3 } } , { $set : { “test2” : “OK”} },false,true );
只添加第一条:
db.test0.update( { “count” : { $gt : 4 } } , { $set : { “test5” : “OK”} },true,false );
全部添加加进去:
db.test0.update( { “count” : { $gt : 5 } } , { $set : { “test5” : “OK”} },true,true );
全部更新:
db.test0.update( { “count” : { $gt : 15 } } , { $inc : { “count” : 1} },false,true );
只更新第一条记录:
db.test0.update( { “count” : { $gt : 10 } } , { $inc : { “count” : 1} },false,false );
1.5 MongoDB 删除文档
MongoDB remove()函数是用来移除集合中的数据。
MongoDB数据更新可以使用update()函数。在执行remove()函数前先执行find()命令来判断执行的条件是否正确,这是一个比较好的习惯。
1.5.1 语法
remove() 方法的基本语法格式如下所示:
1 | db.collection.remove( |
如果你的 MongoDB 是 2.6 版本以后的,语法格式如下:
1 | db.collection.remove( |
参数说明:
- query :(可选)删除的文档的条件。
- justOne : (可选)如果设为 true 或 1,则只删除一个文档。
- writeConcern :(可选)抛出异常的级别。
1.5.2 实例
以下文档执行两次插入操作:
1 | >db.col.insert({title: 'MongoDB 教程', |
使用 find() 函数查询数据:
1 | > db.col.find() |
接下来移除 title 为 ‘MongoDB 教程’ 的文档:
1 | >db.col.remove({'title':'MongoDB 教程'}) |
如果你只想删除第一条找到的记录可以设置 justOne 为 1,如下所示:
1 | >db.COLLECTION_NAME.remove(DELETION_CRITERIA,1) |
如果你想删除所有数据,可以使用以下方式(类似常规 SQL 的 truncate 命令):
1 | >db.col.remove({}) |
二 MongoDB 查询文档
MongoDB 查询文档使用 find() 方法。
find() 方法以非结构化的方式来显示所有文档。
2.1 语法
MongoDB 查询数据的语法格式如下:
1 | db.collection.find(query, projection) |
- query :可选,使用查询操作符指定查询条件
- projection :可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
如果你需要以易读的方式来读取数据,可以使用 pretty() 方法,语法格式如下:
1 | >db.col.find().pretty() |
pretty() 方法以格式化的方式来显示所有文档。
2.1.1 实例
以下实例查询了集合 col 中的数据:
1 | > db.col.find().pretty() |
除了 find() 方法之外,还有一个 findOne() 方法,它只返回一个文档。
2.1.2 MongoDB 与 RDBMS Where 语句比较
如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件语句查询:
操作 | 格式 | 范例 | RDBMS中的类似语句 |
---|---|---|---|
等于 | {<key>:<value> } |
db.col.find({"by":"菜鸟教程"}).pretty() |
where by = '菜鸟教程' |
小于 | {<key>:{$lt:<value>}} |
db.col.find({"likes":{$lt:50}}).pretty() |
where likes < 50 |
小于或等于 | {<key>:{$lte:<value>}} |
db.col.find({"likes":{$lte:50}}).pretty() |
where likes <= 50 |
大于 | {<key>:{$gt:<value>}} |
db.col.find({"likes":{$gt:50}}).pretty() |
where likes > 50 |
大于或等于 | {<key>:{$gte:<value>}} |
db.col.find({"likes":{$gte:50}}).pretty() |
where likes >= 50 |
不等于 | {<key>:{$ne:<value>}} |
db.col.find({"likes":{$ne:50}}).pretty() |
where likes != 50 |
2.2 MongoDB AND 条件
MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,即常规 SQL 的 AND 条件。
语法格式如下:
1 | >db.col.find({key1:value1, key2:value2}).pretty() |
2.2.1 实例
以下实例通过 by 和 title 键来查询 菜鸟教程 中 MongoDB 教程 的数据
1 | > db.col.find({"by":"菜鸟教程", "title":"MongoDB 教程"}).pretty() |
以上实例中类似于 WHERE 语句:WHERE by=’菜鸟教程’ AND title=’MongoDB 教程’
2.3 MongoDB OR 条件
MongoDB OR 条件语句使用了关键字 $or,语法格式如下:
1 | >db.col.find( |
2.3.1 实例
以下实例中,演示了查询键 by 值为 菜鸟教程 或键 title 值为 MongoDB 教程 的文档。
1 | >db.col.find({$or:[{"by":"菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty() |
2.4 AND 和 OR 联合使用
以下实例演示了 AND 和 OR 联合使用,类似常规 SQL 语句为: ‘where likes>50 AND (by = ‘菜鸟教程’ OR title = ‘MongoDB 教程’)’
1 | >db.col.find({"likes": {$gt:50}, $or: [{"by": "菜鸟教程"},{"title": "MongoDB 教程"}]}).pretty() |
2.5 返回数据字段
补充一下 projection 参数的使用方法
1 | db.collection.find(query, projection) |
若不指定 projection,则默认返回所有键,指定 projection 格式如下,有两种模式
1 | db.collection.find(query, {title: 1, by: 1}) // inclusion模式 指定返回的键,不返回其他键 |
_id 键默认返回,需要主动指定 _id:0 才会隐藏
两种模式不可混用(因为这样的话无法推断其他键是否应返回)
1 | db.collection.find(query, {title: 1, by: 0}) // 错误 |
只能全1或全0,除了在inclusion模式时可以指定_id为0
1 | db.collection.find(query, {_id:0, title: 1, by: 1}) // 正确 |
2.6 模糊查询
查询 title 包含”教”字的文档:
1 | db.col.find({title:/教/}) |
查询 title 字段以”教”字开头的文档:
1 | db.col.find({title:/^教/}) |
查询 titl e字段以”教”字结尾的文档:
1 | db.col.find({title:/教$/}) |
2.7 查询嵌入/嵌套文档
本页提供了使用 mongosh 中的 db.collection.find() 方法对嵌入/嵌套文档进行查询操作的示例。 此页面上的示例使用库存集合。 要填充清单集合,请运行以下命令:
1 | db.inventory.insertMany( [ |
2.7.1 匹配嵌入式/嵌套文档
要在作为嵌入/嵌套文档的字段上指定相等条件,请使用查询过滤器文档 {
例如,以下查询选择字段大小等于文档 { h: 14, w: 21, uom: “cm” } 的所有文档:
将以下过滤器复制到 Compass 查询栏中,然后单击查找:
1 | db.inventory.find( { size: { h: 14, w: 21, uom: "cm" } } ) |
整个嵌入文档的相等匹配需要指定
1 | db.inventory.find( { size: { w: 21, h: 14, uom: "cm" } } ) |
2.7.2 查询嵌套字段
要在嵌入/嵌套文档中的字段上指定查询条件,请使用点表示法(“field.nestedField”)。
使用点表示法查询时,字段和嵌套字段必须在引号内。
2.7.3 在嵌套字段上指定相等匹配
以下示例选择嵌套在 size 字段中的字段 uom 等于“in”的所有文档:
1 | db.inventory.find( { "size.uom": "in" } ) |
2.7.4 使用查询运算符指定匹配
查询过滤器文档可以使用查询运算符以下列形式指定条件:
1 | { <field1>: { <operator1>: <value1> }, ... } |
以下查询对嵌入在 size 字段中的字段 h 使用小于运算符 ($lt):
1 | db.inventory.find( { "size.h": { $lt: 15 } } ) |
2.7.5 指定 AND 条件
以下查询选择嵌套字段 h 小于 15、嵌套字段 uom 等于“in”且状态字段等于“D”的所有文档:
1 | db.inventory.find( { "size.h": { $lt: 15 }, "size.uom": "in", status: "D" } ) |
2.8 查询数组
本页提供了在 mongosh 中使用 db.collection.find() 方法对数组字段进行查询操作的示例。 此页面上的示例使用库存集合。 要填充清单集合,请运行以下命令:
1 | db.inventory.insertMany([ |
2.8.1 匹配一个数组
要在数组上指定相等条件,请使用查询文档 {
以下示例查询字段标签值是一个数组的所有文档,其中两个元素“红色”和“空白”按指定顺序排列:
1 | db.inventory.find( { tags: ["red", "blank"] } ) |
相反,如果您希望找到一个包含元素“red”和“blank”的数组,而不考虑数组中的顺序或其他元素,请使用 $all 运算符:
1 | db.inventory.find( { tags: { $all: ["red", "blank"] } } ) |
2.8.2 查询数组中的元素
要查询数组字段是否包含至少一个具有指定值的元素,请使用过滤器 {
以下示例查询所有文档,其中 tags 是一个包含字符串“red”作为其元素之一的数组:
1 | db.inventory.find( { tags: "red" } ) |
要对数组字段中的元素指定条件,请在查询过滤器文档中使用查询运算符:
1 | { <array field>: { <operator1>: <value1>, ... } } |
例如,以下操作查询数组 dim_cm 包含至少一个值大于 25 的元素的所有文档。
1 | db.inventory.find( { dim_cm: { $gt: 25 } } ) |
2.8.3 在数组元素上查询具有复合过滤条件的数组
以下示例查询 dim_cm 数组包含以某种组合满足查询条件的元素的文档; 例如,一个元素可以满足大于 15 的条件,而另一个元素可以满足小于 20 的条件,或者单个元素可以同时满足这两个条件:
1 | db.inventory.find( { dim_cm: { $gt: 15, $lt: 20 } } ) |
2.8.4 查询满足多个条件的数组元素
使用 $elemMatch 运算符对数组元素指定多个条件,以使至少一个数组元素满足所有指定条件。
以下示例查询 dim_cm 数组包含至少一个大于 ($gt) 22 且小于 ($lt) 30 的元素的文档:
1 | db.inventory.find( { dim_cm: { $elemMatch: { $gt: 22, $lt: 30 } } } ) |
2.8.5 按数组索引位置查询元素
使用点表示法,您可以在数组的特定索引或位置指定元素的查询条件。 该数组使用从零开始的索引。
使用点表示法查询时,字段和嵌套字段必须在引号内。
以下示例查询数组 dim_cm 中第二个元素大于 25 的所有文档:
1 | db.inventory.find( { "dim_cm.1": { $gt: 25 } } ) |
2.8.6 按数组长度查询数组
使用 $size 运算符按元素数查询数组。 例如,以下选择数组标签具有 3 个元素的文档。
1 | db.inventory.find( { "tags": { $size: 3 } } ) |
三 MongoDB 条件操作符
条件操作符用于比较两个表达式并从mongoDB集合中获取数据。
在本章节中,将讨论如何在MongoDB中使用条件操作符。
MongoDB中条件操作符有:
- (>) 大于 - $gt
- (<) 小于 - $lt
- (>=) 大于等于 - $gte
- (<= ) 小于等于 - $lte
使用的数据库名称为”runoob” 集合名称为”col”,以下为插入的数据。
为了方便测试,可以先使用以下命令清空集合 “col” 的数据:
1 | db.col.remove({}) |
插入以下数据
1 | >db.col.insert({ |
1 | >db.col.insert({title: 'Java 教程', |
1 | >db.col.insert({title: 'MongoDB 教程', |
使用find()命令查看数据:
1 | > db.col.find() |
3.1 MongoDB (>) 大于操作符 - $gt
如果你想获取 “col” 集合中 “likes” 大于 100 的数据,你可以使用以下命令:
1 | db.col.find({likes : {$gt : 100}}) |
类似于SQL语句:
1 | Select * from col where likes > 100; |
输出结果:
1 | > db.col.find({likes : {$gt : 100}}) |
3.2 MongoDB(>=)大于等于操作符 - $gte
如果你想获取”col”集合中 “likes” 大于等于 100 的数据,你可以使用以下命令:
1 | db.col.find({likes : {$gte : 100}}) |
类似于SQL语句:
1 | Select * from col where likes >=100; |
输出结果:
1 | > db.col.find({likes : {$gte : 100}}) |
3.3 MongoDB (<) 小于操作符 - $lt
如果你想获取”col”集合中 “likes” 小于 150 的数据,你可以使用以下命令:
1 | db.col.find({likes : {$lt : 150}}) |
类似于SQL语句:
1 | Select * from col where likes < 150; |
输出结果:
1 | > db.col.find({likes : {$lt : 150}}) |
3.4 MongoDB (<=) 小于等于操作符 - $lte
如果你想获取”col”集合中 “likes” 小于等于 150 的数据,你可以使用以下命令:
1 | db.col.find({likes : {$lte : 150}}) |
类似于SQL语句:
1 | Select * from col where likes <= 150; |
输出结果:
1 | > db.col.find({likes : {$lte : 150}}) |
3.5 MongoDB 使用 (<) 和 (>) 查询 - $lt 和 $gt
如果你想获取”col”集合中 “likes” 大于100,小于 200 的数据,你可以使用以下命令:
1 | db.col.find({likes : {$lt :200, $gt : 100}}) |
类似于SQL语句:
1 | Select * from col where likes>100 AND likes<200; |
输出结果:
1 | > db.col.find({likes : {$lt :200, $gt : 100}}) |
四 MongoDB Limit与Skip方法
4.1 MongoDB Limit() 方法
如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。
4.1.1 语法
limit()方法基本语法如下所示:
1 | >db.COLLECTION_NAME.find().limit(NUMBER) |
4.1.2 实例
集合 col 中的数据如下:
1 | { "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", |
以下实例为显示查询文档中的两条记录:
1 | > db.col.find({},{"title":1,_id:0}).limit(2) |
注:如果你们没有指定limit()方法中的参数则显示集合中的所有数据。
4.2 MongoDB Skip() 方法
除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。
4.2.1 语法
skip() 方法脚本语法格式如下:
1 | >db.COLLECTION_NAME.find().limit(NUMBER).skip(NUMBER) |
4.2.2 实例
以下实例只会显示第二条文档数据
1 | >db.col.find({},{"title":1,_id:0}).limit(1).skip(1) |
注:skip()方法默认参数为 0 。
4.3 大数据查询
补充说明skip和limit方法只适合小数据量分页,如果是百万级效率就会非常低,因为skip方法是一条条数据数过去的,建议使用where_limit
在查看了一些资料之后,发现所有的资料都是这样说的:
不要轻易使用Skip来做查询,否则数据量大了就会导致性能急剧下降,这是因为Skip是一条一条的数过来的,多了自然就慢了。
这么说Skip就要避免使用了,那么如何避免呢?首先来回顾SQL分页的后一种时间戳分页方案,这种利用字段的有序性质,利用查询来取数据的方式,可以直接避免掉了大量的数数。也就是说,如果能附带上这样的条件那查询效率就会提高,事实上是这样的么?来验证一下:
这里假设查询第100001条数据,这条数据的Amount值是:2399927,来写两条语句分别如下:
1 | b.test.sort({"amount":1}).skip(100000).limit(10) //183ms |
结果已经附带到注释了,很明显后者的性能是前者的三分之一,差距是非常大的。也印证了Skip效率差的理论。
需要注意的是,此处的skip,sort,和limit三者执行顺序和位置无关,但是在聚合aggregate中使用的时候,具有管道流的特质,执行顺序是按照位置关系顺序执行的。
五 MongoDB 排序
5.1 MongoDB sort() 方法
在 MongoDB 中使用 sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列。
5.1.1 语法
sort()方法基本语法如下所示:
1 | >db.COLLECTION_NAME.find().sort({KEY:1}) |
5.1.2 实例
col 集合中的数据如下:
1 | { "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", |
以下实例演示了 col 集合中的数据按字段 likes 的降序排列:
1 | >db.col.find({},{"title":1,_id:0}).sort({"likes":-1}) |
skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit()。
六 MongoDB 索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
6.1 createIndex() 方法
MongoDB使用 createIndex() 方法来创建索引。
注意在 3.0.0 版本前创建索引方法为 db.collection.ensureIndex(),之后的版本使用了 db.collection.createIndex() 方法,ensureIndex() 还能用,但只是 createIndex() 的别名。
6.1.1 语法
createIndex()方法基本语法格式如下所示:
1 | >db.collection.createIndex(keys, options) |
语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
6.1.2 实例
1 | >db.col.createIndex({"title":1}) |
createIndex() 方法中你也可以设置使用多个字段创建索引(关系型数据库中称作复合索引)。
1 | >db.col.createIndex({"title":1,"description":-1}) |
createIndex() 接收可选参数,可选参数列表如下:
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 | 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language. |
6.1.3 实例
在后台创建索引:
1 | db.values.createIndex({open: 1, close: 1}, {background: true}) |
通过在创建索引时加 background:true 的选项,让创建工作在后台执行
6.2 常见索引API
1、查看集合索引
1 | db.col.getIndexes() |
2、查看集合索引大小
1 | db.col.totalIndexSize() |
3、删除集合所有索引
1 | db.col.dropIndexes() |
4、删除集合指定索引
1 | db.col.dropIndex("索引名称") |
6.3 临时索引
利用 TTL 集合对存储的数据进行失效时间设置:经过指定的时间段后或在指定的时间点过期,MongoDB 独立线程去清除数据。类似于设置定时自动删除任务,可以清除历史记录或日志等前提条件,设置 Index 的关键字段为日期类型 new Date()。
例如数据记录中 createDate 为日期类型时:
- 设置时间180秒后自动清除。
- 设置在创建记录后,180 秒左右删除。
1 | db.col.createIndex({"createDate": 1},{expireAfterSeconds: 180}) |
由记录中设定日期点清除。
设置 A 记录在 2019 年 1 月 22 日晚上 11 点左右删除,A 记录中需添加 “ClearUpDate”: new Date(‘Jan 22, 2019 23:00:00’),且 Index中expireAfterSeconds 设值为 0。
1 | db.col.createIndex({"ClearUpDate": 1},{expireAfterSeconds: 0}) |
其他注意事项:
- 索引关键字段必须是 Date 类型。
- 非立即执行:扫描 Document 过期数据并删除是独立线程执行,默认 60s 扫描一次,删除也不一定是立即删除成功。
- 单字段索引,混合索引不支持。
6.4 MongoDB 索引限制
6.4.1 额外开销
每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以,如果你很少对集合进行读取操作,建议不使用索引。
6.4.2 内存(RAM)使用
由于索引是存储在内存(RAM)中,你应该确保该索引的大小不超过内存的限制。
如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
6.4.3 查询限制
索引不能被以下的查询使用:
- 正则表达式及非操作符,如 $nin, $not, 等。
- 算术运算符,如 $mod, 等。
- $where 子句
所以,检测你的语句是否使用索引是一个好的习惯,可以用explain来查看。
6.4.4 索引键限制
从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引。
6.4.5 插入文档超过索引键限制
如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合。与mongorestore和mongoimport工具类似。
6.4.6 最大范围
- 集合中索引不能超过64个
- 索引名的长度不能超过128个字符
- 一个复合索引最多可以有31个字段
6.5 批量创建索引
1 | >db.demo.createIndexes([{"First":1},{"Second":1},{"Third":1},{"Fourth":1},{"Fifth":1}]); |
这将产生以下输出
1 | { |
现在获取所有索引
1 | > db.multipleIndexesDemo.getIndexes(); |
七 MongoDB 聚合
MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。
有点类似 SQL 语句中的 count(*)。
7.1 aggregate() 方法
MongoDB中聚合的方法使用aggregate()。
7.1.1 语法
aggregate() 方法的基本语法格式如下所示:
1 | >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) |
7.1.2 实例
集合中的数据如下:
1 | { |
现在通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:
1 | > db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}]) |
以上实例类似sql语句:
1 | select by_user, count(*) from mycol group by by_user |
在上面的例子中,通过字段 by_user 字段对数据进行分组,并计算 by_user 字段相同值的总和。
下表展示了一些聚合的表达式:
表达式 | 描述 | 实例 |
---|---|---|
$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”}}}]) |
7.2 管道的概念
管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。
MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
这里介绍一下聚合框架中常用的几个操作:
- $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
- $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
- $limit:用来限制MongoDB聚合管道返回的文档数。
- $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
- $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
- $group:将集合中的文档分组,可用于统计结果。
- $sort:将输入文档排序后输出。
- $geoNear:输出接近某一地理位置的有序文档。
7.2.1 管道操作符实例
7.2.1.1 $project实例
1 | db.article.aggregate( |
这样的话结果中就只还有_id,tilte和author三个字段了,默认情况下_id字段是被包含的,如果要想不包含_id话可以这样:
1 | db.article.aggregate( |
7.2.1.2 $match实例
1 | db.articles.aggregate( [ |
$match用于获取分数大于70小于或等于90记录,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。
7.2.1.3 $skip实例
1 | db.article.aggregate( |
经过$skip管道操作符处理后,前五个文档被”过滤”掉。
7.3 按照时间聚合
按日、按月、按年、按周、按小时、按分钟聚合操作如下:
1 | db.getCollection('m_msg_tb').aggregate( |
时间关键字如下:
- $dayOfYear: 返回该日期是这一年的第几天(全年 366 天)。
- $dayOfMonth: 返回该日期是这一个月的第几天(1到31)。
- $dayOfWeek: 返回的是这个周的星期几(1:星期日,7:星期六)。
- $year: 返回该日期的年份部分。
- $month: 返回该日期的月份部分( 1 到 12)。
- $week: 返回该日期是所在年的第几个星期( 0 到 53)。
- $hour: 返回该日期的小时部分。
- $minute: 返回该日期的分钟部分。
- $second: 返回该日期的秒部分(以0到59之间的数字形式返回日期的第二部分,但可以是60来计算闰秒)。
- $millisecond:返回该日期的毫秒部分( 0 到 999)。
- $dateToString: { $dateToString: { format: , date: } }。
7.4 执行顺序
这里有一个小细节:当 match 条件和 group 同时存在时,顺序会影响检索结果,必须先写 match 在前面。
1 | db.articles.aggregate([{$match:{score:{ $gt:70, $lte:90 }}},{$group:{_id: null,count:{$sum: 1 }}}]); |
这两个语句执行的结果会不一样.
八 常见语句示例
8.1 查询部分数据
1 | mysql: |
说明:user是集合的名称,find里面两个{},第一个留空是想所有数据都查出来,加条件的话类型mysql的where,第二个{}表示的意思类似mysql后面的select部分,0代表不显示,1代表显示。
8.2 分页查询
1 | mysql: |
说明:mongo的skip和limit与mysql同理,mysql的limit第一个参数是跳过的数据量与mongo的skip类似,比如第三页的数据是从20开始的,mysql:limit 20,10,即:limit (page-1)*size,size
8.3 条件查询
1 | mysql: |
说明:由于有严格要求数据格式,若存到mongo的id是字符串格式的话,查询的条件得加上双引号””
8.4 范围查询
MySQL | MongoDB | remark |
---|---|---|
> | $gt | 大于 |
< | $lt | 小于 |
>= | $gte | 大于等于 |
<= | $lte | 小于等于 |
!= | $ne | 不等于 |
1 | mysql: |
说明:mysql的between其实就是>=和<=,字符串的话用范围查询好像会有问题,慎用!
8.5 in查询
1 | mysql: |
说明:not in查询就把in换成in换成nin
8.6 条件统计count
1 | mysql: |
8.7 all查询
mongo可以将数组存储起来,若想查询某个字段(是个数组)同时包含值a和b
1 | db.user.find( |
说明:这个查询的结果集,detail字段同时包含字符串7和字符串8
8.8 exists查询
比如我想找出所有包含字段name_real的结果集
1 | db.user.find( |
说明:上面查询的结果中,所有数据肯定都包含有name_real字段;改成false的话就变成不包含
8.9 is null查询
1 | mysql: |
但是这样会有问题,这个查询会把那些没有age字段的结果也查出来,结合exists优化下
1 | db.user.find( |
查询is not null
1 | db.user.find( |
8.10 取模运算
mongo提供取模运算,比如存储了一些数据,我想查出那些取模后等于某个值的数据可以使用$mod
比如下例查询年龄是10的倍数的用户
1 | mysql: |
8.11 查询数据元素个数
由于mongo可以存储数组,如果想查询数组中只有两个元素的记录时,可以使用$size
比如下例查询有三个兴趣爱好的用户
1 | db.user.find( |
8.12 正则匹配查询
如果想用正则匹配查询,可以使用$regex
比如下例匹配年龄是10以下的用户
1 | db.user.find( |
8.13 只取一部分数据
类似mysql的limit,mongo也可以只取一部分数据
1 | mysql: |
8.14 排序
MySQL | MongoDB | 说明 |
---|---|---|
asc | 1 | 升序 |
desc | -1 | 降序 |
1 | mysql: |
说明:mongo字符串类型的也可以排序
8.15 求和
直接累加求和某一项
比如下例对年龄进行求和
1 | mysql: |
分组求和
下例为按类型分组并求和
1 | mysql: |
多条件分组求和
下例为按多个条件进行分组并求和
1 | mysql: |
8.16 分组后having
下例为按条件分组并筛选出求和后大于100的数据
1 | mysql: |
8.17 条件分组
类似mysql的where+group by进行查询
下例为查找出2020-01-01(timestamp:1577808000)后注册的用户,并按类型分组求和
1 | mysql: |
条件分组并having筛选
下例为查找出2020-01-01(timestamp:1577808000)后注册的用户,并按类型分组,同时筛选出大于100的数据
1 | mysql: |
8.18 unwind
加入你的mongo的每一条记录有一个字段,存的是一个数组,数组里面是对象,类似这样,article字段含有
1 | [ |
使用unwind可以使上面原本一条记录进行展开,分为三条数据进行展示,有点像mysql的join查询,只不过mysql得分开两个表存
1 | mysql: |
unwind后求和
1 | mysql: |
8.19 分组后统计总共有多少组
下例分按类型分组,并统计总数
1 | mysql: |
8.20 grep指令
aggregate类型linux的grep指令,像管道处理一样,一级接一级,比如:筛选、分组、过滤等,最后返回结果
1 | db.user.aggregate([ |
8.21 匹配后统计再过滤
1 | db.tax_data.aggregate([ |
8.22 查询重复数据
1 | db.tax_data_tmp.aggregate([ |
或者
1 | db.getCollection("tax_data_tmp").aggregate( |
8.23 删除重复数据
1 | db.demo_db.aggregate([ |
使用aggregate聚合查询重复数据
- $group中是查询条件,根据content、endTime、startTime字段来聚合相同的数据;
- $count用来统计重复出现的次数, $match来过滤没有重复的数据;
- $addToSet将聚合的数据id放入到dups数组中方便后面使用;
查询结果使用forEach进行迭代id来删除数据
- shift()作用是剔除队列中第一条id,避免删掉所有的数据;
注意函数的大小写,mongoDB是严格区分大小写的!!!
注意:上述代码针对与2.4x版本,2.4x版本的数据是放在reslut
字段下的,而3.x是放在·_batch·字段下。
可采用以下语句:
1 | var list = db.demo_db.aggregate([ |