拉勾教育学习-笔记分享のMongoDB"飞升"(上篇)

750 阅读22分钟

【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---
MongoDB是一款高性能的NoSQL(Not Only SQL 不仅仅SQL)数据库

花开花落,云卷云舒

MongoDB是一款高性能额NoSQL(Not Only SQL)数据库
官网
中文文档

一、 MongoDB 体系结构

part - 1 NoSQL和MongoDB

Not Only SQL,支持类似SQL的功能, 与 Relational Database 相辅相成。
其性能较高,不使用SQL意味着没有结构化的存储要求(SQL为结构化的查询语句),没有约束之后架构更加灵活

  • 【NoSQL四大家族】:
    1. 列存储 Hbase
    2. 键值存储 Redis
    3. 图像存储 Neo4j
    4. 文档存储 MongoDB

MongoDB 由 C++编写,可以为web应用提供可扩展、高性能、已部署的数据存储解决方案。
MongoDB 介于 关系型数据库 & 非关系型数据库之间,是非关系数据库中功能最丰富、最像关系数据库的。

part - 2 MongoDB体系结构

part - 3 MongoDB 和 RDBMS(关系型数据库)对比

part - 4 什么是BSON

BSON 是一种 类json 的一种二进制形式的存储格式,简称 Binary JSON,它和JSON一样,支持内嵌的文档对象和数组对象,但是 BSON 有 JSON 没有的一些数据类型,如 Date 和 Binary Data类型。

BSON 可以做为网络数据交换的一种存储形式,是一种 schema-less 的存储形式,它的优点是灵活性高,但它的缺点是空间利用率不是很理想。

// bson 格式
{key:value, key2:value2, ..., keyN:valueN}
// key为字符串类型
// value为 [字符串、double、Array、ISODate...类型]

BSON有三个特点:轻量性可遍历性高效性

part - 5 BSON在MongoDB中的使用

MongoDB 使用 BSON 将 存储数据与网络数据间的交换,转换为了 文档(Document) 概念。
Document 可以嵌套,较之关系型数据库的 Record,更加丰富。

  • 【Document 支持数据类型】

part - 6 MongoDB 在 Linux的安装

  1. 官网下载社区版MongoDB
  2. 用命令 tar -zxvf MongoDB-linux-x86_64-x.x.x.tgz 解压
    • 在解压后的文件夹中建立多级目录 mkdir -p /data/db
  3. 用命令 ./bin/mongod 启动
    • 启动成功后,属于前台进程运行
    • 另起窗口使用 ps -ef | grtp mongo 查看线程
  4. 指定配置文件方式的启动 -> ./bin/mongod -f mongo.conf

    配置文件样例:

    # 启动目录
    dbpath=/data/mongo/
    # 监听端口(默认27017)
    port=37017 
    # 监听ip地址(默认全部可以访问)
    bind_ip=0.0.0.0 
    # 程序采用后台进程运行
    fork=true 
    # 日志路径
    logpath = /data/mongo/MongoDB.log 
    # 是否追加日志
    logappend = true 
    # 是否启用 用户名登录
    auth=false
    

    配置文件需要自行创建在mongodb的根级,后缀 .cnf / .conf 皆可。

part - 7 mongo shell 的启动

  • 启动 mongo shell ./bin/mongo (默认连接本机27017端口)
  • 指定主机和端口的方式启动 ./bin/mongo --host=主机IP --port=端口

part - 8 Mongodb GUI工具

「MongoDB Compass Community」

MongoDB Compass Community 由 MongoDB开发人员开发,这意味着更高的可靠性和兼容性。
它为 MongoDB 提供 GUI mongodb工具,以探索数据库交互,具有完整的CRUD功能并提供可视方式。
借助内置模式可视化,用户可以分析文档并显示丰富的结构。 为了监控服务器的负载,它提供了数据库操作的实时统计信息。

官网下载

就像 MongoDB 一样,Compass 也有两个版本,一个是 Enterprise(付费),社区可以免费使用。适用于 Linux,Mac或Windows。

「NoSQLBooster (MongoBooster)」

NoSQLBooster 是 MongoDB CLI界面中非常流行的GUI工具。它正式名称为 MongoBooster
NoSQLBooster 是一个跨平台,它带有一堆mongodb工具来管理数据库和监控服务器。
这个Mongodb工具包括服务器监控工具,Visual Explain Plan,查询构建器,SQL查询,ES2017语法支持......

