关于mongodb:MongoDB学习之丰富的索引

1次阅读

共计 5871 个字符,预计需要花费 15 分钟才能阅读完成。

MongoDB的索引和 MySql 的索引的作用和优化要遵循的准则根本类似,MySql索引类型根本能够辨别为:

  • 单键索引 – 联结索引
  • 主键索引(聚簇索引) – 非主键索引(非聚簇索引)

MongoDB 中除了这些根底的分类之外, 还有一些非凡的索引类型, 如: 数组索引 | 稠密索引 | 天文空间索引 | TTL 索引等.

为了上面不便测试咱们应用脚本插入以下数据

for(var i = 0;i < 100000;i++){
    db.users.insertOne({
        username: "user"+i,
        age: Math.random() * 100,
        sex: i % 2,
        phone: 18468150001+i
    });
}

单键索引

单键索引即索引的字段只有一个, 是最根底的索引形式.

在汇合中应用 username 字段, 创立一个单键索引,MongoDB会主动将这个索引命名为username_1

db.users.createIndex({username:1})
'username_1'

在创立索引后查看一下应用 username 字段的查问打算,stageIXSCAN 代表应用应用了索引扫描

db.users.find({username:"user40001"}).explain()
{ 
   queryPlanner: 
   { 
     winningPlan: 
     { 
        ......
        stage: 'FETCH',
        inputStage: 
        { 
           stage: 'IXSCAN',
           keyPattern: {username: 1},
           indexName: 'username_1',
           ......
        } 
     }
     rejectedPlans: [] ,},
   ......
   ok: 1 
}

​ 在索引优化的准则当中, 有很重要的准则就是索引要建设在基数高的的字段上, 所谓基数就是一个字段上不反复数值的个数, 即咱们在创立 users 汇合时年龄呈现的数值是 0-99 那么 age 这个字段将会有 100 个不反复的数值, 即 age 字段的基数为 100, 而 sex 这个字段只会呈现 0 | 1 这个两个值, 即 sex 字段的根底是 2, 这是一个相当低的基数, 在这种状况下, 索引的效率并不高并且会导致索引生效.

上面就船舰一个 sex 字段索引, 来查问执行打算会发现, 查问时是走的全表扫描, 而没有走相干索引.

db.users.createIndex({sex:1})
'sex_1'

db.users.find({sex:1}).explain()
{ 
  queryPlanner: 
  { 
     ......
     winningPlan: 
     { 
        stage: 'COLLSCAN',
        filter: {sex: { '$eq': 1} },
        direction: 'forward' 
     },
     rejectedPlans: []},
  ......
  ok: 1 
}

联结索引

联结索引即索引上会有多个字段, 上面应用 agesex两个字段创立一个索引

db.users.createIndex({age:1,sex:1})
'age_1_sex_1'

而后咱们应用这两个字段进行一次查问, 查看执行打算, 顺利地走了这条索引

db.users.find({age:23,sex:1}).explain()
{ 
  queryPlanner: 
  { 
     ......
     winningPlan: 
     { 
        stage: 'FETCH',
        inputStage: 
        { 
           stage: 'IXSCAN',
           keyPattern: {age: 1, sex: 1},
           indexName: 'age_1_sex_1',
           .......
           indexBounds: {age: [ '[23, 23]' ], sex: ['[1, 1]' ] } 
        } 
     },
     rejectedPlans: [],},
  ......
  ok: 1 
 }

数组索引

数组索引就是对数组字段创立索引, 也叫做多值索引, 上面为了测试将 users 汇合中的数据减少一部分数组字段.

db.users.updateOne({username:"user1"},{$set:{hobby:["唱歌","篮球","rap"]}})
......

创立数组索引并进行查看其执行打算, 留神 isMultiKey: true 示意应用的索引是多值索引.

db.users.createIndex({hobby:1})
'hobby_1'

