Mongodb

前瞻

  • 进入命令行:到服务器上运行(当然前提是使用docker方式进行安装的)docker exec -it mongodb mongo admin
  • 通过navicat连接:正常输入用户名密码即可(注意要将开放服务器对应端口的防火墙)

DDL

  • 创建数据库
    • use db_name
    • mongo不需要使用类似create这样的关键字来显式的创建数据库
  • 查看数据库show dbs,注意上面我们使用use命令创建的数据库必须要插入数据之后才会被展示到dbs的结果列表中
  • 查看当前数据库db

DML

插入

1
2
3
db.first_table.insert({'name': 'liuxulu'})
db.first_table.find()
# { "_id" : ObjectId("61c0386a55fe7a268eff2b6d"), "name" : "liuxulu" }
  • 由于没有指定_id,这个时候mongo会给文档自动增加一个_id

批量插入

db.first_table.insertMany([{'key0':0},{'key1':1}, {'key2': 2}])

  • 显然批量插入可以有效减小请求次数从而提高写入效率(目前还不确定服务端是否有相关的优化)
  • 目前mongo能接受的最大消息长度为48M,如果批量插入内容超出,多数的驱动会自动把请求进行拆分
  • 如果执行批量插入过程中的一个文档插入失败,那么在这这个文档之前的数据会被成功插入,这个文档及以后的文档会全部失败,也可以通过配置continueOnError来让后面的文档继续插入

插入校验

MongoDB只会进行最基本的检查

  • 有无_id
  • 文档必须小于16MB(后续可能会进行扩展)

基于Mongo的这个特点导致的是很容易被插入非法数据,所以应该要严格的限制信任的源才能访问数据库(当然基本所有的驱动都会对数据进行合法校验来补齐这个短板)

删除文档

1
2
3
4
5
6
# 删除所有文档
db.first_table.remove()
# 过滤删除
db.first_table.remove({'key1':1})
# 移除集合
db.first_table.drop()

drop就类似于mysql中的truncate,remove删除的是内容会保留集合元数据,drop会直接把整个集合删掉,优点是速度非常快

更新文档

文档替换

1
2
db.first_table.update({"_id" : ObjectId("61c0386a55fe7a268eff2b6d")}, {"name" : "liuxulu", "age": 20})
db.first_table.update({"_id" : ObjectId("61c0386a55fe7a268eff2b6d")}, {"age": 20})
  • 注意这种方式会将原本的文档内容进行完全的替换,而不是局部字段内容的修改
  • 这里我们直接用id对数据进行了定位,如果使用的是别的字段来进行过滤,必须要保证结果的唯一性,不然修改会报错(我不李姐,如果本身就是想要批量修改呢)

使用修改器

  • $set

    • db.first_table.update({"_id" : ObjectId("61c0386a55fe7a268eff2b6d")}, {"$set": {"age": 25}})

    • 对指定字段内容进行修改

  • $unset

    • db.first_table.update({"_id" : ObjectId("61c0386a55fe7a268eff2b6d")}, {"$unset": {"age": 25}})
    • 删除指定的字段
  • $inc:累加数字,不能用于别的类型

  • $push:向数组末尾添加一个元素,如果数组还不存在则初始化一个新数组

  • $pop:删除数组末端的一个元素

  • 还有一些修改器,不一一列举

写入安全机制

Mongo同样提供应答式写入和非应答式写入,不太重要的数据可以使用非应答式写入来提高速度

查询

普通查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 全文查询
db.first_table.find()
# 单条件查询
db.first_table.find({'name': 'liuxulu'})
# 多条件
db.first_table.find({'name': 'liuxulu', 'age': 25})
# 指定返回的字段,这里id默认返回,可以显式指定不返回
db.first_table.find({'name': 'liuxulu'}, {'name': 1, 'age': 1, '_id': 0})

# 范围查询
db.first_table.find({'age': {"$gte" : 18, "$lte" : 30}})
# 不等于
db.first_table.find({'name': {'$ne' : 'liuxulu'}})
# in查询
db.first_table.find({'name': {'$in' : ['liuxulu', 'firefly']}})
# nin查询,in的反义查询
db.first_table.find({'name': {'$nin' : ['liuxulu', 'firefly']}})
# or查询
db.first_table.find({
"$or":[
{'name': {'$nin' : ['liuxulu', 'firefly']}},
{'age': 25}
]
})
# not查询,注意not后面必须要接一个判断语句
db.first_table.find({'name': {'$not':{'$in' : ['liuxulu', 'firefly']}}})
# null判断
db.first_table.find({'name':null})

数组查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据案例
{ "_id" : ObjectId("61c0386a55fe7a268eff2b6d"), "name" : "liuxulu", "age" : 25, "fruit" : [ "apple", "banana", "peach" ] }

# 判断是否包含某个元素
db.first_table.find({"fruit" : "banana"})
# 同时包含多个元素
db.first_table.find({"fruit" : {"$all": ["banana", "apple"]}})
# 对数组相等进行判断
db.first_table.find({"fruit" : ["apple", "banana", "peach"]})
# size判断
db.first_table.find({"fruit" : {"$size": 3}})
# 由于size不能盒gt、lt进行配合,无法直接做到对数组长度进行范围过滤,变通的方式是创建一个额外的字段size来对数组长度进行记录,当对数据进行push或者pop之类的操作的时候使用inc来同步修改size的数值,后面就可以通过对size使用gt、lt来进行过滤了

# slice,只返回数值的部分元素
db.first_table.find({"_id": ObjectId("61c0386a55fe7a268eff2b6d")}, {"_id": 0, "fruit": {"$slice": -1}})

游标

1
2
3
4
var cursor = db.collection.find();
while (cursor.hasNext()) {
obj = cursor.next();
}
  • 游标在语句构建的时候请求没有真的发给数据库,等到cursor.hasNext()方法调用的时候才会查询服务器
  • 客户端每次只会取前100个结果(或者是4M数据),当客户端将结果耗尽之后会通过一个ge tMore请求获取更多的结果
  • 注意一下游标的释放,游标在服务端是需要占用内存资源的(保存查询状态嘛),正常来说客户端在释放掉游标资源的时候会告诉服务端同样进行清理,当然服务器会有一个10分钟超时清理的机制来避免内存泄露

limit、ship、sort

这三个通用方法,不介绍功能,使用的时候要注意以下的情况

  • 避免使用skip略过大量结果:skip的原理是把符合条件的结果查询出来然后丢弃指定数量多结果后进行返回

  • 分页问题

    • 方式之一就是用skip,弊端就是越往后效率就越低,经验来看数据量要控制在1w以内
    1
    2
    3
    var page1 = db.foo.find(criteria).limit(100)
    var page2 = db.foo.find(criteria).skip(100).limit(100)
    var page3 = db.foo.find(criteria).skip(200).limit(100)
    • 方式二是最多提供前1w条数据,其实对用用户来说给到更多的结果也没有意义,应该通过添加过滤条件来进行数据定位
    • 方式三结合场景来构建查询条件,比如按时间排序,每页的查询都从前一页最后一个数据开始查询即可(点赞数量排序之类的也类似),其实是方式二的变种,缺点是对数据更新频繁的库很可能导致查询出现脏数据