云盘安装包分享 提取码:x9gy

它有免费,个人和商业版本,当然,免费版本有一些功能限制。NoSQLBooster也可用于Windows,MacOS和Linux。

二、 MongoDB 命令

part - 1 MongoDB的基本操作

  • 查询数据库 show dbs;
  • 切换数据库 如果没有对应的数据库则创建 use db_name;
  • 创建集合 db.createCollection("COLLECTION_NAME")
  • 查看集合 show tables; / show collections;
  • 删除集合 db.COLLECTION_NAME.drop();
  • 删除当前数据库 db.dropDatabase();

part - 2 MongoDB集合数据操作(CURD)

「插入」

  • 【插入单条数据】 db.collection_name.insert(document_name);
    • 文档的数据结构和 JSON 基本一致
    • 所有存储在集合中的数据都是 BSON 格式
    • BSON 是一种类 JSON 的一种二进制形式的存储格式,简称 Binary JSON
    • e.g.
    db.mock_resume_preview.insert({
        name:"Archie",
        birthday:new ISODate("2020-02-21"),
        expectSalary:15000,
        gender:0,
        city:"shenzhen"
    })
    

    没有指定 _id 这个字段系统会自动生成, 当然我们也可以指定 _id
    _id 类型是 ObjectId, 类型是一个12字节 BSON 类型数据,有以下格式:

    • 前4个字节表示时间戳。 由ObjectId("对象Id字符串").getTimestamp() 来获取
    • 接下来的3个字节是机器标识码
    • 紧接的两个字节由进程id 组成(PID)
    • 最后三个字节是随机数。
  • 【插入多条数据】 db.collection_name.insert([document_name01,document_name02,...,document_nameN]);

「查询」

  • 【比较条件查询】 db.COLLECTION_NAME.find(condition);
    • = 等于
      • 格式 {key:value}
    • > 大于 greater than
      • 格式 {key:{$gt:value}}
    • < 小于 less than
      • 格式 {key:{$lt:value}}
    • >= 大于等于 greater than or equal to
      • 格式 {key:{$gte:value}}
    • <= 小于等于 less than or equal to
      • 格式 {key:{$lte:value}}
    • != 不等于 not equal to
      • 格式 {key:{$ne:value}}
  • 【逻辑条件查询】
    • and 条件 db.COLLECTION_NAME.find({key1:value1, key2:value2, ...}).pretty();
    • or 条件 db.COLLECTION_NAME.find({$or:[{key1:value1}, {key2:value2}, ...]}).pretty();
    • not 条件 db.COLLECTION_NAME.find({key:{$not:{$操作符:value}}).pretty();
  • 【分页查询】
    • db.COLLECTION_NAME.find({CONDITION}).sort({排序字段:排序方式})).skip(跳过的行数).limit(一页显示多少数据);

「更新」

  • $set :设置字段值
  • $unset :删除指定字段
  • $inc:对修改的值进行自增
  • $rename :对字段进行重命名
db.COLLECTION_NAME.update( 
    <query>, 
    <update>,
    { 
    	upsert: <boolean>, 
        multi: <boolean>,
        writeConcern: <document>
    }
)
  • 【参数说明】

    • query : update的查询条件,类似sql update查询内where后面的。
    • update : update的对象和一些更新的操作符(如$set,$inc...)等,也可以理解为sql update中 set后面的
    • upsert : (可选),这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认 是false,不插入。
    • multi : (可选),MongoDB 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查 出来多条记录全部更新。
    • writeConcern : (可选),用来指定mongod对写操作的回执行为比如写的行为是否需要确认。包含以下字段:
        1. w: <value> 指定写操作传播到的成员数量
        1. j: <boolean> 要求得到Mongodb的写操作已经写到硬盘日志的确认
        1. wtimeout: <number> 指定write concern的时间限制,只适用于w>1的情况
  • 【示例】

现在我们有三个数据:

  1. 将期望薪资小于14000的人标记为 "通过考虑":
db.mock_resume_preview.update({expectSalary:{$lt:14000}},{$set:{consideration:"passed"}},false,true)

  1. 将期望薪资大于14000的人 薪资减少3000:
db.mock_resume_preview.update({expectSalary:{$gt:14000}},{$inc:{expectSalary:-3000}},false,true)

  1. 将"city"字段改为"location":
db.mock_resume_preview.update({},{$rename:{"city":"location"}},false,true)

「删除」

