mongodb 文档数据库,存储的是文档(Bson->json的二进制化),非关系型。

特点:内部执行引擎为JS解释器, 把文档存储成bson结构,在查询时,转换为JS对象,并可以通过熟悉的js语法来操作.

mongo和传统型数据库相比,最大的不同:

传统型数据库: 结构化数据, 定好了表结构后,每一行的内容,必是符合表结构的,就是说–列的个数,类型都一样.

mongo文档型数据库: 表下的每篇文档,都可以有自己独特的结构(json对象都可以有自己独特的属性和值), 缺点:事物和join。

优点: 支持丰富的查询能力,性能。 serverless的宠儿

安装

docker安装mongodb

1docker run -d --name mymongo -v /Users/maozhongyu/docker_data/mongo_data:/data/db mongo:4
2docker run -d --name mongodb -p 27017:27017  -v /Users/maozhongyu/docker_data/mongodata2:/data/db mongo:4

mongodb管理界面

1 docker run --link mymongo:mongo -p 8081:8081 mongo-express:0.54.0

mongodb vscode客户端

vscode 安装 MongoDB for VS Code插件

mongo 操作mongodb

mongo shell 客户端

1docker exec -it mymongo mongo

库表操作语句

数据库隐式创建

1show dbs #  查看库
2use test # 选择库
3show tables/collections # 看有哪些表
4
5db.help()
6db.dropDatabase()

crud

对象主键

  • 默认的文档主键
  • 可以快速生成的12字节id
  • 包含创建时间

collection 允许隐式创建

1> db.createCollection('user');
2{ "ok" : 1 }
3> show collections;
4account
5user

删除表

1
2> db.user.drop()   # 删除表 
3true
4> db.user.find();

插入一条

insertOne

1> db.user.insertOne({name:"lisi",age:22});
2WriteResult({ "nInserted" : 1 })
3> db.user.find();
4{ "_id" : ObjectId("65031ad0134a83215e86c1ee"), "name" : "lisi", "age" : 22 }

增加多个文档

1db.collectionName.insert(
2[
3{time:'friday',study:'mongodb'},
4{_id:9,gender:'male',name:'QQ'}
5]
6)

查找

 1
 2> db.user.insertOne({name:"crmao",age:23,hoppy:{"basketbal":20}});
 3WriteResult({ "nInserted" : 1 })
 4> db.user.find();
 5{ "_id" : ObjectId("65031ad0134a83215e86c1ee"), "name" : "lisi", "age" : 22 }
 6{ "_id" : ObjectId("65031b39134a83215e86c1ef"), "name" : "crmao", "age" : 23, "hoppy" : { "basketbal" : 20 } }
 7
 8
 9
10> db.test.insertOne({name:"crmao"});
11WriteResult({ "nInserted" : 1 })
12> db.test.findOne({name:"cr"})
13null

删除

1
2# 删除全部sn=001的
3 db.user.remove({sn:"001"}) 
4# true 只删除一行
5 db.user.remove({sn:"001"},true) 

更新

改 update操作

操作选项 —– 可选参数

语法: db.collection.update(查询表达式,新值,选项);

例:

db.news.update({name:‘QQ’},{name:‘MSN’});

是指选中news表中,name值为QQ的文档,并把其文档值改为{name:’MSN’},

结果: 文档中的其他列也不见了,改后只有_id和name列了.

即–新文档直接替换了旧文档,而不是修改

如果是想修改文档的某列,可以用$set关键字

db.collectionName.update(query,{$set:{name:’QQ’}})

修改时的赋值表达式

  • $set 修改某列的值
  • $unset 删除某个列
  • $rename 重命名某个列
  • $inc 增长某个列
  • $setOnInsert 当upsert为true时,并且发生了insert操作时,可以补充的字段.

Option的作用:

{upsert:true/false,multi:true/false}

Upsert—是指没有匹配的行,则直接插入该行.(和mysql中的replace一样)

例:db.stu.update({name:‘wuyong’},{$set:{name:‘junshiwuyong’}},{upsert:true});

如果有name=’wuyong’的文档,将被修改

如果没有,将添加此新文档

例:

db.news.update({_id:99},{x:123,y:234},{upsert:true});

没有_id=99的文档被修改,因此直接插入该文档

multi: 是指修改多行(即使查询表达式命中多行,默认也只改1行,如果想改多行,可以用此选项)

例:

db.news.update({age:21},{$set:{age:22}},{multi:true});

则把news中所有age=21的文档,都修改

 1> db.test.update({name:"crmao"},{name:"cr"});
 2WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
 3> db.test.find();
 4{ "_id" : ObjectId("6528074dac548ac3cbe95e0b"), "name" : "cr" }
 5
 6
 7
 8> db.test.insert({name:"crmao",age:100,hobby:"baasket",sex:"nan"});
 9db.test.update({name:"crmao"},{$set:{name:"crmmmm"},$unset:{hobby:"baasket"},$rename:{sex:"nan"}});