db.users.find({hobby:{$elemMatch:{$eq:"钓鱼"}}}).explain()
{ 
   queryPlanner: 
   { 
     ......
     winningPlan: 
     { 
        stage: 'FETCH',
        filter: {hobby: { '$elemMatch': { '$eq': '钓鱼'} } },
        inputStage: 
        { 
           stage: 'IXSCAN',
           keyPattern: {hobby: 1},
           indexName: 'hobby_1',
           isMultiKey: true,
           multiKeyPaths: {hobby: [ 'hobby'] },
           ......
           indexBounds: {hobby: [ '[" 钓鱼 "," 钓鱼 "]' ] } } 
         },
     rejectedPlans: []},
  ......
  ok: 1 
}

​ 数组索引相比于其它索引来说索引条目和体积必然呈倍数减少, 例如均匀每个文档的 hobby 数组的 size 为 10, 那么这个汇合的 hobby 数组索引的条目数量将是一般索引的 10 倍.

联结数组索引

​ 联结数组索引就是含有数组字段的联结索引, 这种索引不反对一个索引中含有多个数组字段, 即一个索引中最多能有一个数组字段, 这是为了防止索引条目爆炸式增长, 假如一个索引中有两个数组字段, 那么这个索引条目标数量将是一般索引的 n * m 倍

天文空间索引

在原先的 users 汇合上, 减少一些地理信息

for(var i = 0;i < 100000;i++){
    db.users.updateOne({username:"user"+i},
    {
        $set:{
            location:{
                type: "Point",
                coordinates: [100+Math.random() * 4,40+Math.random() * 3]
            }
        }
    });
}

创立一个二维空间索引

db.users.createIndex({location:"2dsphere"})
'location_2dsphere'

// 查问 500 米内的人
db.users.find({
  location:{
    $near:{$geometry:{type:"Point",coordinates:[102,41.5]},
      $maxDistance:500
    }
  }
})

天文空间索引的 type 有很多蕴含Ponit(点) | LineString(线) | Polygon(多边形)

TTL 索引

​ TTL 的全拼是 time to live, 次要是用于过期数据主动删除, 应用这种索引须要在文档中申明一个工夫类型的字段, 而后为这个字段创立 TTL 索引的时候还须要设置一个expireAfterSeconds 过期工夫单位为秒, 创立实现后 MongoDB 会定期对汇合中的数据进行查看, 当呈现:

$$
以后工夫 – TTL 索引字段时间 > expireAfterSrconds
$$

MongoDB将会主动将这些文档删除, 这种索引还有以下这些要求:

  • TTL 索引只能有一个字段, 没有联结 TTL 索引
  • TTL 不能用于固定汇合
  • TTL 索引是一一遍历后, 发现满足删除条件会应用 delete 函数删除, 效率并不高

首先在咱们文档上增减一个工夫字段

for(var i = 90000;i < 100000;i++){
    db.users.updateOne({username:"user"+i},
    {
        $set:{createdDate:new Date()
        }
    });
}

创立一个 TTL 索引并且设定过期工夫为 60s, 待过 60s 后查问, 会发现这些数据曾经不存在

db.users.createIndex({createdDate:1},{expireAfterSeconds:60})
'createdDate_1'

另外还能够用 CollMod 命令更改 TTL 索引的过期工夫

db.runCommand({
  collMod:"users",
  index:{keyPattern:{createdDate:1},
    expireAfterSeconds:120
  }
})

{expireAfterSeconds_old: 60, expireAfterSeconds_new: 120, ok: 1}

条件索引

条件索引也叫局部索引(partial), 只对满足条件的数据进行建设索引.

只对 50 岁以上的 user 进行建设 username_1 索引, 查看执行打算会发现 isPartial 这个字段会变成true

db.users.createIndex({username:1},{partialFilterExpression:{age:{$gt:50}
  }})
'username_1'