db.collection.remove( 
    <query>, 
    { 
    	justOne: <boolean>, 
        writeConcern: <document>
    }
)
  • 【参数说明】
    • query : (可选)删除的文档的条件。
    • justOne : (可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
    • writeConcern : (可选)用来指定mongod对写操作的回执行为。

part - 3 MongoDB 聚合操作

聚合是MongoDB的高级查询语言,它允许我们通过转化合并由多个文档的数据来生成新的在单个文档里不存在的文档信息。
一般都是将记录按条件分组之后进行一系列求最大值,最小值,平均值的简单操作,也可以对记录进行复杂数据统计,数据挖掘的操作。
聚合操作的输入是集中的文档,输出可以是一个文档也可以是多个文档。

「单目的聚合」

  1. count() 求和
    • 语法:db.COLLECTION_NAME.count(<query>)db.COLLECTION_NAME.find(<query>).count()
    • 举例:
    // 全数据求和
    db.lg_resume_preview.find({}).count();
    // 期望薪资大于14000的数据求和
    db.lg_resume_preview.find({expectSalary:{$gt:14000}}).count();
    
  2. distict() 去重
    • 语法:db.collection.distinct(field,query)
    • 举例:
    // 键值去重
    db.mock_resume_preview.distinct("location");
    // 去重后计数
    db.mock_resume_preview.distinct("location").length;
    // 期望薪资大于14000的数据去重
    db.mock_resume_preview.distinct("location",{expectSalary:{$gt:14000}});
    

「聚合管道 (Aggregation Pipeline)」

  • 语法:db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION);
  • 表达式:
    • $sum 计算总和
    • $avg 计算平均值
    • $min 获取集合中所有文档对应值得最小值
    • $max 获取集合中所有文档对应值得最大值
    • $push 在结果文档中插入值到一个数组中
    • $addToSet 在结果文档中插入值到一个数组中,但数据不重复
    • $first 根据资源文档的排序获取第一个文档数据
    • $last 根据资源文档的排序获取最后一个文档数据
  • 聚合框架常用操作:
    • $group 将集合中的文档分组,可用于统计结果。
    • $project 修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
    • $match 用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
    • $limit 用来限制MongoDB聚合管道返回的文档数。
    • $skip 在聚合管道中跳过指定数量的文档,并返回余下的文档。
    • $sort 将输入文档排序后输出。
    • $geoNear 输出接近某一地理位置的有序文档。

其实大部分都和RDBMS有共通之处,具体使用可以参考网络上大量实例。

「MapReduce 编程模型」

Pipeline 查询速度快于 MapReduce,但是 MapReduce 的强大之处在于能够在多台Server上并行执行复杂的聚合逻辑
MongoDB 不允许 Pipeline 的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗20%以上的内存,那么MongoDB直接停止操作,并向客户端输出错误消息。

MapReduce 是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。

【语法】

db.COLLECTION_NAME.mapReduce(
  function() {emit(key,value);} // Map 函数,调用emit(k,v)遍历collection中的所有记录
  function(key, value) {return reduceFunction}, // Reduce 函数,接收 Map 处理后的 key & value
  {
    out: Collection, // 统计结果存放集合
    query: Document, // 一个筛选条件,只有满足条件的文档才会调用map函数
    sort: Document, // 和limit结合的sort排序参数(也是在发往map函数前给文档排序),可以优化分组机制
    limit: Number, // 发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)
    finalize: [Function], // 可以对reduce输出结果再一次修改
    verbose: [Boolean] // 是否包括结果信息中的时间信息,默认为fasle
  }
)

【示例】

现,集合中有以下文档数据:

  1. 统计不同地区的年龄总和
// 统计不同地区的年龄总和
var map = function() {emit(this.location, this.age);}
var reduce = function(key, value) {return Array.sum(value);}
var options = {out:"age_totals"}
db.archieDB.mapReduce(map, reduce, options)

  1. 统计不同地区的人数总和
// 统计不同地区的人数总和
var map = function() {emit(this.location, 1);}
var reduce = function(key, value) {return Array.sum(value);}
var options = {out:"person_totals"}
db.archieDB.mapReduce(map, reduce, options)

  1. 统计不同地区的人员名单
// 统计不同地区的人员名单
var map = function() {emit(this.location, this.name);}
var reduce = function(key, value) {return value.join(', ');}
var options = {out:"person_names"}
db.archieDB.mapReduce(map, reduce, options)

  1. 统计不同地区(小于25岁)的人员名单