10
11
12
13> db.test.find()
14{ "_id" : ObjectId("6528074dac548ac3cbe95e0b"), "name" : "cr" }
15{ "_id" : ObjectId("65280c2aac548ac3cbe95e0c"), "name" : "crmmmm", "age" : 100, "nan" : "nan" }
16
17
18> db.test.update({name:"crmmmm"},{$set:{name:"crmao"},$unset:{hobby:"baasket"},$rename:{sex:"nan"},$inc:{age:100}});
19WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
20> db.test.find()
21{ "_id" : ObjectId("6528074dac548ac3cbe95e0b"), "name" : "cr" }
22{ "_id" : ObjectId("65280c2aac548ac3cbe95e0c"), "name" : "crmao", "age" : 200, "nan" : "nan" }

更新一条updateOne()

1db.test.updateOne({open_id:102},{$set:{open_id:110}})
2{
3    "acknowledged": true,
4    "insertedId": null,
5    "matchedCount": 1,
6    "modifiedCount": 1,
7    "upsertedCount": 0
8}

findAndModify

可以进行更新,插入, 并返回 老的或者旧数据, 很全能的一个函数

 1
 2db.test.findAndModify({
 3  query: { open_id:111},
 4  update:{$set:{open_id:112}},
 5  upset:true
 6})
 7找到返回老的记录, 没找到返回null 并进行插入操作
 8{
 9  "_id": {
10  "$oid": "654ef081130618ef248c666b"
11},
12  "open_id": 111
13}
14
15
16db.test.findAndModify({
17  query: { open_id:111},
18  update:{$set:{open_id:112}},
19  upset:true,
20  new:true  //默认false , 没有更新成功,则进行插入, 一直返回新的记录
21})

深入查询表达式

查: find, findOne

语法: db.collection.find(查询表达式,查询的列);

Db.collections.find(表达式,{列1:1,列2:1});

  • 例1:db.stu.find()

    查询所有文档 所有内容

  • 例2: db.stu.find({},{gendre:1})

    查询所有文档,的gender属性 (_id属性默认总是查出来)

  • 例3: db.stu.find({},{gender:1, _id:0})

    查询所有文档的gender属性,且不查询_id属性

  • 例4: db.stu.find({gender:’male’},{name:1,_id:0});

    查询所有gender属性值为male的文档中的name属性

1: 最简单的查询表达式

{filed:value} ,是指查询field列的值为value的文档

2: $ne — != 查询表达式

{field:{$nq:value}}

作用–查filed列的值 不等于 value 的文档

3: $nin –> not in

4: $all

语法: {field:{$all:[v1,v2..]}}

是指取出 field列是一个数组,且至少包含 v1,v2值

5: $exists

语法: {field:{$exists:1}}

作用: 查询出含有field字段的文档

6: $nor,

{$nor,[条件1,条件2]}

是指 所有条件都不满足的文档为真返回

7:用正则表达式查询 以”诺基亚”开头的商品

例:db.goods.find({goods_name:/诺基亚.*/},{goods_name:1});

8: 用$where表达式来查询

例: db.goods.find({$where:’this.cat_id != 3 && this.cat_id != 11’});

注意: 用$where查询时, mongodb是把bson结构的二进制数据转换为json结构的对象, 然后比较对象的属性是否满足表达式. 速度较慢

 1查出满足以下条件的商品
 21.1:主键为32的商品
 3 db.goods.find({goods_id:32});
 4
 51.2:不属第3栏目的所有商品($ne)
 6 db.goods.find({cat_id:{$ne:3}},{goods_id:1,cat_id:1,goods_name:1});
 7
 81.3:本店价格高于3000元的商品{$gt}
 9 db.goods.find({shop_price:{$gt:3000}},{goods_name:1,shop_price:1});
