Mongodb基础

1,179 阅读24分钟

Mongodb基础一:数据库创建删除、集合创建删除、数据增删改查

mongodb中文网

1. 数据库使用

  • ./mongod --dbpath DB_PATH开启mongodb服务,然后./mongo开启客户端;
  • 清屏: cls
  • 查看所有数据库列表: show dbs

2. 创建数据库

使用数据库、创建数据库:

> use joyitsai

如果真的想把这个数据库创建成功,那么必须插入一个数据。 数据库中不能直接插入数据,只能往集合(collections)中插入数据。

不需要专门创建集合,只需要写点语法插入数据就会创建集合, 系统发现student是一个陌生的集合名字,所以就自动创建了集合:

> db.student.insert({'name': 'xiaoming'});

显示当前数据库中所有集合(mysql中叫表):

> show collections;

删除当前所在的数据库:

> db.dropDatabase();

删除指定集合(表): db.COOLECTION_NAME.drop(),例如删除上面的student集合:

> db.student.drop();

3. 插入数据:

插入数据,随着数据的插入,数据库创建成功了,集合也创建成功了:

db.集合名.insert({"name":"zhangsan"}); 

4. 数据查找

查询userinfo集合中所有数据:

> db.userinfo.find(); /*相当于select * from userinfo;*/

查询去重后的字段数据:

> db.userinfo.distinct('name');

过滤掉name中的相同数据,相当于:select distict name from userinfo;

查询age = 22的记录:

db.userInfo.find({"age": 22}); 

相当于: select * from userInfo where age = 22;

查询age > 22的记录:

 db.userInfo.find({age: {$gt: 22}}); 

相当于:select * from userInfo where age >22;

查询age < 22的记录 :

> db.userInfo.find({age: {$lt: 22}}); 

查询age >= 25的记录:

> db.userInfo.find({age: {$gte: 25}}); 

相当于:select * from userInfo where age >= 25;

查询age <= 25的记录:

db.userInfo.find({age: {$lte: 25}}); 

查询name中包含 mongo的数据(模糊查询):

 > db.userInfo.find({name: /mongo/});

相当于select * from userInfo where name like ‘%mongo%’;

查询name中以mongo开头的 数据:

> db.userInfo.find({name: /^mongo/}); 

相当于select * from userInfo where name like ‘mongo%’;

查询指定字段(name、age)数据:

> db.userInfo.find({}, {name: 1, age: 1}); 

相当于:select name, age from userInfo;,当然name也可以用true或false,当用ture的情况下和name:1效果一样,如果用false就是排除name,显示name以外的列信息。

查询指定字段(name、age)数据且 age > 25 :

> db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1}); 

相当于:select name, age from userInfo where age >25;

按照年龄排序 :

升序:

 db.userInfo.find().sort({age: 1}); 

降序:

> db.userInfo.find().sort({age: -1}); 

查询name = zhangsan, age = 22的数据:

 > db.userInfo.find({name: 'zhangsan', age: 22}); 

相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22’;

查询前5条数据:

 db.userInfo.find().limit(5); 

相当于:selecttop 5 * from userInfo;

查询10条以后的数据:

db.userInfo.find().skip(10); 

相当于:select * from userInfo where id not in ( selecttop 10 * from userInfo );

查询在5-10之间的数据:

db.userInfo.find().limit(10).skip(5); 

可用于分页,limit是pageSize,skip是第几页*pageSize

or查询:

db.userInfo.find({$or: [{age: 22}, {age: 25}]}); 

相当于:select * from userInfo where age = 22 or age = 25;

findOne 查询第一条数据:

 db.userInfo.findOne(); 

相当于:selecttop 1 * from userInfo;或者db.userInfo.find().limit(1);

查询某个结果集的记录条数 统计数量:

db.userInfo.find({age: {$gte: 25}}).count();

相当于:select count(*) from userInfo where age >= 20;

4. 数据的修改:

4.1一般查找修改语法:

db.集合名.update({查询条件},{set{修改的数据}});其中的set关键字是用来设置要修改的数据。如果没有用$set,那么整条数据会被重新覆盖成新设置的数据。

查找名字叫做小明的,把年龄更改为16岁:

> db.student.update({"name":"小明"},{$set:{"age":16}});

查找数学成绩是70,把年龄更改为33岁:

> db.student.update({"math":70},{$set:{"age":33}});

4.2 更改所有匹配项目:

默认的.update()只能更新一条数据,如果你需要更新所有与条件匹配的数据,就得加上multi选项。例如吧所有性别为男的数据将age更新为33:

> db.student.update({"sex":"男"},{$set:{"age":33}},{multi: true});

4.3 没有使用$set关键字,原来的数据被新数据覆盖掉:

db.student.update({"name":"小明"},{"name":"大明","age":16});

5. 删除数据

db.collectionsNames.remove( { 查询条件 } )

默认的,.remove()方法会将匹配到的所有数据都删除掉,如果只想删除匹配到的第一条数据,使用 justOne:true:

Mongodb基础二:索引原理与使用

1. 索引的原理?

索引是对数据库表中一列或多列的值进行排序的一种数据结构,可以让我们查询数据库变得更快。为了方便后续介绍,先科普下MongoDB里的索引机制(同样适用于其他的数据库比如mysql)。

> db.person.find()
{ "_id" : ObjectId("571b5da31b0d530a03b3ce82"), "name" : "jack", "age" : 19 }
{ "_id" : ObjectId("571b5dae1b0d530a03b3ce83"), "name" : "rose", "age" : 20 }
{ "_id" : ObjectId("571b5db81b0d530a03b3ce84"), "name" : "jack", "age" : 18 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce85"), "name" : "tony", "age" : 21 }
{ "_id" : ObjectId("571b5dc21b0d530a03b3ce86"), "name" : "adam", "age" : 18 }

当你往某各个集合插入多个文档后,每个文档在经过底层的存储引擎持久化后,会有一个位置信息,通过这个位置信息,就能从存储引擎里读出该文档。为方便介绍,统一用pos(position的缩写)来代表位置信息。

比如上面的例子里,person集合里包含插入了5个文档,假设其存储后位置信息如下(为方便描述,文档省去_id字段)

位置信息文档
pos1{“name” : “jack”, “age” : 19 }
pos2{“name” : “rose”, “age” : 20 }
pos3{“name” : “jack”, “age” : 18 }
pos4{“name” : “tony”, “age” : 21}
pos5{“name” : “adam”, “age” : 18}

假设现在有个查询 db.person.find( {age: 18} ), 查询所有年龄为18岁的人,这时需要遍历所有的文档(全表扫描),根据位置信息读出文档,对比age字段是否为18。当然如果只有4个文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。

如果想加速 db.person.find( {age: 18} ),就可以考虑对person表的age字段建立索引。

db.person.createIndex( {age: 1} )  // 按age字段创建升序索引

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,索引结构类似如下,索引通常采用类似btree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

AGE位置信息
18pos3
18pos5
19pos1
20pos2
21pos4

简单的说,索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。

众所周知,MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引。

> db.person.getIndexes() // 查询集合的索引信息
[
    {
        "ns" : "test.person",  // 集合名
        "v" : 1,               // 索引版本
        "key" : {              // 索引的字段及排序方向
            "_id" : 1           // 根据_id字段升序索引
        },
        "name" : "_id_"        // 索引的名称
    }
]

2. MongoDB索引的基本使用

2.1 单字段索引

创建单字段索引:

> db.user.ensureIndex({name: 1})

上述语句针对name创建了单字段索引,其能加速对name字段的各种查询请求,是最常见的索引形式,MongoDB默认创建的id索引也是这种类型。

{name: 1}代表升序索引,也可以通过{name: -1}来指定降序索引,对于单字段索引,升序/降序效果是一样的。

查看当前集合的所有索引信息:

> db.user.getIndexes()

删除单字段索引:

db.user.dropIndex({name: 1});

2.2 复合索引:

数字1表示name键的索引按升序存储,-1表示age键的索引按照降序方式存储。

db.user.ensureIndex({"name":1, "age":-1})

该索引被创建后,基于name和age的查询将会用到该索引,或者是基于name的查询也会用到该索引,但是只基于age的查询将不会用到该复合索引。因此可以说,如果想用到复合索引,必须在查询条件中包含复合索引中的前N个索引列。

如果查询条件中的键值顺序和复合索引中的创建顺序不一致的话,MongoDB可以智能的帮助我们调整该顺序,以便使复合索引可以为查询所用。如:

 db.user.find({"age": 30, "name": "stephen"})

对于上面示例中的查询条件,MongoDB在检索之前将会动态的调整查询条件的顺序,以使该查询可以用到刚刚创建的复合索引。

对于上面创建的索引,MongoDB都会根据索引的keyname和索引方向为新创建的索引自动分配一个索引名,下面的命令可以在创建索引时为其指定索引名(userindex),如:

db.user.ensureIndex({"name":1},{"name":"userindex"})

2.3 唯一索引

在缺省{unique:true}的情况下创建的索引均不是唯一索引。下面将创建唯一索引,如:

 db.user.ensureIndex({"userid":1},{"unique":true})

如果再次插入userid重复的文档时,MongoDB将报错,以提示插入重复键,如:

db.user.insert({"userid":5}) 
db.user.insert({"userid":5})

E11000 duplicate key error index: user.user.$userid_1 dup key: { : 5.0 }

如果插入的文档中不包含userid键,那么该文档中该键的值为null,如果多次插入类似的文档,MongoDB将会报出同样的错误:

E11000 duplicate key error index: user.user.$userid_1 dup key: { : null }

3. 使用explain

explain是非常有用的工具,会帮助你获得查询方面诸多有用的信息:

explain会返回查询使用的索引情况,耗时和扫描文档数的统计信息。

3.1 explain( 'executionStats' )查询具体的执行时间

db.tablename.find().explain( "executionStats" )

输出的如下数值:explain.executionStats.executionTimeMillis就是当前数据查询所用的时间

参考《MongoDB索引原理》

Mongodb基础三:mongoose实现数据增删改查

菜鸟教程

添加权限控制

mongoose 的安装以及使用

安装

npm install mongoose --save

引入mongoose并连接数据库

const mongoose = require('mongoose');
mongoose.connect('mongodb://jzcms:jzcms@152.136.136.13:27017/jzCms',(err)=>{
  if(err){
    console.log(err)
    console.log('数据库链接失败')
  }else{
    console.log('数据库连接成功');
    console.log('服务器启动成功');
    server.listen(port);
    server.on('error', onError);
    server.on('listening', onListening);
  }
})

如果mongodb数据库有账号密码:

/**通过mongodb://username:password@host1:port/database_name?authSource指定验证前面身份信息的数据库来源 */
mongoose.connect('mongodb://账号:密码@ip地址:27017/集合名称?authSource=admin',{ 
    useNewUrlParser: true,
});

定义一个Schema

Schema是mongoose 里会用到的一种数据模式,可以理解为表结构的定义;每个schema 会映射到mongodb 中的一个collection,它不具备操作数据库的能力,只是对数据库集合的各个字段类型的规范定义。

<!--独立-->
const mongoose = require('mongoose');
module.exports = new mongoose.Schema({
    title: String,
    shareTitle: String,
    shareImgUrl: String,
    shareDesc: String,
    content: Array
})

<!--非独立-->
var UserSchema=mongoose.Schema({
    name: String,
    age:Number,
    status:'number'
})

创建数据模型

  • 定义好了Schema,接下就是生成Model。model 是由schema 生成的模型,可以对数据库的操作。
  • mongoose.model 里面可以传入两个参数也可以传入三个参数:
  • mongoose.model(参数1:模型名称(首字母大写),参数2:Schema)
  • mongoose.model(参数1:模型名称(首字母大写),参数2:Schema,参数3:集合名称)
  • 如果传入2 个参数的话:User模型会默认去操作与User模型同名的复数形式的集合,如通过下面方法创建模型,那么这个模型将会操作users这个集合:
var User=mongoose.model('User', UserSchema);

如果传入3 个参数的话:模型默认操作第三个参数定义的集合名称,如下面的代码,模型User会指定操作user这个集合:

var User=mongoose.model('User', UserSchema, 'user');

// 模型类 用于对用户的表进行操作
const mongoose = require('mongoose');

const taobaoSchema = require('../schemas/taobao');

// 创建一个模型
module.exports = mongoose.model('taobao',taobaoSchema, 'taobao');

增删改查

查找数据

/**模型.find({查询条件}, (err, data)=>{回调函数}) */
User.find({}, (err, docs)=>{
    if(err){
        console.log(err);
        return;
    }
    console.log(docs);
});

增加数据

/*模型实例化*/
const news = new News({
    title:'新闻标题',
    author:'joyitsai',
    pic:'01.png',
    content:'新闻内容',
    status: 1
});
/*实例.save((err,docs)=>{回调函数}) 来将数据保存到数据库*/
news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

修改数据

ews.updateOne(
    {'_id':'5cf5e613ba3c6298a8734973'}, //条件
    {title: '这是一则新闻111'},     //要更新的内容
    /*回调函数*/
    (err, docs)=>{
        if(err){return console.log('更新数据失败');}
        console.log(docs);
    }
)

删除数据

News.deleteOne(
    {'_id':'5cf5e613ba3c6298a8734973'}, //查找条件
    /*回调函数*/
    (err,docs)=>{
        if(err){return console.log('删除数据失败')}
        console.log(docs);
    }

)

保存成功查找

News.updateOne(
    {'_id':'5cf5e613ba3c6298a8734973'}, //条件
    {title: '这是一则新闻111'},     //要更新的内容
    (err, docs)=>{
        if(err){
            return console.log('更新数据失败');
        }
        /**更新数据成功,紧接着查询数据 */
        News.find({},(err, docs)=>{
            if(err){
                return console.log(err)
            }
            console.log(docs);
        })
    }
)

MongoDB基础四:mongoose设置默认值、模块化及性能测试

1. mongoose设置默认值

1.1 Schema中未定义的字段,其数据不能被添加

在上一篇文章里说到,首先要定义一个Schema,作为对数据库集合的各个字段类型的规范定义,此外,在添加数据时,如果添加的数据字段没有在Schema中定义,那么该字段数据不会被添加到集合里:

/*定义Schema*/
const NewsSchema = mongoose.Schema({
    title: String,
    author: String,
    pic:String,
    content: String,
    status: Number
});

/*定义News模型*/
const News = mongoose.model('News', NewsSchema, 'news');

/*实例化模型,添加数据*/
const news = new News({
    title:'最新新闻',
    author:'joyitsai',
    pic:'03.png',
    content:'新闻内容',
    status: 1,
    views: 300
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

上面再添加数据时,views:200字段的数据再Schema中并没有定义,那么再添加数据时,这个字段的数据并不会被加到集合中,查看一下news集合的数据

> db.news.find()
{ "_id" : ObjectId("5cf62d5333e8ab297050eb97"), "title" : "最新新闻", "author" : "joyitsai", "pic" : 
"03.png", "content" : "新闻内容", "status" : 1, "__v" : 0 }

1.2 Schema定义时,指定默认值:

在Schema中定义字段的默认值,添加数据时,如果该字段数据没有被添加,那么mongoose会自动使用Schema中该字段的默认值作为数据添加到集合里:

/*定义Schema*/
const NewsSchema = mongoose.Schema({
    title: String,
    author: String,
    pic:String,
    content: String,
    /*指定字段的type、默认值*/
    status: {
        type: Number,
        default: 1
    }
});

const News = mongoose.model('News', NewsSchema, 'news');

/*添加数据,不添加status字段时,会自动使用默认值添加*/
const news = new News({
    title:'最新新闻007',
    author:'joyitsai',
    pic:'07.png',
    content:'新闻内容007'
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

查看刚刚添加的数据,会发现数据中有status:1字段:

> db.news.find()
{ "_id" : ObjectId("5cf630516b0b333d40a59f31"), "status" : 1, "title" : "最新新闻007", "author" : 
"joyitsai", "pic" : "07.png", "content" : "新闻内容007", "__v" : 0 }

2. mongoose模块化:

在每次使用mongoose来操作数据库时,都要写连接数据库、定义Schema、定义模型,显得很繁琐。那么现在就用模块化来解决这个问题。

简单说,就是把连接数据库、定义Schema、定义模型都写成独立的模块,在相应功能代码中直接引入模块,进行数据库操作即可。

2.1 连接数据库——模块化:

// db.js

const mongoose = require('mongoose');
mongoose.connect(
    'mongodb://127.0.0.0:27017/databaseName',
    {useNewUrlParser: true,},
    (err)=>{
        if(err){
            console.log('数据库连接失败');
            return;
        }
    }  
)
module.exports = mongoose;

2.2 定义Schema,定义模块

在定义Schema和模块的代码中导入上面连接成功后的mongoose模块:

// news.js
const mongoose = require('./db');
// 定义Schema
const NewsSchema = mongoose.Schema({
    title: String,
    author: String,
    pic:String,
    content: String,
    status: {
        type: Number,
        default: 1
    }
});

// 导出News模型
const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

2.3 引入news.js模型,直接操作数据库:

//insertData.js
const News = require('./myDatabase/news');

const news = new News({
    title:'最新新闻007',
    author:'joyitsai',
    pic:'07.png',
    content:'新闻内容007'
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

以上就实现了对mongoose连接数据库、定义Schema和定义模型的基本模块化就完成了。

3. 模块化性能测试

现在有人可能会对这种模块化的性能产生疑问,因为如果要对多个集合的数据进行操作,必然要在同一个文件中导入多个模型。

然而,每次导入一个模型,上层都会导入一次数据库连接的模块,会不会导致导入多个模型时数据库重复连接,而严重影响程序执行性能

其实,mongoose在内部给我们解决了这个问题,即使在一次性导入多个模型,只由第一次导入时的模块担任数据库连接工作,下面就测试一下:

//假设又封装了一个User集合模型
console.time('news')
const News = require('./myDatabase/news');

const news = new News({
    title:'最新新闻007',
    author:'joyitsai',
    pic:'07.png',
    content:'新闻内容007'
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});
// 测试向news集合中添加数据的时间
console.timeEnd('news');


console.time('user')
const User = require('./myDatabase/user');

const user = new User({
    name:'zhangsan',
    age: 36,
    pic:'07.png',
});

user.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});
// 测试向user集合中添加数据的时间
console.timeEnd('user');

下面看一下测试结果:

news: 341.740ms
user: 2.666ms
{ status: 1,
  _id: 5cf67e2fc3aa2254349ed978,
  title: '最新新闻007',
  author: 'joyitsai',
  pic: '07.png',
  content: '新闻内容007',
  __v: 0 }
{ status: 1,
  _id: 5cf67e2fc3aa2254349ed979,
  name: 'zhangsan',
  age: 36,
  pic: '07.png',
  __v: 0 }

很显然,在第一次引入News模型的用时是第二次引入User模型的150倍,这验证了在多次引入模型的情况下,并不会重复连接数据库。

MongoDB基础五:Mongoose 预定义模式修饰符、Setters与Getters自定义修饰符

一、mongoose 预定义模式修饰符

mongoose 提供的预定义模式修饰符,可以对我们增加的数据进行一些格式化。mongoose提供了lowercase、uppercase 、trim预定义模式修饰符,其中trim能够对字段数据做两端去空格的格式化:

/*定义带trim修饰符的Schema*/
const NewsSchema = mongoose.Schema({
    title: {
        type:String,
        trim: true
    },
    author: String,
    pic:String,
    content: String,
    status: {
        type: Number,
        default: 1
    }
});
const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;
const News = require('./myDatabase/news');
/*添加数据时,将title字段的数据两端加一些空格*/
const news = new News({
    title:'     最新新闻007     ',
    author:'joyitsai',
    pic:'07.png',
    content:'新闻内容007'
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

添加数据成功后,查看发现数据如下:

{ "_id" : ObjectId("5cf72b415b4e487a34c85cb4"), "status" : 1, "title" : "最新新闻007", "author" : 
"joyitsai", "pic" : "07.png", "content" : "新闻内容007", "__v" : 0 }

你会发现,刚刚添加的数据中title字段数据两端的空格被格式化去掉了,就是trim的作用。

二、Mongoose Getters 与Setters 自定义修饰符

除了mongoose 内置的修饰符以外,我们还可以通过set(建议使用) 修饰符在增加数据的时候对数据进行格式化。也可以通过get(不建议使用)在实例获取数据的时候对数据进行格式化。

2.1 Setters

set(用户添加数据){ return 真正添加到数据库的数据}来自定义修饰符,在set内部对用户添加的数据进行格式化操作,最终返回格式化后的数据作为真正存入数据库的数据:

const NewsSchema = mongoose.Schema({
    title: {
        type:String,
        trim: true
    },
    author: String,
    /*
      url为空,则返回空的url;
      url前面没有`http://`或`https:// `,则在前面拼接上`http://`
      返回带有`http://`或`https://`的url,作为存入数据库的数据
    */
    redirect: {
        type: String,
        set(url){
            if(!url) return url;
            if(url.indexOf('http://')!=0 && url.indexOf('https://')!=0){
                url = 'http://' + url;
            }
            return url;
        }
    } 
});

const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

添加数据时,向redirect字段添加一个www.baidu.com,然后查看存入数据库的数据是否是www.baidu.com,如果是,说明set自定义修饰符有效了:

const News = require('./myDatabase/news');

const news = new News({
    title:'     最新新闻007     ',
    author: 'joyitsai',
    redirect: 'www.baidu.com'
});

news.save((err, docs)=>{
    if(err){
        console.log('添加数据出错');
        return;
    }
    console.log(docs);
});

查看一下数据:

{ "_id" : ObjectId("5cf731f8d8bda03afc8367b7"), "title" : "最新新闻007", "author" : "joyitsai", 
"redirect" : "http://www.baidu.com", "__v" : 0 }

set自定义修饰符有效了。

2.2 Getters

get将用户添加的数据格式化后,并没有存入数据库;仅仅是用户在实例化模型时,这个实例中的数据被做了格式化操作,在常规应用中意义不大。

MongoDB基础六:Mongoose 索引、内置CURD 方法、扩展静态方法和实例方法

一、Mongoose 索引

索引是对数据库表中一列或多列的值进行排序的一种结构,可以让我们查询数据库变得更快。

1.1 创建索引

mongoose 中除了以前创建索引的方式,我们也可以在定义Schema时创建索引:

const NewsSchema = mongoose.Schema({
    news_id:{
        type:Number,
        // 唯一索引
        unique: true
    },
    title: {
        type:String,
        // 普通索引
        index: true
    },
    author: String,
});

const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

上面代码中,通过在Schema的字段中定义unique: true创建唯一索引,和index: true创建一般索引。

1.2 测试索引查询数据的性能

首先看一下没有索引时(将上面代码中unique: true和index: true注释掉)查询数据的时间(news集合中有100万条数据):

console.time('news');
News.find({title: '新闻200000'}, (err, docs)=>{
    if(err) return console.log(err);
    console.log(docs);
    console.timeEnd('news');
})

查询用时:

[ { _id: 5cf7795fb1f4664f499f265c,
    news_id: 200000,
    title: '新闻200000',
    author: 'joyitsai' } ]
news: 469.795ms

当依照上面创建索引之后,再对这条数据进行查询,查询用时:

[ { _id: 5cf77921b1f4664f499c673c,
    news_id: 20000,
    title: '新闻20000',
    author: 'joyitsai' } ]
news: 92.108ms

在mongoose中使用索引查询数据的性能对于没有索引的情况下有了5倍左右的提升,但没有在mongo命令行中查询时的性能好。

二、Mongoose 内置CURD

CURD: CURD是一个数据库技术中的缩写词,一般的项目开发的各种参数的基本功能都是CURD。作用是用于处理数据的基本原子操作。

Model.deleteMany()
Model.deleteOne()
Model.find()
Model.findById()
Model.findByIdAndDelete()
Model.findByIdAndRemove()
Model.findByIdAndUpdate()
Model.findOne()
Model.findOneAndDelete()
Model.findOneAndRemove()
Model.findOneAndUpdate()
Model.replaceOne()
Model.updateMany()
Model.updateOne()

三、扩展Mongoose CURD 方法

3.1 在Schema上自定义静态方法:

在定义的Schema上通过Schema.statics.yourFind封装自己的数据查找方法,方便后期数据查找工作。callback为数据查找完成后的回调函数,回调函数中可以进行错误处理或者数据处理等操作:

const NewsSchema = mongoose.Schema({
    news_id:{
        type:Number,
        // 唯一索引
        unique: true
    },
    title: {
        type:String,
        // 普通索引
        index: true
    },
    author: String
});

// 通过Schema来自定义模型的静态方法,自定义数据查找的方法
NewsSchema.statics.findByNewsId = function(news_id, callback){
    //this指向当前模型,调用模型上的find()方法,封装自己的静态方法
    this.find({news_id: news_id}, function(err, docs){
        //数据查找完成后,调用callback,对错误信息或者查找到的数据进行处理
        callback(err, docs);
    })
}

const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

然后在相关功能代码中,就可以通过News.findByNewsId()来查找数据了:

News.findByNewsId(20000, (err, docs)=>{
    if(err) return console.log(err);
    console.log(docs);
});

查找结果如下:

[ { _id: 5cf77921b1f4664f499c673c,
    news_id: 20000,
    title: '新闻20000',
    author: 'joyitsai' } ]

3.2 在Schema上自定义实例方法:

实例方法是通过Schema.methods.yourMethod来定义的,其中this指向了NewsSchema对应模型的实例:

NewsSchema.methods.print = function(){
    console.log('这是一个实例方法');
    console.log(this.news_id);
}

调用实例方法:

// 对News模型实例化
const news = new News({
    news_id: 1,
    title: '新闻1',
    author: 'joyitsai'
})

//在实例上调用实例方法
news.print();

调用实例方法后的结果对应了实例方法的定义:

这是一个实例方法
新闻1

实例方法不太常用,但如果你在项目中需要对数据模型实例化之后,进行一些特殊的操作,可以通过实例化方法来执行,提高功能性操作的效率。

MongoDB基础七:Mongoose 数据校验

一、Mongoose 校验参数

在定义Schema时,可以设置数据的校验参数,这样可以对操作数据库数据时,对其做进一步的规范。主要的校验参数有:

  • ==required== : 数据可以是任意类型,表示这个数据必须传入
  • ==max==: 用于Number 类型数据,最大值
  • ==min==: 用于Number 类型数据,最小值
  • ==enum==:枚举类型(数组中的类型是String),要求数据必须满足枚举值enum: ['0', '1', '2']
  • ==match==:增加的数据必须符合match(正则)的规则
  • ==maxlength==:数据必须是String类型,数据最大长度
  • ==minlength==:数据必须是String类型,数据最小长度

1.1 required示例:

// 定义news_id是必须要传入的数据
const NewsSchema = mongoose.Schema({
    news_id:{
        type:Number,
        // 唯一索引
        unique: true,
        required: true
    },
    title: {
        type:String,
        // 普通索引
        index: true
    },
    author: String
});

const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

现在我们在news集合中添加数据时,如果没有传入news_id的字段数据,就会报错:

const news = new News({
    /*此处没有传入news_id数据*/
    title: '新闻1',
    author: 'joyitsai'
})

news.save((err, docs)=>{
    if(err) return console.log(err);
    console.log(docs);
})

运行报错:

{ ValidationError: News validation failed: news_id: Path `news_id` is required.
  .....
}

1.2 max和min示例:

如果Schema定义时,指定max和min,那么传入的数据值必须在min和max之间,否则报错:

const NewsSchema = mongoose.Schema({
    news_id:{
        type:Number,
        // 唯一索引
        unique: true,
    },
    title: {
        type:String,
        // 普通索引
        index: true
    },
    author: String,
    words: {
        type: Number,
        min: 200,
        max: 1000
    }
});

const News = mongoose.model('News', NewsSchema, 'news');
module.exports = News;

现在如果添加数据时words为100:

const news = new News({
    news_id: 2,
    title: '新闻2',
    author: 'joyitsai',
    words: 100
})

news.save((err, docs)=>{
    if(err) return console.log(err);
    console.log(docs);
})

就会提示数据不能小于min的值,相反如果数据大于max也会提示相应错误:

{ ValidationError: News validation failed: words: Path `words` (100) is less than minimum allowed value (1000).
  ......
}

其他的几个参数就不一一举例了,请自行验证。

二、自定义校验:

类似于之前set的用法,在定义Schema时可以用validate关键字自定义一个校验器函数,来检测数据是否满足想要的规范,如果通过验证返回true,没有通过则返回false:

const NewsSchema = mongoose.Schema({
    news_id:{
        type:Number,
        // 唯一索引
        unique: true,
    },
    title: {
        type:String,
        // 普通索引
        index: true
    },
    author: String,
    content: {
        type: String,
        /*words的长度要大于8且小于20*/
        validate: function(words){
            return (words.length>=8 && words.length<=20)
        }
    }
});

添加数据时:

const news = new News({
    news_id: 2,
    title: '新闻2',
    author: 'joyitsai',
    words: 'qwert'
})

news.save((err, docs)=>{
    if(err) return console.log(err);
    console.log(docs);
})

提示报错:

{ ValidationError: News validation failed: words: Validator failed for path `words` with value `qwert`
  ......
}

如果添加的words数据长度在8-20之间,就没问题了:

{ _id: 5cfd15eacf0cee3f14f5bb85,
  news_id: 2,
  title: '新闻2',
  author: 'joyitsai',
  words: 'qwertyuio',
  __v: 0 }

MongoDB基础八:Mongodb自增id实现方法

本文实例讲述了Mongodb自增id实现方法,具体如下:

首先创建一个自动增长id集合 ids

>db.ids.save({name:"user", id:0});

可以查看一下是否成功

> db.ids.find();
{ "_id" : ObjectId("4c637dbd900f00000000686c"), "name" : "user", "id" : 0 }

然后每次在db.user集合里添加新用户之前,添加新用户之前自增一下 ids集合 获得id:将db.ids集合中的name="user"文档的id值加1,返回文档。

注:因为findAndModify是一个方法完成更新查找两个操作,所以具有原子性,多线程不会冲突。 然后保存相应的数据

>db.user.save({uid:userid.id, username:"dotcoo", password:"dotcoo"});

将上面两端代码合并:

>db.user.save({
  uid: db.ids .findAndModify({
    update:{$inc:{'id':1}},
    query:{"name":"user"},
    new:true}).id, 
  username: "dotcoo",
  password:"dotcoo"});

查询一下刚刚添加的用户数据:

> db.user.find();
{ "_id" : ObjectId("4c637f79900f00000000686d"), "uid" : 1, "username" : "admin", "password" : "admin" }

注意:

如果你是通过mongoose模块来进行上面的操作,应使用model.findOneAndUpdate()方法对ids集合的id进行自增,另外,在新版本中,需要在mongoose.connect()时配置useFindAndModify: false,,具体原因如官方文档所提: image

在js项目中使用mongoose模块实现id自增的具体代码如下:

const IDs = require('./dataModel/ids');
const Adminuser = require('./dataModel/adminuser');

IDs.findOneAndUpdate({name: 'user'}, {$inc: {id: 1}}, {new: true,}, (err, docs)=>{
    if(err) return err;
    console.log(docs);
    const adminuser = Adminuser({
        user_id: docs.id,
        username: 'admin'+docs.id,
        password: '12345678'
    });
    adminuser.save((err, docs)=>{
        if(err) return err;
        console.log(docs);
    })
})

{new: true}选项,会将更新后的文档输出。

以守护进程方式(Linux系统),开机自启动Mongodb服务

1. 编辑mongodb的配置文件:

修改/opt/mongo/bin/mongodb.conf配置文件,在/etc/init.d中编写自启动脚本时,需要让mongodb按照配置文件来启动和关闭:

 /*数据存放目录*/
 dbpath=/opt/mongo/data/db   
 /*指定日志文件,该文件将保存所有的日志记录、诊断信息*/
 logpath=/opt/mongo/log/mongodb.log   
 /*进程ID,没有指定则启动时候就没有PID文件*/
 pidfilepath=/opt/mongo/db.pid   
//设置为true,修改数据目录存储模式,每个数据库的文件存储在DBPATH指定目录的不同的文件夹中。
//使用此选项,可以配置的MongoDB将数据存储在不同的磁盘设备上,以提高写入吞吐量或磁盘容量。
 directoryperdb=true    
//写日志的模式:设置为true为追加。默认是覆盖。如果未指定此设置,启动时MongoDB的将覆盖现有的日志文件。
 logappend=true
//绑定地址
 bind_ip= localhost
 /*端口。默认27017*/
 port=27017   
//是否后台运行,设置为true  启动进程在后台运行的守护进程模式。
 fork=true
//来禁用预分配的数据文件,会缩短启动时间,但在正常操作过程中,可能会导致性能显著下降。
 noprealloc=true
 //设置为true,使用较小的默认数据文件大小。smallfiles减少数据文件的初始大小,并限制他们到512M,也减少了日志文件的大小,
//并限制他们到128M。如果数据库很大,各持有少量的数据,会导致mongodb创建很多文件,会影响性能。
 smallfiles=true
  1. 创建mongodb服务脚本

文件夹/etc/init.d/是用来放服务脚本的,当Linux启动时,会寻找这些目录中的服务脚本,并根据脚本的run level确定不同的启动级别。

在/etc/init.d/中创建mongodb服务脚本文件,编辑内容如下

#!/bin/bash
 #
 #chkconfig: 2345 80 90
 #description: mongodb

 start() {
  /opt/mongo/bin/mongod --config /opt/mongo/bin/mongodb.conf
 }

 stop() {
   /opt/mongo/bin/mongod --config /opt/mongo/bin/mongodb.conf --shutdown
 }
 
case "$1" in
   start)
  start
  ;;

 stop)
  stop
  ;;

 restart)
  stop
  start
  ;;
   *)
  echo 
 $"Usage: $0 {start|stop|restart}"
  exit 1
 esac

上面脚本,是让mongodb脚本服务,可以通过service mongodb start来实现mongod服务按照配置文件启动;通过service mongodb stop来实现mongod服务按照配置文件停止;还有service mongodb restart实现mongod服务重启。

增加服务并开机启动:

chmod +x /etc/init.d/mongodb
chkconfig --add mongodb
chkconfig --level 345 mongodb on
chkconfig --list mongodb
service mongodb start

作者:自如大前端研发中心-FE研发部架构组-冯腾飞

招聘信息

自如大前端研发中心招募新同学!

FE/iOS/Android工程师

公司福利有:

  • 全额五险一金,并额外购买商业保险
  • 免费健身房+年度体检
  • 公司附近租房9折优惠
  • 每年2次晋升机会

欢迎对技术有执着热爱的你加入我们!简历请投递 zhangxl122@ziroom.com, 或加微信 v-nice-v 详聊!