// 统计不同地区的人员名单
var map = function() {emit(this.location, this.name);}
var reduce = function(k,v) {return v.join(', ');}
var options = {query:{age:{$lt:25}},out:'person_name_less_than_25'}
db.archieDB.mapReduce(map, reduce, options)

三、 MongoDB 索引 index

part - 1 从游标 到 索引

「About 游标」

  • 游标定义:是一种能从数据记录的结果集中每次提取一条记录的机制
  • 游标作用:可以随意控制最终结果集的返回,如限制返回数量、跳过记录、按字段排序、设置游标超时等。
  • MongoDB中的游标
    • 生命周期
      1. 游标声明 --> var cursor = db.COLLECTION_NAME.find(CONDITION); (MongoDB单条记录的最大大小是16M)
      2. 打开游标 --> cursor.hasNext(); 游标是否已经迭代到了最后(正在访问数据库)
      3. 读取游标 --> cursor.Next(); 获取游标下一个文档(正在访问数据库)
      4. 关闭游标 --> cursor.close(); 通常迭代完毕会自动关闭,也可以显示关闭
    • 常见方法
      • cursor.batchSize(size) 指定游标从数据库每次批量获取文档的个数限制
      • cursor.count() 统计游标中记录总数
      • cursor.explain(verbosity) 输出对应的执行计划
      • cursor.forEach() 采用js函数forEach对每一行进行迭代
      • cursor.hasNext() 判断游标记录是否已经迭代完毕
      • cursor.hint(index) 认为强制指定优化器的索引选择
      • cursor.limit() 指定游标返回的最大记录数
      • cursor.maxTimeMS(time) 指定游标两次getmore间隔的最大处理时间(毫秒)推荐
      • cursor.next() 返回游标下一条记录
      • cursor.noCursorTimeout() 强制不自动对空闲游标进行超时时间计算(默认10分钟)慎用
    • 示例
      // 显示游标
      var cursor = db.archieDB.find(); /* 游标被定义,不会访问数据库 */
      while(cursor.hasNext()) {
          printjson(cursor.next());
      }
      

「About 索引」

  • 索引定义:索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
    • 索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
    • 索引目标是提高数据库的查询效率,没有索引的话,查询会进行全表扫描(scan every document in a collection),数据量大时严重降低了查询效率。
  • MongoDB 中的索引:默认情况下 Mongo在一个集合(collection)创建时,自动地对集合的 _id 创建了唯一索引。

part - 2 索引类型

「单键索引」

对于单个字段索引,索引键的排序顺序无关紧要,因为MongoDB可以在任一方向读取索引
db.COLLECTION_NAME.createIndex({FIELD_NAME : SORT_ORDER});

SORT_ORDER(排序顺序):1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可

  • 示例
  // 添加年龄作为索引,并让创建工作在后台执行
  db.archieDB.createIndex({'age':1}, {background: true});

  • 可选参数
    • 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.

特殊的单键索引——过期索引 TTL (Time To Live)
TTL索引是MongoDB中一种特殊的索引, 可以支持文档在一定时间之后自动过期删除

语法db.COLLECTION_NAME.createIndex({'日期字段' : SORT_ORDER}, {expireAfterSeconds: 秒数});

  • 索引关键字段必须是 Date 类型
  • 非立即执行:扫描 Document 过期数据并删除是独立线程执行,默认 60s 扫描一次,删除也不一定是立即删除成功。
  • 单字段索引,混合索引不支持

「复合索引」

通常我们需要在多个字段的基础上搜索表/集合,这是非常频繁的。 如果是这种情况,我们可能会考虑在 MongoDB 中制作复合索引。
复合索引支持基于多个字段的索引,这扩展了索引的概念并将它们扩展到索引中的更大域。

db.COLLECTION_NAME.createIndex({ 
    FIELD_NAME1 : SORT_ORDER, 
    FIELD_NAME2 : SORT_ORDER, 
    ..., 
    FIELD_NAMEn : SORT_ORDER 
})

「多键索引」

针对属性包含数组数据的情况,MongoDB支持针对数组中每一个 element 创建索引
Multikey indexes 支持 stringsnumbersnested documents

MongoDB会自动识别数组后创建为多键索引,无需刻意指定

「地理空间索引」

LSB相关项目中,针对地理空间坐标数据创建索引。

  • 2dsphere 索引,用于存储和查找球面上的点
  • 2d 索引,用于存储和查找平面上的点

示例

