MongoDB使用进阶

引用
  • 关系引用
var result = db.users.findOne({"name": "Tom Benzamin"}, {"address_ids": 1})
var addresses = db.address.find({"_id": {"$in": result["address_ids"]}})
  • 数据库引用
/*
文档结构

{
   "_id":ObjectId("53402597d852426020000002"),
   "address": {
     "$ref": "address_home",
     "$id": ObjectId("534009e4d852427820000002"),
     "$db": "runoob"
   },
   "contact": "987654321",
   "dob": "01-01-1991",
   "name": "Tom Benzamin"
}
*/

var user = db.users.findOne({"name": "Tom Benzamin"})
var dbRef = user.address
db[dbRef.$ref].findOne("_id": (dbRef.$id))
// 在 MongoDB 4.0 写法
// db[dbRef.$ref].findOne({"_id": ObjectId(dbRef.$id)})
索引
  • 创建索引
// 联合索引
db.users.ensureIndex({gender: 1, user_name: 1})
  • 使用覆盖索引
    • 不能使用覆盖查询:1. 所有索引字段是一个数组; 2. 所有索引字段是一个文档
// 索引中没有 _id,需要排除
db.users.find({gender: "M"}, {user_name: 1, _id: 0})
  • 查询分析
    • explain()
    • hint()
db.users.find({gender: "M"}, {user_name: 1, _id: 0}).explain()
/*
{
   "cursor" : "BtreeCursor gender_1_user_name_1",  // 如果没有使用索引则是 BasicCursor
   "isMultiKey" : false,
   "n" : 1,  // 当前查询返回的文档数量
   "nscannedObjects" : 0,  // 扫描的文档数
   "nscanned" : 1,  // 扫描的文档数
   "nscannedObjectsAllPlans" : 0,
   "nscannedAllPlans" : 1,
   "scanAndOrder" : false,
   "indexOnly" : true,  // 使用了索引
   "nYields" : 0,
   "nChunkSkips" : 0,
   "millis" : 0,  // 当前查询所需时间(毫秒数)
   "indexBounds" : {  // 具体使用的索引
      "gender" : [
         [
            "M",
            "M"
         ]
      ],
      "user_name" : [
         [
            {
               "$minElement" : 1
            },
            {
               "$maxElement" : 1
            }
         ]
      ]
   }
}
*/
// 强制使用指定的索引
db.users.find({gender: "M"}, {user_name: 1, _id: 0}).hint({gender: 1, user_name: 1})
db.users.find({gender: "M"}, {user_name: 1, _id: 0}).hint({gender: 1, user_name: 1}).explain()

/*
{
   "address": {
      "city": "Los Angeles",
      "state": "California",
      "pincode": "123"
   },
   "tags": [
      "music",
      "cricket",
      "blogs"
   ],
   "name": "Tom Benzamin"
}
*/
  • 为数组建立索引
db.users.ensureIndex({"tags":1})
db.users.find({tags:"cricket"}).explain()
  // 显示 "cursor" : "BtreeCursor tags_1" ,则表示已经使用了索引
  • 索引子文档字段
db.users.ensureIndex({"address.city":1,"address.state":1,"address.pincode":1})
db.users.find({"address.city":"Los Angeles"}).explain()
  // 查询表达不一定遵循指定的索引的顺序,mongodb 会自动优化
db.users.find({"address.city":"Los Angeles","address.state":"California","address.pincode":"123"}).explain()
  • 索引限制

    • 如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降
    • 查询限制
      • 正则表达式及非操作符,如 $nin, $not, 等。
      • 算术运算符,如 $mod, 等。
      • $where 子句
    • 范围限制
      • 集合中索引不能超过64个
      • 索引名的长度不能超过128个字符
      • 一个复合索引最多可以有31个字段
    • 其它
      • 从2.6版本开始,如果现有的索引字段的值超过索引键的限制,MongoDB中不会创建索引
      • 如果文档的索引字段值超过了索引键的限制,MongoDB不会将任何文档转换成索引的集合
  • 全文索引: 对每一个词建立一个索引,指明该词在文章中出现的次数和位置

    • 在 2.6 版本以后是默认开启全文检索的,之前版本开启 db.adminCommand({setParameter:true,textSearchEnabled:true})mongod --setParameter textSearchEnabled=true
    • 操作
/*
{
   "post_text": "enjoy the mongodb articles on Runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}
*/

