一、Index/Key/DataPage——索引 /键 /数据⻚?

二、Covered Query

三、IXSCAN/COLLSCAN

四、Selectivity——过滤性

在一个有 10000条记录的集合中(以下条件都是独立检索):

  • 满足 gender= F 的记录有 4000 条
  • 满足 city=BJ 的记录有 100 条
  • 满足 lastname='Zhao' 的记录有 10 条

查询条件:

条件 lastname能过滤最多的数据, city其次, gender最弱。所以 : lastname过滤性( selectivity) > city > gender

五、索引结构

1、B-树和 B+树的区别

B树的两个明显特点 :

  • 树内的每个节点都存储数据
  • 叶子节点之间无指针相邻

B+树的两个明显特点

  • 数据只出现在叶子节点
  • 所有叶子节点增加了一个链指针

因此,在关系型数据中,遍历操作比较常见,因此采用 B+树作为索引,比较合适。而在非关系型数据库中,单

一查询比较常见,因此采用 B树作为索引,比较合适。 https://source.wiredtiger.com/10.0.0/tune_page_size_and_comp.html

重点是圈住的这句话

WiredTiger maintains a table's data in memory using a data structure called a B- Tree ( B+ Tree to be specific), referring to the nodes of a B-Tree as pages. Internal pages carry only keys. The leaf pages store both keys and values.

WiredTiger 使用称为 B-Tree(具体为 B+ 树)的数据结构在内存中维护表的数据,将 B-Tree 的节点 称为⻚。树内部⻚面只携带索引。叶子节点⻚存储索引和值。

由于 B树 /B+树的工作过程过于复杂,但本质上它是一个有序的数据结构。我们用数组来理解它。假设索引为 {a: 1}( a 升序)

  • 数据增加 /删除时始终保持索引字段有序
  • 数组插入效率低,但是 B数可以高效实现
  • 在有序结构上实施二分查找,可实现 O(log2(n))的高效搜索

六、索引类型

6.1 单列索引

由于 B树/B+树的工作过程过于复杂,但本质上它是一个有序的数据结构。我们用数组来理解它。假设索引为{a: 1}(a 升序)

  • 数据增加/删除时始终保持索引字段有序
  • 数组插入效率低,但是B数可以高效实现
  • 在有序结构上实施二分查找,可实现O(log2(n))的高效搜索

一个名为 records的集合,其中包含类似于以下样本文档的文档:

{
  "_id": ObjectId("570c04a4ad233577f97dc459"),
  "score": 1034,
  "location": { state: "NY", city: "New York" }
}

1、在单个字段上创建升序索引

db.records.createIndex( { score: 1 } )

为 records的集合的 score字段创建一个升序索引

可支持如下查询:

db.records.find( { score: 2 } )
db.records.find( { score: { $gt: 10 } } )

2、在嵌入式字段上创建索引

在 location.state字段上创建索引

db.records.createIndex( { "location.state": 1 } )

创建的索引将支持在字段 location.state上的查询,例如

db.records.find( { "location.state": "CA" } )
db.records.find( { "location.city": "Albany", "location.state": "NY" } )

3、在嵌入式文档上创建索引

以下命令将在整个位置字段上创建一个索引:

db.records.createIndex({location:1})

下面的查询可以使用 location字段上的索引:

db.records.find( { location: { city: "New York", state: "NY" } } )

6.2 复合索引

考虑一个名为 products的集合,它包含类似于以下文档的文档:

{
 "_id": ObjectId(...),
 "item": "Banana",
 "category": ["food", "produce", "grocery"],
 "location": "4th Street Store",
 "stock": 4,
 "type": "cases"
}

1、组合索引的最佳方式:ESR原则

//简单来说就是索引的字段顺序应该是:等值查询字段在最前面,然后是排序字段,最后是范围查询字段。 这是一个超级有用的小原则。

  • 精确(Equal)匹配的字段放最前面
  • 排序(Sort)条件放中间
  • 范围(Range)匹配的字段放最后面

请看一下查询条件:

db.members.find({ gender: F, age: {$gte: 18}}).sort(join_date:1)
{ gender: 1, age: 1, join_date: 1 }
{ gender: 1, join_date:1, age: 1 }
{ join_date: 1, gender: 1, age: 1 }
{ join_date: 1, age: 1, gender: 1 }
{ age: 1, join_date: 1, gender: 1}
{ age: 1, gender: 1, join_date: 1}

这么多候选的,用哪一个?

2、创建复合索引

db.collection.createIndex( { <field1>: <type>, <field2>: <type2>, ... } )

//在索引规范中,该字段的值描述了字段的索引类型。例如,值1指定一个索引,该索引按升序对记录条目进行排序。值-1指定一个索引,该索引按降序对记录条目进行排序。