// 使用places集合存放地点,loc字段存放地区数据 GeoJSON Point
db.places.insert(
   [{
      loc : { type: "Point", coordinates: [ -73.97, 40.77 ] },
      name: "Central Park",
      category : "Parks"
   },
   {
      loc : { type: "Point", coordinates: [ -73.88, 40.78 ] },
      name: "La Guardia Airport",
      category : "Airport"
   }]
)
// 建立地理索引(参数不是 1/-1,而是 '2dsphere')
db.palces.ensureIndex({loc : '2dsphere'})
// 当然,组合索引也是支持的 √
// db.places.ensureIndex( { loc : "2dsphere" , category : -1, name: 1 } )
  1. 查询多边形范围的值
db.places.find( { loc :
                  { $geoWithin :
                    { $geometry :
                      { type : "Polygon" , // polygon 多边形
                        coordinates : [ [ // 以下5个点的闭环保证了一个四边形区域
                                          [ 0 , 0 ] ,
                                          [ 0 , 90 ] ,
                                          [ -100 , 90 ] ,
                                          [ -100 , 0 ],
                                          [ 0 , 0 ]
                                        ] ]
                } } } } )
  1. 查询附近的值
db.places.find( { loc :
                    { $near :
                      { $geometry :
                         { type : "Point" ,
                           coordinates : [ -73 , 40 ] } ,
                        $maxDistance : 0.55
                 } } } )
  1. 查询圆形内的值
db.places.find( { loc :
                  { $geoWithin :
                    { $centerSphere :
                       [ [ -88 , 30 ] , 10 ]
                } } } )

「全文索引」

MongoDB 提供了针对 String 内容的文本查询,Text Index 支持任意属性值为 string 或 string数组 元素的索引查询。

注意:一个集合仅支持最多一个Text Index,中文分词不理想 推荐ES。

【语法】

db.COLLECTION_NAME.createIndex( {FIELD : "text"} )
db.COLLECTION_NAME.find( {"$text" : {"$search": "coffee"}} )

「哈希索引」

针对属性的哈希值进行索引查询,当要使用Hashed index时,MongoDB能够自动的计算hash值,无 需程序计算hash值。

db.COLLECTION_NAME.createIndex( {FIELD : "hashed"} )

注:

  1. hash index 仅支持等于查询,不支持范围查询
  2. 不能创建具有哈希索引字段的复合索引,也不能在哈希索引上指定唯一约束
  3. MongoDB哈希索引在哈希之前将浮点数截断为64位整数。例如,哈希索引将为具有2.3、2.2和2.9的值的字段存储相同的值。为防止冲突,请勿对不能准确地转换为64位整数的浮点数使用哈希索引。MongoDB哈希索引不支持大于2^53的浮点值。

part - 3 索引和 explain 分析

「索引管理」

// 获取针对某个集合的索引
db.COLLECTION_NAME.getIndexes()

// 索引的大小
db.COLLECTION_NAME.totalIndexSize()

// 索引的重建
db.COLLECTION_NAME.reIndex()

细则

// 索引的删除(_id 是MongoDB 默认创建的主键索引,无法删除)
db.COLLECTION_NAME.dropIndex("INDEX-NAME")
db.COLLECTION_NAME.dropIndexes()

「explain 分析」

--------【理论部分】----------