10
111.4:本店价格低于或等于100元的商品($lte)
12 db.goods.find({shop_price:{$lte:100}},{goods_name:1,shop_price:1});
13
141.5:取出第4栏目或第11栏目的商品($in)
15 db.goods.find({cat_id:{$in:[4,11]}},{goods_name:1,shop_price:1});
16
17
181.6:取出100<=价格<=500的商品($and)
19db.goods.find({$and:[{price:{$gt:100},{$price:{$lt:500}}}]);
20
21
221.8:取出价格大于100且小于300,或者大于4000且小于5000的商品()
23db.goods.find({$or:[{$and:[{shop_price:{$gt:100}},{shop_price:{$lt:300}}]},{$and:[{shop_price:{$gt:4000}},{shop_price:{$lt:5000}}]}]},{goods_name:1,shop_price:1});
24
25
26
271.9:取出goods_id%5 == 1, 即,1,6,11,..这样的商品
28db.goods.find({goods_id:{$mod:[5,1]}});
29
30
311.10:取出有age属性的文档
32db.stu.find({age:{$exists:1}});
33含有age属性的文档将会被查出

游标

游标是什么?

通俗的说,游标不是查询结果,而是查询的返回资源,或者接口.

通过这个接口,你可以逐条读取.

就像php中的fopen打开文件,得到一个资源一样, 通过资源,可以一行一行的读文件.

批量插入数据

1for (var i=0;i<10000;i++){
2    db.collection.insertOne({"_id":i+1,"name":"hello","count":"aaa"+i })
3}
4db.collection.find().count()

声明游标:

1var cursor =  db.collectioName.find(query,projection);
2Cursor.hasNext() ,判断游标是否已经取到尽头
3Cursor.Next() , 取出游标的下1个单元

循环游标

1var mycursor = db.bar.find({_id:{$lte:5}})
2while(mycursor.hasNext()) {
3  printjson(mycursor.next());
4}
5
6for(var doc=true;cursor.hasNext();) { printjson(cursor.next());}
7
8
9for(var  cursor=db.goods.find(), doc=true;cursor.hasNext();) { printjson(cursor.next());}

游标还有一个迭代函数,允许我们自定义回调函数来逐个处理每个单元. cursor.forEach(回调函数); 例:

1var gettitle = function(obj) {print(obj.goods_name)}
2var cursor = db.goods.find();
3cursor.forEach(gettitle);

分页中的应用

比如查到10000行,跳过100页,取10行.

一般地,我们假设每页N行, 当前是page页

就需要跳过前 (page-1)*N 行, 再取N行, 在mysql中, limit offset,N来实现

在mongo中,用skip(), limit()函数来实现的

如 var mycursor = db.bar.find().skip(9995);

则是查询结果中,跳过前9995行

查询第901页,每页10条

则是 var mytcursor = db.bar.find().skip(9000).limit(10);

通过cursor一次性得到所有数据, 并返回数组. 例:

1var cursor = db.goods.find();
2printjson(cursor.toArray());  //看到所有行
3printjson(cursor.toArray()[2]);  //看到第2行

注意: 不要随意使用toArray()

原因: 会把所有的行立即以对象形式组织在内存里.

可以在取出少数几行时,用此功能.

索引

1:索引提高查询速度,降低写入速度,权衡常用的查询字段,不必在太多列上建索引

2.在mongodb中,索引可以按字段升序/降序来创建,便于排序

3.默认是用btree来组织索引文件,2.4版本以后,也允许建立hash索引.

查看查询计划

1db.find(query).explain();

“cursor” : “BasicCursor”, —-说明没有索引发挥作用

“nscannedObjects” : 1000 —理论上要扫描多少行

cursor" : “BtreeCursor sn_1”, 用到的btree索引

查看当前索引

1db.collection.getIndexes();

创建普通的单列索引

1
2db.collection.ensureIndex({field:1/-1}); //  1是升续 2是降续
3db.collection.createIndex({field:1/-1}); //  1是升续 2是降续

删除索引

1db.collection.dropIndex({filed:1/-1}); // 删除单个索引
2
3db.collection.dropIndexes(); // 删除所有索引

创建多列索引

1db.collection.ensureIndex({field1:1/-1, field2:1/-1});

创建子文档索引

1db.collection.ensureIndex({filed.subfield:1/-1});

创建唯一索引

1db.collection.ensureIndex({filed.subfield:1/-1}, {unique:true});

稀疏索引

稀疏索引的特点——如果针对field做索引,针对不含field列的文档,将不建立索引. 与之相对,普通索引,会把该文档的field列的值认为NULL,并建索引.

适宜于: 小部分文档含有某列时.

1db.collection.ensureIndex({field:1/-1},{sparse:true});
2db.account.createIndex(
3        {open_id:1},
4        {unique:true}
5)
1db.tea.find();
2{ "_id" : ObjectId("5275f99b87437c610023597b"), "email" : "a@163.com" }
3{ "_id" : ObjectId("5275f99e87437c610023597c"), "email" : "b@163.com" }
4{ "_id" : ObjectId("5275f9e887437c610023597e"), "email" : "c@163.com" }
5{ "_id" : ObjectId("5275fa3887437c6100235980") }

如上内容,最后一行没有email列, 如果分别加普通索引,和稀疏索引, 对于最后一行的email分别当成null 和 忽略最后一行来处理. 根据{email:null}来查询,前者能查到,而稀疏索引查不到最后一行.

哈希索引

哈希索引速度比普通索引快,但是,无能对范围查询进行优化.

适宜于—随机性强的散列

1db.collection.ensureIndex({file:"hashed"});

重建索引

一个表经过很多次修改后,导致表的文件产生空洞,索引文件也如此. 可以通过索引的重建,减少索引文件碎片,并提高索引的效率. 类似mysql中的optimize table

1db.collection.reIndex()

用户管理

auth启动mongo

1docker run --name mongodb3 -p 27019:27017 -d mongo:4 --auth

添加用户

先要添加管理员账号

1 use admin 
2 db.createUser({user:"admin123",pwd:"123456",roles:[{role:"root",db:"admin"}]})
3 use admin
4  db.auth("admin123","123456")
1  use `database_name`
2  db.createUser({user:"root",pwd:"pwdxxx",roles:[{role:"readWrite",db:"database_name"}]})

下次连上来

1use `database_name`
2db.auth("root","pwdxxx")
3// 然后接着 crud 操作