// 创建全文索引
db.posts.ensureIndex({post_text:"text"})

// 使用
db.posts.find({$text:{$search:"runoob"}})

// 旧版 db.posts.runCommand("text",{search:"runoob"})
/*
{ 
   "_id" : ObjectId("53493d14d852429c10000002"), 
   "post_text" : "enjoy the mongodb articles on Runoob", 
   "tags" : [ "mongodb", "runoob" ]
}
*/

// 删除全文索引
db.posts.getIndexes()
db.posts.dropIndex("post_text_text")
原子操作

MongoDB 不支持事务

db.books.findAndModify ( {
   query: {
            _id: 123456789,
            available: { $gt: 0 }
          },
   update: {
             $inc: { available: -1 },
             $push: { checkout: { by: "abc", date: new Date() } }
           }
} )
  • 原子操作常用命令
{$set: {field: value}}
{$unset: {field: 1}}

{$inc: {field: value}}


{$push: {field: value}}
{$pushAll: {field: value_array}}
{$pull: {field: _value}}
$addToSet
{$pop: {field: 1}}

{$rename: {old_field_name:: new_field_name}}

{$bit: {field: {and: 5}}}
  • 示例
t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 3 }, { "by" : "jane", "votes" : 7 } ] }
 
t.update( {'comments.by':'joe'}, {$inc:{'comments.$.votes':1}}, false, true )
 
t.find() { "_id" : ObjectId("4b97e62bf1d8c7152c9ccb74"), "title" : "ABC", "comments" : [ { "by" : "joe", "votes" : 4 }, { "by" : "jane", "votes" : 7 } ] }
ObjectId
  • 格式

    • 前4个字节表示时间戳
    • 接下来的3个字节是机器标识码
    • 紧接的两个字节由进程id组成(PID)
    • 最后三个字节是随机数
  • 创建新的 ObjectId

newObjectId = ObjectId() // ObjectId("5349b4ddd2781d08c09890f3")
  • 获取文档创建的时间戳
ObjectId("5349b4ddd2781d08c09890f4").getTimestamp()  // ISODate("2014-04-12T21:49:17Z")
  • 转换为字符串
new Object().str // 5349b4ddd2781d08c09890f3
Map Reduce
  • 基本语法
db.collection.mapReduce(
   function() {emit(key,value);},  // map 函数
   function(key,values) {return reduceFunction},   // reduce 函数
   {
      out: collection,
      query: document,  // 文档筛选条件
      sort: document,  // 排序参数
      limit: number  // 文档数量上限
   }
)
  • 示例
// 添加测试数据
db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"active"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "mark",
   "status":"disabled"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"disabled"
})

db.posts.insert({
   "post_text": "菜鸟教程,最全的技术文档。",
   "user_name": "runoob",
   "status":"active"
})


db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
)
/*
{
        "result" : "post_total",
        "timeMillis" : 23,
        "counts" : {
                "input" : 5,
                "emit" : 5,
                "reduce" : 1,
                "output" : 2
        },
        "ok" : 1
}
*/


db.posts.mapReduce( 
   function() { emit(this.user_name,1); }, 
   function(key, values) {return Array.sum(values)}, 
      {  
         query:{status:"active"},  
         out:"post_total" 
      }
).find()

/*
{ "_id" : "mark", "value" : 4 }
{ "_id" : "runoob", "value" : 1 }
*/
正则表达式
  • 操作
/*
{
   "post_text": "enjoy the mongodb articles on runoob",
   "tags": [
      "mongodb",
      "runoob"
   ]
}
*/

db.posts.find({post_text:{$regex:"runoob"}})
db.posts.find({post_text:/runoob/})
db.posts.find({tags:{$regex:"run"}})  // 数组元素使用正则表达式
// 不区分大小写
db.posts.find({post_text:{$regex:"runoob",$options:"$i"}})

  • 优化正则表达式
    • 文档中字段设置了索引,那么使用索引相比于正则表达式匹配查找所有的数据查询速度更快
    • 正则表达式是前缀表达式,所有匹配的数据将以指定的前缀字符串为开始
    • 正则表达式中使用变量。一定要使用eval将组合的字符串进行转换,不能直接将字符串拼接后传入给表达式。否则没有报错信息,只是结果为空!