explain() 接收的参数:

  1. queryPlanner 默认参数

    • plannerVersion 查询计划版本
    • namespace 要查询的集合(该值返回的是该query所查询的表)数据库.集合
    • indexFilterSet 针对该query是否有indexFilter
    • parsedQuery 查询条件
    • winningPlan 被选中的执行计划
    • winningPlan.stage
      • 被选中执行计划的stage(查询方式),常见的有:COLLSCAN/全表扫描:(应该知道就是CollectionScan,就是所谓的“集合扫描”, 和mysql中table scan/heap scan类似,这个就是所谓的性能最烂最无奈的由来)、IXSCAN/索引扫描:(是IndexScan,这就说明我们已经命中索引了)、FETCH/根据索引去检索文档、SHARD_MERGE/合并分片结果、IDHACK/针对_id进行查询等
    • winningPlan.inputStage 用来描述子stage,并且为其父stage提供文档和索引关键字
    • winningPlan.stage的child stage 如果此处是IXSCAN,表示进行的是index scanning
    • winningPlan.keyPattern 所扫描的index内容
    • winningPlan.indexName winning plan所选用的index。
    • winningPlan.isMultiKey 是否是Multikey,此处返回是false,如果索引建立在array上,此处将是true。
    • winningPlan.direction 此query的查询顺序,此处是forward,如果用了.sort({字段:-1})将显示backward。
    • filter 过滤条件
    • winningPlan.indexBounds
      • winningplan所扫描的索引范围,如果没有制定范围就是[MaxKey,MinKey],这主要是直接定位到mongodb的chunck中去查找数据,加快数据读取
    • rejectedPlans 被拒绝的执行计划的详细返回,其中具体信息与winningPlan的返回中意义相同,故不在此赘述)
    • serverInfo MongoDB服务器信息
  2. executionStats 返回执行计划的一些统计信息

    • executionSuccess 是否执行成功
    • nReturned 返回的文档数
    • executionTimeMillis 执行耗时
    • totalKeysExamined 索引扫描次数
    • totalDocsExamined 文档扫描次数
    • executionStages 这个分类下描述执行的状态
    • stage 扫描方式,具体可选值与上文的相同
    • nReturned 查询结果数量
    • executionTimeMillisEstimate 检索 document 获得数据的时间
    • inputStage.executionTimeMillisEstimate 该查询扫描文档 index 所用时间
    • works 工作单元数,一个查询会分解成小的工作单元
    • advanced 优先返回的结果数
    • docsExamined 文档检查数目,与 totalDocsExamined 一致。检查了总共的 document 个数,而从返回上面的 nReturned 数量
  3. allPlansExecution 用来获取所有执行计划

    • queryPlanner 参数和executionStats的拼接

【executionStats 返回逐层分析】

  • executionTimeMillis 越少越好
    • executionStats.executionTimeMillis 该query的整体查询时间
    • executionStats.executionStages.executionTimeMillisEstimate 该查询检索document获得数据的时间
    • executionStats.executionStages.inputStage.executionTimeMillisEstimate 该查询扫描文档 index 所用时间
  • index 扫描数 & document 查询返回条目数
    • nReturned 该条查询返回的条目
    • totalKeysExamined 索引扫描条目
    • totalDocsExamined 文档扫描条目
  • stage 状态分析
    • COLLSCAN 全表扫描
    • IXSCAN 索引扫描
    • FETCH 根据索引去检索指定document
    • SHARD_MERGE 将各个分片返回数据进行merge
    • SORT 表明在内存中进行了排序
    • LIMIT 使用limit限制返回数
    • SKIP 该条查询返回的条目
    • IDHACK 针对_id进行查询
    • SHARDING_FILTER 通过mongos对分片数据进行查询
    • COUNT 利用db.coll.explain().count()之类进行count运算
    • TEXT 使用全文索引进行查询时候的stage返回
    • PROJECTION 限定返回字段时候stage的返回

我们一般希望看到的stage组合如下:

  • Fetch + IDHACK
  • Fetch + IXSCAN
  • Limit + (Fetch + IXSCAN)
  • PROJECTION + IXSCAN
  • SHARDING_FITER + IXSCAN 不希望看到的stage组合如下:
  • COLLSCAN (全表扫描)
  • SORT (使用sort但是无index)
  • COUNT (不使用index进行count)

--------【实战部分】----------

for(var i = 0; i < 100000; i ++) {
    db.archieDB.insert({
        name: 'user' + i,
        birthday: ISODate("2021-02-01"),
        age: 25,
        expectSalary: 9999,
        location: 'Shenzhen'
    })
}

通过上述语句(耗时 4335.909s -> 72min),在某文档中插入了100000 条数据,结合以下参数来分析:

part - 4 慢查询分析

  • 开启内置的查询分析器,记录读写操作效率
    db.setProfilingLevel(n,m) n表示级别,m表示单位
    • 0 不记录
    • 1 记录慢速操作 (此级别下 m 必须为'* ms')
    • 2 记录所有读写操作
  • 查询监控结果
    db.system.profile.find().sort({millis:-1}).limit(3)
  • 分析慢速查询
    应用程序设计不合理、不正确的数据模型、硬件配置问题,缺少索引等
  • 解读explain结果 确定是否缺少索引

part - 5 MongoDB 索引底层实现原理分析

MongoDB 是文档型的数据库,它使用BSON 格式保存数据,比关系型数据库存储更方便
之前关系型数据库中处理用户、订单等数据要建立对应的表,还要建立它们之间的关联关系。
但是 BSON 就不一样了,我们可以把一条数据和这条数据对应的数据都存入一个BSON对象中,这种形式更简单,通俗易懂。