db.users.find({$and:[{username:"user4"},{age:60}]}).explain()
{ 
  queryPlanner: 
  { 
     ......
     winningPlan: 
     { 
        stage: 'FETCH',
        filter: {age: { '$eq': 60} },
        inputStage: 
        { 
           stage: 'IXSCAN',
           keyPattern: {username: 1},
           indexName: 'username_1',
           ......
           isPartial: true,
           ......
         } 
     },
     rejectedPlans: []},
  ......
  ok: 1 
}

稠密索引

​ 个别的索引会依据某个字段为整个汇合创立一个索引, 即便某个文档不存这个字段, 那么这个索引会把这个文档的这个字段当作 null 建设在索引当中.

稠密索引不会对文档中不存在的字段建设索引, 如果这个字段存在然而为 null 时, 则会创立索引.

上面给 users 汇合中的局部数据创立稠密索引

for(var i = 5000;i < 10000;i++){if(i < 9000){
    db.users.updateOne({username:"user"+i},
      {$set:{email:(120000000+i)+"@qq.email"}}
    )
  }else{
    db.users.updateOne({username:"user"+i},
      {$set:{email:null}}
    )
  }
}

当不建设索引应用 {email:null} 条件进行查问时, 咱们会发现查出来的文档蕴含没有 email 字段的文档

db.users.find({email:null})
{_id: ObjectId("61bdc01ba59136670f6536fd"),
  username: 'user0',
  age: 64.41483801726282,
  sex: 0,
  phone: 18468150001,
  location: 
  { 
    type: 'Point',
    coordinates: [101.42490900320335, 42.2576650823515] 
  } 
}
......

​ 而后对 email 这个字段创立一个稠密索引应用 {email:null} 条件进行查问, 则发现查问来的文档全副是 email 字段存在且为 null 的文档.

db.users.createIndex({email:1},{sparse:true});
'email_1'

db.users.find({email:null}).hint({email:1})
{_id: ObjectId("61bdc12ca59136670f655a25"),
  username: 'user9000',
  age: 94.18397576757012,
  sex: 0,
  phone: 18468159001,
  hobby: ['钓鱼', '乒乓球'],
  location: 
  { 
    type: 'Point',
    coordinates: [101.25903151863596, 41.38450145025062] 
  },
  email: null 
}
......

文本索引

文本索引将建设索引的文档字段先进行分词再进行检索, 然而目前还不反对中文分词.

上面减少两个文本字段, 创立一个联结文本索引

db.blog.insertMany([{title:"hello world",content:"mongodb is the best database"},
  {title:"index",content:"efficient data structure"}
])

// 创立索引
db.blog.createIndex({title:"text",content:"text"})
'title_text_content_text'
// 应用文本索引查问
db.blog.find({$text:{$search:"hello data"}})
{_id: ObjectId("61c092268c4037d17827d977"),
  title: 'index',
  content: 'efficient data structure' 
},
{_id: ObjectId("61c092268c4037d17827d976"),
  title: 'hello world',
  content: 'mongodb is the best database' 
}

惟一索引

​ 惟一索引就是在建设索引地字段上不能呈现反复元素, 除了单字段惟一索引还有联结惟一索引以及数组惟一索引 (即 数组之间不能有元素交加 )

// 对 title 字段创立惟一索引
db.blog.createIndex({title:1},{unique:true})
'title_1'
// 插入一个曾经存在的 title 值
db.blog.insertOne({title:"hello world",content:"mongodb is the best database"})
MongoServerError: E11000 duplicate key error collection: mock.blog index: title_1 dup key: {: "hello world"}
// 查看一下执行打算,isUnique 为 true
db.blog.find({"title":"index"}).explain()
{ 
  queryPlanner: 
  { 
     ......
     winningPlan: 
     { 
        stage: 'FETCH',
        inputStage: 
        { 
           stage: 'IXSCAN',
           keyPattern: {title: 1},
           indexName: 'title_1',
           isMultiKey: false,
           multiKeyPaths: {title: [] },
           isUnique: true,
           ......
         } 
     },
     rejectedPlans: []},
  .......
  ok: 1 
}
正文完
 0