var name=eval("/" + 变量值key +"/i"); 
title:eval("/"+title+"/i")    // 等同于 title:{$regex:title,$Option:"$i"}
  • 关于 regex
    • regex 操作符
      • {<field>:{$regex:/pattern/,$options:’<options>’}}
      • {<field>:{$regex:’pattern’,$options:’<options>’}}
      • {<field>:{$regex:/pattern/<options>}}
    • 正则表达式对象: {<field>: /pattern/<options>}
    • 两者区别
      • KaTeX parse error: Expected '}', got 'EOF' at end of input: …对象,例如: `{name:{in:[/joe/i,/jack/}}`
      • 在使用隐式的 a n d 操 作 符 中 , 只 能 使 用 and操作符中,只能使用 and使regex,例如: {name:{$regex:/^jo/i, $nin:['john']}}
      • 当option选项中包含X或S选项时,只能使用KaTeX parse error: Expected '}', got 'EOF' at end of input: …ex,例如: `{name:{regex:/m.*line/,$options:“si”}}`
    • options 选项:i/m/x/s(忽略大小写/多行匹配/忽略非转义的空白字符/单行匹配)
管理工具
  • Rockmongo
  • Navicat for MongoDB
GridFS
  • 要点

    • 用于存储和恢复那些超过16M(BSON文件限制)的文件
    • GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。
    • GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。
    • 每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。
  • 示例

mongofiles.exe -d gridfs put song.mp3
db.fs.files.find()

/*
{
   _id: ObjectId('534a811bf8b4aa4d33fdf94d'), 
   filename: "song.mp3", 
   chunkSize: 261120, 
   uploadDate: new Date(1397391643474), md5: "e4f53379c909f7bed2e9d631e15c1c41",
   length: 10401959 
}
*/

db.fs.chunks.find({files_id:ObjectId('534a811bf8b4aa4d33fdf94d')})
固定集合

是性能出色且有着固定大小的集合,对于大小固定,我们可以想象其就像一个环形队列,当集合空间用完后,再插入的元素就会覆盖最初始的头部的元素!

  • 创建
db.createCollection("cappedLogCollection",{capped:true,size:10000})
db.createCollection("cappedLogCollection",{capped:true,size:10000,max:1000})

// size 是整个集合空间大小,单位为【KB】
// max 是集合文档个数上线,单位是【个】

db.cappedLogCollection.isCapped()  // 判断是否为固定集合
db.runCommand({"convertToCapped":"posts",size:10000})  // 转换为固定集合

// 固定集合文档按照插入顺序储存的,默认情况下查询就是按照插入顺序返回的,也可以使用$natural调整返回顺序。
db.cappedLogCollection.find().sort({$natural:-1})
  • 特点

    • 可以插入及更新,但更新不能超出collection的大小,否则更新失败,不允许删除,但是可以调用drop()删除集合中的所有行,但是drop后需要显式地重建集合。
    • 受系统文件大小的限制
  • 属性

    • 对固定集合进行插入速度极快
    • 按照插入顺序的查询输出速度极快
    • 能够在插入最新数据时,淘汰最早的数据
  • 用法

    • 储存日志信息
    • 缓存一些少量的文档
实现 _id 自动增长
db.createCollection("counters")

{
  "_id":1,
  "product_name": "Apple iPhone",
  "category": "mobiles"
}

function getNextSequenceValue(sequenceName){
   var sequenceDocument = db.counters.findAndModify(
      {
         query:{_id: sequenceName },
         update: {$inc:{sequence_value:1}},
         "new":true
      });
   return sequenceDocument.sequence_value;
}


db.products.insert({
   "_id":getNextSequenceValue("productid"),
   "product_name":"Apple iPhone",
   "category":"mobiles"}
 )
db.products.insert({
   "_id":getNextSequenceValue("productid"),
   "product_name":"Samsung S3",
   "category":"mobiles"}
 )
   
db.products.find()

/*
{ "_id" : 1, "product_name" : "Apple iPhone", "category" : "mobiles"}
{ "_id" : 2, "product_name" : "Samsung S3", "category" : "mobiles" }
*/
零碎知识点
# 切换或创建数据库
use <数据库名>

db.auth('用户名', '密码')


// 返回的是对象
db.users.findOne()

// 返回的是数组
db.users.find()
参考
  • https://www.runoob.com/mongodb/mongodb-tutorial.html
记录
  • 2019/12/21 周六 20:03:25
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈挨踢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值