//索引字段的顺序对给定查询的特定索引的有效性有很大影响。对于大多数复合索引,遵循ESR(相等、排序、范围)规则, ESR规则有助于创建高效的索引。

注意:不能创建具有哈希索引类型的复合索引。如果尝试创建包含哈希索引字段的复合索引,则会收到错误消息。

db.products.createIndex( { "item": 1, "stock": 1 } )

在 item 和 stock字段上创建一个升序索引

//复合索引中的字段顺序很重要。索引将包含对文档的引用,这些文档首先按item字段的值排序,然后在item字段的每个值内,按stock字段的值排序。 除了支持在所有索引字段上都匹配的查询之外,复合索引还可以支持在索引字段的前缀(索引起始子集)上匹配的查询。也就是说,

索引支持对 item字段以及 item字段和 stock字段的查询:

db.products.find( { item: "Banana" } )
db.products.find( { item: "Banana", stock: { $gt: 5 } } )

3、复合索引的排序

查询结果首先是按 username进行升序排序,然后按 date值降序排序,例如:

db.events.find().sort( { username: 1, date: -1 } )

查询返回结果首先按 username值降序,然后按 date值升序排序,例如:

db.events.find().sort( { username: -1, date: 1 } )

以下索引可以支持这两种排序操作:

db.events.createIndex( { "username" : 1, "date" : -1 } )

//但是,以上索引不能支持按username值升序,然后按date值升序排序,例如:

db.events.find().sort( { username: 1, date: 1 } )
db.events.find().sort( { username: -1, date: -1 } )

总结:

  • 单列索引正反向排序都不受影响
  • 复合索引则是乘以(-1)的排序可以用相同的索引,
  • 1,1 和 -1,-1可以使用索引
  • -1,1 和 1,-1 可以使用相同的索引

4、最左前缀原则

{ "item": 1, "location": 1, "stock": 1 }

索引具有以下索引前缀:

  • { item: 1 }
  • { item: 1, location: 1 }
  • 对于复合索引,MongoDB可以使用索引来支持对索引前缀的查询。这样,MongoDB可以将索引用于以下字段的查询:
  • item 字段,
  • item 和 location 字段,
  • item 和 location和 stock 字段。
  • MongoDB无法使用复合索引来支持包含以下字段的查询,因为如果没有 item字段,则列出的任何字段都不对应于前缀索引:
  • location 字段,
  • stock 字段,或
  • location 和 stock 字段。

6.3 多键索引

1、定义:

  • 基于一个数组创建索引, MongoDB会自动创建为多键索引,无需刻意指定
  • 多键索引也可以基于内嵌文档来创建
  • 多键索引的边界值的计算依赖于特定的规则
  • 多键索引不等于在文档上的多列创建索引 (复合索引 )

2、创建语法

db.coll.createIndex( { <field>: < 1 or -1 > } )

3、复合多键索引

  • 对于一个复合多键索引,每个索引最多可以包含一个数组。
  • 在多于一个数组的情形下来创建复合多键索引不被支持。

例如:

集合:

{ _id: 1, a: [ 1, 2 ], b: [ 1, 2 ], category: "AB - both arrays" }

不能创建一个基于 { a: 1, b: 1 } 的多键索引,因为 a和 b都是数组

集合:

{ _id: 1, a: [1, 2], b: 1, category: "A array" }
{ _id: 2, a: 1, b: [1, 2], category: "B array" }

则可以基于每一个文档创建一个基于 { a: 1, b: 1 }的复合多键索引,原因是每一个索引的索引字段只有一个数组

4、限制

  • 不能够指定一个多键索引为分片片键索引
  • 哈希索引不能够成为多键索引
  • 多键索引不支持覆盖查询

6.4 文本索引

一个集合最多有一个文本索引

对 reviews集合 comments字段创建一个文本索引

db.reviews.createIndex( { comments: "text" } )

在字段 subject和 comments上创建一个文本索引

db.reviews.createIndex(
 {
  subject: "text",
  comments: "text"
 }
)

6.5 地理空间索引

//在我们存储地理数据和编写查询条件前,首先,必须选择表面类型,这将被用在计算中。您所选择的类型将 会影响您的数据如何被存储,建立的索引的类型,以及您的查询的语法形式。 MongoDB提供了两种表面类型:

  • 2dsphere 索引
  • 2d 索引

创建 2dsphere 索引

db.collection.createIndex( { <location field> : "2dsphere" } )

创建 2d 索引

db.collection.createIndex( { <location field> : "2d" } ,
 { min : <lower bound> , max : <upper bound> } )

6.6 hash索引

1、创建哈希索引

db.collection.createIndex( { _id: "hashed" } )

2、注意事项

  • MongoDB支持任何单个字段的哈希索引
  • 不支持多键(即数组)索引
  • 不能创建具有哈希索引字段的复合索引
  • 不能在哈希索引上指定唯一约束
  • 可以在同一字段上创建哈希索引和升序 /降序(即非哈希)索引

