输出查询信息
db.tablename.find().explain( “executionStats” )
MongoDB的存储是BJSON(Binary JSON)格式,一种二进制的json格式,实际上BJSON的比json格式更大,所以mongodb是以空间换时间的;
_id和ObjectId:24个十六进制的数(12个字节):
时间戳(4个字节):保证不同秒不同;
机器id(3个字节):保证不同机器不同;
PID(2个字节):保证同一机器的多进程不同;
计数器(3个字节):保证同一秒钟产生的不同;
数据库设计优化:
一、内嵌文档:
1.子文档较小;
2.数据不会定期改变;
3.最终数据一致即可;
4.文档数据小幅增加;
5.数据通常需要二次查询才能获得;
6.快速读取;
二、引用文档:
1.子文档较大;
2.数据经常改变;
3.中间数据必须一致;
4.文档数据大幅增加;
5.数据通常不包含在结果中;
6.快速写入;
三、通常把需要改变的字段放到文档的最后面;
四、采用占位字段避免文档变大时的移动位置;
五、采用索引优化查询速度;
六、避免使用skip跳过大量数据(跳过过多数据会造成性能急剧下降);
七、尽量使用$in代替$ne查询;
修改器操作符:
1.普通操作符:
$set:{filedname:value}
$unset: {filedname:value}
$inc:{filedname:value}
2.数组操作符:
$push:
$addToSet:
$each:
$slice:
$pull:
$pop:
$sort:
3.定位操作符:
$
查询操作符:
$gte:
$gt:
$lt:
$lte:
$or:
$not:
$in:
$ne:
$exists:
$eq:
$all:
$size:
$slice:
$elemMatch:
聚合(aggregate)之管道操作符:
1.普通操作符:
$match:
1.兼容所有常规查询操作符;
$project:
1.管道表达式:
$fieldname(字段名)
2.数学表达式:
$add:
$subtract:
$multiply:
$divide:
$mod:
3.日期表达式:
$year:
$month:
$week:
$dayOfMonth:
$dayOfWeek:
$dayOfYear:
$hour:
$minute:
$second:
4.字符串表达式:
$substr:
$concat:
$toLower:
$toUpper:
5.逻辑表达式:
大部分查询的比较操作符
$cmp:
$strcasecmp:
$and:
$or:
$not:
$cond:
$ifNull:
$group:
1.算数操作符:
$sum:
$avg:
2.极值操作符
$max:
$min:
$first:
$last:
3.数组操作符:
$addToSet:
$push:
$unwind:
$sort:
$limit:
$skip:
2.MapReduce
package.json
{
"name": "mongodb",
"version": "1.0.0",
"description": "mongodb入门",
"main": "index.js",
"scripts": {
"test": "npm run test"
},
"keywords": [
"mongodb"
],
"author": "vince",
"license": "ISC",
"dependencies": {
"bluebird": "^3.5.1",
"mongoose": "^4.13.9"
}
}
model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
module.exports = mongoose.model('test', new Schema({
title: String,
contents: String,
score: {},
attr: { type: String, default: null },
comments: [],
placeholder: {
type: String, default: '...............................................' +
'..................................................................' +
'...................................................................'
},
}));
class.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
module.exports = mongoose.model('class', new Schema({
class: { type: String, unique: true },
teacher: String,
course: []
}));
student.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
module.exports = mongoose.model('student', new Schema({
name: String,
age: Number,
hobby: [],
class: String
}));
index.js
const Promise = require('bluebird');
const mongoose = require('mongoose');
const TestModel = require('./model');
const Class = require('./class');
const Student = require('./student');
mongoose.Promise = Promise; //引用bluebird替换mongoose的promise
mongoose.connect('mongodb://10.10.10.16:27017/test');
const connection = mongoose.connection;
connection.on('error', error => { // 数据库连接失败
console.log('链接错误:' + error)
});
// console.log(mongoose.Types.ObjectId())
connection.once('open', () => {
console.log('连接成功');
return connection.dropDatabase()
.then(data => {
console.log('清除数据库')
// 插入数据:create({});
// 批量插入数据: create([{]}]) / insertMany([{}])
return TestModel.insertMany([
{
title: '标题1',
contents: "内容1",
comments: [
{
author: 'qqq1',
comment: '好',
score: 5
},
{
author: 'qqq2',
comment: '很好',
score: 5
},
{
author: 'qqq3',
comment: '非常好',
score: 5
}
],
score: 15
},
{
title: '标题2',
contents: "内容2",
comments: [
{
author: 'zzz1',
comment: '好',
score: 5
},
{
author: 'zzz2',
comment: '很好',
score: 5
},
{
author: 'zzz3',
comment: '非常好',
score: 10
}
],
score: 25
},
])
})
.then(data => {
console.log('insertMany成功:\n', data)
// 更新数据:update();
// 使用$push/$addToSet配合$each可以向数组中添加多个元素;$push还可以配合$slice:整数
return TestModel.update(
{ title: '标题2' },
{
$push: {
comments: {
$each: [
{
author: 'zzz3',
comment: '非常好',
score: 10
},
{
author: 'zzz4',
comment: '非常好',
score: 10
},
{
author: 'zzz5',
comment: '非常好',
score: 10
}
],
$slice: -4
}
},
$unset: { placeholder: 1 }
}
)
})
.then(data => {
console.log('update/$push/$each/$slice成功:\n', data);
//addToSet在数组中添加不重复的数值
return TestModel.update(
{ title: '标题1' },
{
$addToSet: {
comments: {
$each: [
{
author: 'qqq4',
comment: '非常好',
score: 10
},
{
author: 'zzz5',
comment: '非常好',
score: 10
},
{
author: 'qqq4',
comment: '非常好'
},
],
$slice: -4
}
},
$unset: { placeholder: 1 }
}
)
})
.then(data => {
console.log('update/$addToSet/$each成功:\n', data);
//配合$位置修改器 更改数组中的某一个值
return TestModel.update(
{ 'comments.author': 'zzz5', title: '标题1' },
{
$set: { 'comments.$.author': 'qqq5' },
$unset: { placeholder: 1 }
}
// { multi: true }
)
})
.then(data => {
console.log('update/$成功:\n', data);
//自增修改器$inc:{filed:整数}
return TestModel.update(
{ title: '标题1' },
{
$inc: { score: 1 },
$unset: { placeholder: 1 }
}
// ,{ multi: true } //更新多个文档
)
})
.then(data => {
console.log('update/$inc成功:\n', data);
//update第三参数添加upsertshu属性为true,更新时,如果找不到会创建(没有默认值),否则,不会有任何更新;如果找到就是正常更新
return TestModel.update(
{ title: '标题3' },
{
contents: "内容2",
comments: [
{
author: 'yyy1',
comment: '好'
},
{
author: 'yyy2',
comment: '很好'
},
{
author: 'yyy3',
comment: '非常好'
}
],
},
{ upsert: true }
)
})
.then(data => {
console.log('update/upsert成功:\n', data);
return TestModel.update(
{ title: '标题3' },
{
contents: "内容3",
comments: [
{
author: 'yyy1',
comment: '好'
},
{
author: 'yyy2',
comment: '很好'
},
{
author: 'yyy2',
comment: '很好'
},
{
author: 'yyy2',
comment: '很好'
},
{
author: 'yyy3',
comment: '非常好'
}
],
$set: { score: 15 },
$unset: { placeholder: 1 }
},
{ upsert: 1 }
)
})
.then(data => {
console.log('update/upsert成功:\n', data);
//使用正则表达式
return TestModel.find(
{
title: /标题2|标题3/,
score: { $gte: 15 }
},
{
_id: 0,
title: 1,
contents: 1,
comments: 1,
score: 1
}
)
})
.then(data => {
console.log('reg/查找成功:\n', data);
//$or匹配两个不同字段任意一个条件;
return TestModel.find(
{
$or: [{ title: '标题1' }, { score: { $gte: 15 } }]
},
{
_id: 0,
title: 1,
contents: 1,
comments: 1,
score: 1
}
)
})
.then(data => {
console.log('$or查询成功:\n' + data);
// $in 匹配同一个字段任意条件
return TestModel.find(
{
title: { $in: ['标题2', '标题3'] }
},
{
_id: 0,
title: 1,
contents: 1,
comments: 1,
score: 1
}
)
})
.then(data => {
console.log('$in查找成功:\n' + data);
//当返回的是model实例时;
//保留原有的大部分API;
//调用toString方法返回的是model._doc;
//model.已定义字段名=值 <=> model._doc.已定义字段名=值
//model.未定义字段名=值 <=> model.未定义字段名=值
return TestModel.findOne(
{
title: '标题3'
},
{
_id: 0,
title: 1,
contents: 1,
comments: 1,
score: 1
}
)
})
.then(data => {
data.verify = true;
data.score = 20;
//调用lean()后返回值将变成普通的对象,不再拥有model的API;
console.log('添加属性1:\n', data);
return TestModel.findOne(
{
title: '标题3'
},
{
_id: 0,
title: 1,
contents: 1,
comments: 1,
score: 1
}
).lean(true)
})
.then(data => {
data.verify = true;
data.score = 20;
console.log('添加属性2:\n', data);
return TestModel.findOne(
{
title: '标题3'
}
)
})
.then(data => {
data._doc.verify = true;
data.score = [5, 25];
console.log('添加属性3:\n', data);
// findOne如果使用了第二参数,返回的mondel保存时会报错;
return data.save()
})
.then(data => {
console.log('保存属性:\n', data);
/*
$where:function(){ js代码 }
注意事项:
1.效率很低,, MongoDB需要把文档转成json然后执行js逻辑;
2.外层不能使用箭头函数;不然this所指上下文会不正确;
3.this指代的是游标当前位置的文档;
*/
return TestModel.find(
{
'$where': function () {
if (this.comments.length == 4) {
return true
} else {
return false
}
}
}
)
})
.then(data => {
console.log('$where成功:\n', data)
// 如果字段是一个混合类型时要注意;数组的匹配;
// 25<?<20 理论上是匹配不到文档;
// 实际上mongodb执行的是两个查询然后将结果取交集;
return TestModel.find({
score: { $gte: 25, $lte: 20 }
})
})
.then(data => {
// 示例中5<=20 && 25>=25 所以会匹配到该文档
console.log('数组查询1:', data)
//采用$elemMatch对数组中的每个元素进行匹配;注:只能对数组进行匹配;如果这个字段不是数组,将匹配不成功
return TestModel.find({ score: { $elemMatch: { $gte: 5, $lte: 20 } } }) //score:[16,18] √ score:15 ×
})
.then(data => {
console.log('$elemMatch成功:\n', data)
// return TestModel.find({}, {}, { skip: 1, limit: 2 })
// skip进行游标操作时, 不要跳过大量的文档,这样会很慢;因为MongoDB实际上是一个一个找到后在过滤掉的;
return TestModel.find().skip(1).limit(2)
})
.then(data => {
console.log('skip/limit成功:\n', data)
//查询值为null的字段;MongoDB中null==null为true;不存在的字段也为null;
return TestModel.find({ attr: null })
})
.then(data => {
console.log('null查询成功:\n', data)
//查询值为null且存在时;
return TestModel.find({ attr: { $in: [null], $exists: true } })
})
.then(data => {
console.log('$exists/null查询成功:\n', data)
//插入数据
return TestModel.update(
{ title: '标题4' },
{
contents: '内容5',
comments: [
{
author: 'qqq1',
comment: '好',
score: 5
}
],
score: ['a', 'b', 'c']
},
{ upsert: 1 }
)
})
.then(data => {
console.log('upsert插入成功:\n', data)
return TestModel.create(
{
title: '标题5',
contents: '内容5',
comments: [
{
author: 'qqq1',
comment: '好',
score: 5
}
],
score: ['a', 'b', 'd']
}
)
})
.then(data => {
console.log('create创建成功:\n', data)
//针对数组查询
//示例中的写法等价于$in;匹配时数组中有一个元素在其中,就满足条件;
return TestModel.find({ score: ['c', 'd'] })
})
.then(data => {
console.log('查询1:\n', data)
//匹配时数组中满足两个条件才能匹配;
return TestModel.find({ score: { $all: ['c', 'd'] } })
})
.then(data => {
console.log('查询2:\n', data)
//匹配时数组中满足两个条件才能匹配;
return TestModel.find({ score: { $all: ['a', 'd'] } })
})
.then(data => {
console.log('查询3:\n', data)
// find的第二参数可以配合使用$slice对数组进行操作;
// 示例:跳过前面2个取4个;
return TestModel.findOne({ title: '标题1' }, { comments: { $slice: [2, 4] } })
})
.then(data => {
console.log('查询4:\n', data)
return TestModel.distinct('contents')
})
.then(data => {
console.log('查询5:\n', data)
//聚合:通过管道进行筛选/投射/分组/排序/限制/跳过
//管道操作的数据保存在内存中;
return TestModel.aggregate([
{
$unwind: '$comments'
}
])
})
.then(data => {
console.log('管道操作1:\n', data)
return TestModel.aggregate([
{
$project: { 标题: '$title', 评论: '$comments' }
}
])
})
.then(data => {
console.log('管道操作2:\n', data)
return TestModel.aggregate([
{
$group: {
_id: '$contents',
标题: { $addToSet: "$title" }
}
}
])
})
.then(data => {
console.log('管道操作3:\n', data)
return TestModel.aggregate([
{
$match: {
title: { $in: ['标题2', '标题3', '标题4', '标题5'] }
}
},
{
$unwind: '$comments'
},
{
$project: {
标题: { $concat: ['这是标题:', { $substr: ['$title', 6, 4] }] },//$substr以字节为单位!!!
内容: '$contents',
评论: '$comments',
评分: { $cond: ['$comments.score', '$comments.score', 0] }
}
},
{
$group: {
_id: '$标题',
score: { $sum: '$评分' },
contents: { $addToSet: '$内容' }
}
}
])
})
.then(data => {
console.log('管道操作4:\n', data)
return Promise.all([
Class.insertMany([
{
class: '一年级',
teacher: '刘老师',
course: ['一年级语文', '一年级数学', '一年级自然']
},
{
class: '二年级',
teacher: '李老师',
course: ['二年级语文', '二年级数学', '二年级自然']
},
{
class: '三年级',
teacher: '伍老师',
course: ['三年级语文', '三年级数学', '三年级自然']
},
]),
Student.insertMany([
{
name: '小花',
age: 6,
hobby: ['跳舞', '唱歌'],
class: '一年级'
},
{
name: '小明',
age: 7,
hobby: ['游泳'],
class: '二年级'
},
])
])
})
.then(data => {
return Student.aggregate([
{
$lookup:
{
from: "classes",
localField: "class",
foreignField: "class",
as: "myClass"
}
}
])
})
.then(data => {
console.log('管道操作5:\n', data)
})
.catch(err => {
console.log('发生错误', err)
})
})