MySql 是关系型数据库,数据的关联性是非常强的,区间访问是常见的一种情况,底层索引组织数 据使用B+树,B+树由于数据全部存储在叶子节点,并且通过指针串在一起,这样就很容易进行区间遍历甚至全部遍历

MongoDB使用B-树,所有节点都有Data域,只要找到指定索引就可以进行访问,单次查询从结构上来看要快于MySql

B-树是一种自平衡的搜索树,形式很简单:

  • B-树的特点:
    • 多路 非二叉树
    • 每个节点 既保存数据 又保存索引
    • 搜索时 相当于二分查找
  • B+树的特点:
    • 多路 非二叉树
    • 只有叶子节点保存数据
    • 搜索时 相当于二分查找
    • 增加了 相邻节点指针

从上面我们可以看出最核心的区别主要有俩

  1. 数据的保存位置
  2. 相邻节点的指向

【原理总结】:

  1. B+树相邻接点的指针可以大大增加区间访问性,可使用在范围查询等,而B-树每个节点 key 和 data 在一起 适合随机读写 ,而区间查找效率很差
  2. B+树适合外部存储,也就是磁盘存储,使用B-结构的话,每次磁盘预读中的很多数据是用不上的数据。因此,它没能利用好磁盘预读的提供的数据。由于节点内无 data 域,每个节点能索引的范围更大更精确
  3. B-树每个节点即保存数据又保存索引 树的深度小,所以磁盘IO的次数很少,B+树只有叶子节点保存,较B树而言深度大磁盘IO多,但是区间访问比较好。

四、 MongoDB 应用实战

part - 1 MongoDB 的适用场景

  • 网站数据:Mongo 非常适合实时的 插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
  • 缓存:由于性能很高,Mongo 也适合作为信息基础设施的缓存层。在系统重启之后,由Mongo搭建的持久化缓存层可以避免下层的数据源过载。
  • 大尺寸、低价值的数据:使用传统的关系型数据库存储一些大尺寸低价值数据时会比较浪费,在此之前,很多时候程序员往往会选择传统的文件进行存储。
  • 高伸缩性的场景:Mongo 非常适合由数十或数百台服务器组成的数据库,Mongo 的路线图中已经包含对MapReduce 引擎的内置支持以及集群高可用的解决方案。
  • 用于对象及JSON 数据的存储:Mongo 的BSON 数据格式非常适合文档化格式的存储及查询。

part - 2 MongoDB 的行业具体应用场景

  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新。
  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  • 直播,使用 MongoDB 存储用户信息、礼物信息等。

part - 3 如何抉择是否使用 MongoDB

  • 应用不需要事务及复杂 join 支持?
  • 新应用,需求会变,数据模型无法确定,想快速迭代开发?
  • 应用需要2000-3000以上的读写QPS(更高也可以)?
  • 应用需要TB甚至 PB 级别数据存储?
  • 应用发展迅速,需要能快速水平扩展?
  • 应用要求存储的数据不丢失?
  • 应用需要99.999%高可用?
  • 应用需要大量的地理位置查询、文本查询?

当以上情况存在2+的'Yes'时,就可以考虑使用 MongoDB 了

part - 4 Java 访问 MongoDB

demo源码

「添加 maven 坐标」

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongo-java-driver</artifactId>
    <version>3.10.1</version>
</dependency>

「插入 Demo」

// 1. 创建连接
MongoClient mongoClient = new MongoClient("106.75.60.49", 37017);
// 2. 获取数据库对象
MongoDatabase myDB = mongoClient.getDatabase("archieDB");
// 3. 获取文档
MongoCollection<Document> places = myDB.getCollection("places");
// 4. 初始化待插入 文档节点
Document document = Document.parse("{loc : { type: 'Point', coordinates: [ -73.97, 40.77 ] }," +
        "name:'La Guardia Airport'," +
        "category:'Airport'" +
        "num:3}");
// 5. 执行插入
places.insertOne(document);
// 6. 关闭连接
mongoClient.close();

「查询 Demo」