七、索引属性

7.1 TTL索引

1、创建 TTL索引

在 eventlog集合的 lastModifiedDate字段上创建 TTL索引, TTL值为 3600秒

db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 } )

2、数据过期

  • 到期阈值是索引字段值加上指定的秒数。
  • 如果字段是数组,并且索引中有多个日期值,则 MongoDB使用数组中最低(即最早)的日期值来计算到期阈 值。
  • 如果文档中的索引字段不是日期或包含日期值的数组,则该文档不会过期。
  • 如果文档没有索引字段,则该文档不会过期
db.runCommand({
  collMod: "eventlog",
  index: {
    keyPattern: { lastModifiedDate: 1 },
    expireAfterSeconds: 3600
  }
})

3、删除操作

  • mongod中的后台线程读取索引中的值,并从集合中删除过期的文档。
  • 删除过期文档的后台任务每 60秒运行一次。
  • 在副本集成员上,仅当成员处于 primary状态时, TTL后台线程才会删除过期文档。
  • 在副本集成员上,当成员处于 secondary状态时, TTL背景线程处于空闲状态。
  • TTL索引对查询的支持,与非 TTL索引一样。

4、限制:

  • TTL索引是单字段索引。复合索引不支持 TTL,并且会忽略 expireAfterSeconds选项。
  • _id字段不支持 TTL索引。
  • 不能在固定集合( capped collection)上创建 TTL索引,因为 MongoDB无法从固定集合中删除文档。
  • 不能使用 createIndex()更改现有索引的 expireAfterSeconds的值。可以使用 collMod修改
  • 如果某个字段已经存在非 TTL单字段索引,则无法在同一字段上创建 TTL索引

7.2 唯一索引

1、创建唯一索引

(1)单字段的唯一索引

db.members.createIndex( { "user_id": 1 }, { unique: true } )

(2)唯一复合索引

db.members.createIndex( { groupNumber: 1, lastname: 1, firstname: 1 }, { unique: true } )

集合 { _id: 1, a: [ { loc: "A", qty: 5 }, { qty: 10 } ] },在 a.loc和 a.qty上创建唯一 的复合多键索引:

db.collection.createIndex( { "a.loc": 1, "a.qty": 1 }, { unique: true } )
db.collection.insert( { _id: 2, a: [ { loc: "A" }, { qty: 5 } ] } )
db.collection.insert( { _id: 3, a: [ { loc: "A", qty: 10 } ] } )

唯一索引允许将以下文档插入到集合中,因为该索引对于 a.loc和 a.qty值的组合具有唯一性:

2、限制

  • 若集合包含违反索引唯一约束的数据,则无法在指定的索引字段上创建唯一索引。
  • 不能在哈希索引上指定唯一约束。
  • 文档在唯一索引中没有索引字段的值,则索引将为此文档存储一个空值且只允许一个缺少索引字段的文档。 //如果存在多个没有索引字段值的文档,或者缺少索引字段,则索引构建将失败,并给出重复键错误。也就是说:如果集合尚不包含缺少字段 x的文档,则唯一索引允许插入不包含字段 x的文档;如果集合中已经包含缺少字段 x的文档,则再次插入没有字段 x的文档时,则出现唯一索引错误。

  • 对于要分片的集合,如果该集合具有其他唯一索引,则无法分片该集合。

  • 对于已分片的集合,不能在其他字段上创建唯一索引。

7.3 隐藏索引

1、定义:

  • 隐藏索引对查询规划器不可见,不能用于支持查询。
  • 通过对规划器隐藏索引,用户可以在不实际删除索引的情况下评估删除索引的潜在影响。如果影响是负面 的,用户可以取消隐藏索引,而不必重新创建已删除的索引。 4.4新版功能。

2、创建隐藏索引

db.collection.createIndex({fileName:1},{hidden:true});

eg:borough字段上创建一个隐藏的升序索引

db.addresses.createIndex(
   { borough: 1 },
   { hidden: true }
);

3、隐藏现有索引

db.collection.hideIndex({fileName:1});

或者

db.collection.hideIndex("索引名称")

4、取消隐藏索引

db.collection.unhideIndex({fileName:1});

或者

db.collection.unhideIndex("索引名称");

7.4 Sparse 索引

1、创建稀疏索引

db.addresses.createIndex( { "xmpp_id": 1 }, { sparse: true } )

2、行为说明

  • 如果 sparse索引会导致查询和排序操作的结果集不完整, MongoDB将不会使用该索引,除非 hint()明确指 定该索引。 //例如,查询 {x: {$exists: false}}不会在 x字段上使用 sparse索引,除非有明确提示。
  • 如果在执行集合中所有文档的 count()时包含 sparse索引 count(),则即使 sparse索引导致计数不正确,也会使用 sparse索引。

7.5 部分索引

1、创建部分索引

//请使用 db.collection.createIndex () 方法,使用 partialFilterExpression选项。例如,下 面的操作创建一个复合索引,该索引只针对 rating字段大于 5的文档创建。

db.restaurants.createIndex(
 { cuisine: 1},
 { partialFilterExpression: { rating: { $gt: 5 } } }
)

partialFilterExpression选项接受一个文档,该文档使用以下方式指定筛选条件 :

  • equality expressions, //布尔表达式,等于
  • $exists: true expression,
  • $gt, $gte, $lt, $lte expressions,
  • $type expressions,
  • $and operator at the top-level only //只在顶层操作符

2、查询范围

  • 可使用索引
db.restaurants.find( { cuisine: "Italian", rating: { $gte: 8 } } )
  • 不可使用索引
db.restaurants.find( { cuisine: "Italian", rating: { $lt: 8 } } )
db.restaurants.find( { cuisine: "Italian" } )

//该查询不能使用部分索引,因为查询谓词不包括筛选器表达式,并且使用索引将返回不完整的结果集。

//部分索引根据指定的筛选器确定索引项。过滤器不仅可以包括索引键以外的字段 并指定条件,还可以指定 为索引键。如图 1,对字段 a大于 5的文档加正序索引,那么 a是 1-4的文档上则不会有索引。

//我们还可以使用部分索引实现与 sparse索引相同的行为 :如下面左侧第二个图,对 wechat做正序且存在的 文档做索引,这就实现了稀疏索引的效果

八、执行计划

8.1 获取执行计划

1、支持的操作

aggregate(); count(); distinct(); find(); group(); remove(); update()

2、格式: db.collection.find().explain(verbose)

其中 verbosity说明返回信息的粒度。verbosity参数:

  • queryPlanner,缺省模式,对当前的查询进行评估并选择一个最佳的查询计划 //不会真正进行query语句查询,而是针对query语句进行执行计划分析并选出winning plan。
  • executionStats,对当前的查询进行评估并选择一个最佳的查询计划进行执行 //在执行完毕后返回这个最佳执行计划执行完成时的相关统计信息;对于写操作db.collection.explain()返回关于更新和删除操作的信息,但是并不将修改应用到数据库;对于那些被拒绝的执行计划,不返回其统计信息
  • allPlansExecution,前2种模式的更细化,即会包括上述2种模式的所有信息 //即按照最佳的执行计划执行以及列出统计信息,而且还会列出一些候选的执行计划,如果有多个查询计划,executionStats信息包括这些执行计划的部分统计信息
  • IXSCAN 索引扫描
  • FETCH 根据索引去检索文档
  • SHARD_MERGE 合并分片结果
/data/mongodb/mongodb_repl/bin/mongo 192.168.1.150:27018/admin -uroot -proot123456
use test
for(var i = 1 ;i < 10000; i++) {
  db.col.insert({name: i,age:i,date:new Date()});
}
db.col.find({name:1111}).explain(true)

8.2 优化后的执行计划

db.col.createIndex({name:1})
db.col.find({name:1111}).explain(true)

九、索引管理

1、查看索引

db.col.getIndexes()    #用来查看集合的所有索引,
db.col.getIndexKeys()   #查看索引键
db.col.totalIndexSize()  #查看集合索引的总大小,
db.col.getIndexSpecs()  #查看集合各索引的详细信息

2、在后台创建索引

db.col.createIndex({open: 1, close: 1}, {background: true})  //通过在创建索引时加 background:true 的选项,让创建工作在后台执行,不阻塞其他数据库操作

3、查看集合索引大小

db.col.totalIndexSize()

4、查看数据库中所有索引

db.system.indexes.find();

5、删除索引

db.col.dropIndexes()  #删除集合所有索引
db.col.dropIndex("索引名称 ")  #删除指定索引

6、查看索引创建进度

db.currentOp({
 $or: [
 { op: "command", "query.createIndexes": { $exists: true } },
 { op: "insert", ns: /\.system\.indexes\b/ }
 ]
});

7、终止索引的创建

db.killOp()

8、索引的最大范围

  • 集合中索引不能超过 64个
  • 索引名的⻓度不能超过 128个字符
  • 一个复合索引最多可以有 31个字段

9、索引限制

  • 额外开销 : 每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作。所以, 如果你很少对集合进行读取操作,建议不使用索引。
  • 内存 (RAM)使用 : 由于索引是存储在内存 (RAM)中 ,你应该确保该索引的大小不超过内存的限制。如果索引 的大小大于内存的限制, MongoDB会删除一些索引,这将导致性能下降。
  • 查询限制 : 索引不能被以下的查询使用 :正则表达式及非操作符,如 $nin, $not,等、算术运算符