// 1. 创建连接
MongoClient mongoClient = new MongoClient("106.75.60.49", 37017);
// 2. 获取数据库对象
MongoDatabase myDB = mongoClient.getDatabase("archieDB");
// 3. 获取文档
MongoCollection<Document> places = myDB.getCollection("places");
// 4. 根据 _id 降序排列
Document document = new Document();
document.append("_id", -1);
FindIterable<Document> iterable = places.find().sort(document);
// 7. 遍历输出文档
for (Document everyDocument : iterable) {
    System.out.println("everyDocument = " + everyDocument);
}
// 6. 关闭连接
mongoClient.close();

「过滤查询 Demo」

// 1. 创建连接
MongoClient mongoClient = new MongoClient("106.75.60.49", 37017);
// 2. 获取数据库对象
MongoDatabase myDB = mongoClient.getDatabase("archieDB");
// 3. 获取文档
MongoCollection<Document> places = myDB.getCollection("places");
// 4. 根据 _id 降序排列
Document document = new Document();
document.append("_id", -1);
FindIterable<Document> iterable = places.find().sort(Filters.gt("num", 5)).sort(document);
// 7. 遍历输出文档
for (Document everyDocument : iterable) {
    System.out.println("everyDocument = " + everyDocument);
}
// 6. 关闭连接
mongoClient.close();

part - 5 Spring 访问 MongoDB

demo源码

「添加 maven 坐标」

  <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-mongodb</artifactId>
      <version>2.0.9.RELEASE</version>
  </dependency>

「applicationContext.xml 配置」

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">

    <!-- 构建MongoDb工厂对象 -->
    <mongo:db-factory id="mongoDbFactory" client-uri="mongodb://106.75.60.49:37017/archieDB" ></mongo:db-factory>

    <!-- 构建 MongoTemplate 类型的对象 -->
    <bean id="mongoTemplate"  class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg index="0" ref="mongoDbFactory"></constructor-arg>
    </bean>

    <!-- 开启组件扫描 -->
    <context:component-scan base-package="com.archie"></context:component-scan>
</beans>

「Bean」

public class MyBean {

    private String id;
    private String name;
    private Date birthday;
    private Integer age;
    private Double expectSalary;
    private String location;
    
    // 构造函数
    
    // get/set...
    
}

「DAO 层」

public interface MongoDao {
    
    /* 插入 */
    void insert(MyBean bean);
    /* 通过名称查 */
    MyBean findByName(String name);
    /* [批量] 通过名称查 */
    List<MyBean> listByName(String name);
    /* 通过 名称&期望薪资 查 */
    List<MyBean> listByNameAndExpectSalary(String name, Double expectSalary);
    
}

@Repository("mongoDao")
public class MongoDaoImpl implements MongoDao {
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    @Override
    public void insert(MyBean bean) {
        // mongoTemplate.insert(bean); // 如果不指定集合名,则自动 类名 ==MyBean首字母小写==> myBean
        mongoTemplate.insert(bean, "archieDB");
    }
    
    @Override
    public MyBean findByName(String name) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name));
        List<MyBean> dataList = mongoTemplate.find(query, MyBean.class, "archieDB");
        return dataList.isEmpty() ? null: dataList.get(0);
    }
    
    @Override
    public List<MyBean> listByName(String name) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name));
        return mongoTemplate.find(query, MyBean.class, "archieDB");
    }
    
    @Override
    public List<MyBean> listByNameAndExpectSalary(String name, Double expectSalary) {
        Query query = new Query();
        query.addCriteria(Criteria.where("name").is(name)
                .andOperator(Criteria.where("expectSalary").is(expectSalary)));
        return mongoTemplate.find(query, MyBean.class, "archieDB");
    }
}

「结果测试」

插入

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MongoDao mongoDao = applicationContext.getBean("mongoDao", MongoDao.class);
// 插入功能测试
MyBean myBean = new MyBean();
myBean.setName("person_A");
myBean.setBirthday(new Date());
myBean.setAge(20);
myBean.setExpectSalary(10000.0);
myBean.setLocation("Shenzhen");
mongoDao.insert(myBean);
System.out.println("已插入!");

通过 name 查询

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MongoDao mongoDao = applicationContext.getBean("mongoDao", MongoDao.class);
// 名称查询
MyBean result = mongoDao.findByName("person_A");
System.out.println("result = " + result.toString());

通过 name & expectSalary 查询

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
MongoDao mongoDao = applicationContext.getBean("mongoDao", MongoDao.class);
// 名称查询
MyBean result = mongoDao.findByName("person_A");
System.out.println("result = " + result.toString());

part - 6 Spring Boot 访问 MongoDB

demo源码

和 Spring 启动几乎没区别,可以直接拉上述源码看。