MongoDB:索引

介绍

索引是一种用来快速查询数据的数据结构。B+Tree 就是一种常用的数据库索引数据结构,MongoDB 采用 B+Tree 做索引 ,索引创建在colletions上。

MongoDB主要使用B+树作为其索引结构。与普通的B树相比,B+树在叶子节点上存储了所有的数据记录(或指向记录的指针),而非仅存储键值对。这样做的好处是可以减少IO操作,提高数据的读取效率。同时,B+树的叶子节点之间会互相链接,形成链表结构,这有助于范围查询等操作的优化。

MongoDB不使用索引的查询,先扫描所有的文档,再匹配符合条件的文档。

使用索引的查询,通过索引找到文档,使用索引能够极大的提升查询效率。

MongoDB中的索引通常存储在磁盘上,以文件的形式存储在数据库目录中。索引文件包含了索引的结构信息以及索引所对应的文档数据的引用。

索引的作用

提高查询性能:索引可以显著减少数据库引擎需要扫描的数据量,从而加快查询速度。

加速数据检索:对于大型数据集,索引可以大大缩短数据检索的时间。

支持排序和范围查询:索引可以优化排序和范围查询操作。

支持唯一性约束:通过创建唯一索引,可以确保数据库表中每一行数据的唯一性。

索引类型

  1. 单字段索引(Single Field Indexes):这是最基本的索引类型,它可以基于集合中的单个字段创建。

    示例:db.collection.createIndex({field:1}),其中1表示升序索引,-1表示降序索引。

  2. 复合索引(Compound Indexes):复合索引是单字段索引的升级版,它可以基于集合中的多个字段创建。

    示例:db.collection.createIndex({field1:1,field2:-1}),表示在field1上建立升序索引,在field2上建立降序索引。

  3. 唯一索引(Unique Indexes):唯一索引确保索引字段的值在集合内是唯一的。

    示例:db.collection.createIndex({field:1},{unique:true}),在field上创建唯一索引。

  4. 多键索引(Multikey Indexes):多键索引针对数组或者嵌套文档的字段进行索引。

    当索引的字段是数组时,MongoDB会为数组中的每个元素创建索引项。

  5. 地理空间索引(Geospatial Indexes):地理空间索引对包含地理坐标的字段进行索引,支持对地理空间数据的查询。

    MongoDB提供了两种类型的地理空间索引:2dsphere和2d。

  6. 文本索引(Text Indexes):文本索引可用于字符串字段,主要用于执行全文搜索。

    示例:db.collection.createIndex({field:"text"}),在field上创建文本索引。

  7. 哈希索引(Hashed Indexes):哈希索引在某些特定的应用场景下可以提供比普通索引更好的性能,但它不支持范围查询。

    示例:db.collection.createIndex({field:"hashed"}),在field上创建哈希索引。

  8. 通配符索引(Wildcard Indexes):通配符索引使用通配符对任意字段进行索引,提供了一种灵活的方式来索引集合中的未知或动态变化的字段。

    注意:这种索引类型可能在某些MongoDB版本中不是默认支持的,或者需要特定的配置才能使用。

  9. TTL索引(Time-To-Live Indexes):TTL索引是一种特殊的索引,用于自动删除集合中过期的数据。

    示例:db.collection.createIndex({expireField:1},{expireAfterSeconds:3600}),在expireField上创建TTL索引,设置数据过期时间为3600秒。

  10. 部分索引(Partial Indexes):部分索引只针对满足特定条件的文档进行索引,这有助于减少索引的大小并提高查询性能。

    示例(假设创建):db.collection.createIndex({field:1},{partialFilterExpression:{condition:true}}),其中condition定义了哪些文档应该被索引。

索引覆盖

索引覆盖查询是指查询操作可以仅通过索引来获取所需的数据,而无需访问集合中的文档。换句话说,当查询的字段都包含在索引中时,MongoDB可以直接使用索引来执行查询,而无需进一步加载文档数据。

为了实现索引覆盖,查询字段必须全部包含在索引中。

示例

假设在MongoDB中有一个集合,包含以下文档:

1
2
3
4
{"name":"John","age":30,"city":"New York"}  
{"name":"Alice","age":25,"city":"Los Angeles"}  
{"name":"Bob","age":35,"city":"New York"}  
{"name":"Eve","age":28,"city":"Los Angeles"}

可以针对查询字段nameage创建覆盖索引:

1
db.collection.createIndex({name:1,age:1})

接下来,执行一个查询,查找年龄小于等于30岁的文档,并只返回nameage字段:

1
db.collection.find({age:{$lte:30}},{_id:0,name:1,age:1}).explain("executionStats")

在执行上述查询时,MongoDB将使用覆盖索引来执行查询,直接从索引中获取nameage字段的值,并返回给用户,无需访问文档数据,从而提高了查询性能。

创建索引

在MongoDB中,创建索引可以显著提升数据查询的性能,尤其是对于大型数据集。MongoDB提供了多种类型的索引,包括单一字段索引、复合索引、唯一索引、地理空间索引、全文搜索索引等。

在MongoDB中,你可以通过createIndex()函数来创建索引。

从MongoDB 3.0开始,推荐使用createIndex(),而之前的版本使用的是ensureIndex()

基本语法:

1
db.collection.createIndex(keys, options)
  • keys参数定义了索引的类型和方向。例如,{ field: 1 }创建一个升序索引,{ field: -1 }创建一个降序索引。
  • options参数是一个可选的对象,用于设置索引的额外选项,例如是否唯一、是否稀疏、是否是文本索引等。

常见示例

单一字段索引

1
db.collection.createIndex({ field: 1 });

指定索引名称

name参数允许你指定索引的名称。如果不指定,MongoDB将自动生成一个名称。在创建复合索引或具有特殊选项(如唯一索引)的索引时,指定名称很有用。

1
2
3
4
db.collection.createIndex(  
    { "field1": 1 },  
    { name: "customIndexName" }  
)

复合索引

1
db.collection.createIndex({ field1: 1, field2: -1 });

唯一索引

1
db.collection.createIndex({ field: 1 }, { unique: true });

稀疏索引(仅对非空字段创建索引)

1
db.collection.createIndex({ field: 1 }, { sparse: true });

部分索引

部分索引仅索引满足指定过滤条件的文档。

在MongoDB 3.2及更高版本中,可以使用partialFilterExpression来创建部分索引。

1
2
3
4
db.collection.createIndex(  
    { "status": 1 },  
    { partialFilterExpression: { "status": { "$ne": "D" } } }  
)

过期索引(TTL,Time-To-Live)

通过expireAfterSeconds参数,可以创建TTL索引,这些索引会根据文档中的某个时间戳字段自动删除旧文档。

1
2
3
4
db.collection.createIndex(  
    { "createdAt": 1 },  
    { expireAfterSeconds: 3600 } // 例如,删除超过1小时前的文档  
)

文档级别的唯一性(_id字段自动创建)

不需要显式创建。

地理空间索引

1
db.collection.createIndex({ location: "2dsphere" });

全文搜索索引

1
db.collection.createIndex({ text: "text" });

哈希索引

1
db.collection.createIndex({ field: "hashed" });

查看索引信息

可以使用getIndexes()方法来查看集合中的索引信息。

1
db.collection.getIndexes();

删除索引

在 MongoDB 中,dropIndex() 方法用于删除一个指定的索引。

在较新的 MongoDB 版本和官方 shell 中,你通常应该传递索引名称(字符串)给 dropIndex() 方法,而不是索引的键模式(对象)。例如,如果你要删除一个基于 age 字段的索引,你首先需要知道该索引的名称,然后使用该名称来删除索引:

1
db.stus.dropIndex("age_1");

这里的 "age_1" 是索引的名称,它是由 MongoDB 自动生成的,其中 _1 表示升序索引。

在旧版本的 MongoDB 或者某些驱动程序中,确实可以使用键模式对象来删除索引,但是这种方式并不推荐,因为它可能导致意外删除同名的多个索引。例如,在某些旧版本的驱动程序中,下面的代码可能可以工作:

1
db.stus.dropIndex({age:1});

但是,这并不是最佳实践,而且在较新版本的 MongoDB 中可能无法正常工作。最好的做法是获取索引的完整名称,然后使用名称来删除索引。

为了安全地删除索引,你应该先使用 getIndexes()listIndexes() 方法来查看所有索引的名称,然后使用正确的索引名称调用 dropIndex() 方法。

查看查询是否经过了索引

在MongoDB中,要查看查询是否经过了索引,你可以使用explain()方法来分析查询的执行计划。explain()方法会返回一个包含查询执行计划的文档,其中包含了索引使用情况等信息。

对于基本的查询,你可以通过链式调用find()explain()方法来实现,如下所示:

1
db.collection.find({ /* 查询条件 */ }).explain()

执行上述命令后,MongoDB会返回查询的执行计划文档。

执行计划文档包含了多个字段,其中与索引使用相关的关键字段包括:

  • queryPlanner.winningPlan:这表示查询过程中选择的最佳执行计划。你可以在这个计划中查看索引的使用情况。

    • stage:表示查询优化器为给定查询选择的最优执行计划的当前阶段。如果stage值为COLLSCAN,则表示全集合扫描;如果值为FETCH,则说明经过了索引。

      queryPlanner.winningPlan.stage字段的详细说明
      `queryPlanner.winningPlan.stage`在MongoDB的`explain()`方法的输出中是一个非常重要的字段,它表示查询优化器为给定查询选择的最优执行计划的当前阶段。这个字段提供了关于查询如何被执行的关键信息,包括查询使用的操作类型(如索引扫描、集合扫描、文档检索等)。
      
      具体来说,`queryPlanner.winningPlan.stage`可以包含以下几种类型的阶段(stage),每种类型都代表了查询执行的不同方面:
      
      1. COLLSCAN:集合扫描。这表示MongoDB正在对集合进行全表扫描来查找匹配的文档。这通常发生在没有可用的索引或者查询条件无法有效利用索引时。
      2. IXSCAN:索引扫描。这表示MongoDB正在使用索引来查找匹配的文档。索引扫描可以显著提高查询性能,因为它减少了需要检查的文档数量。
      3. FETCH:检出文档。这个阶段通常与索引扫描(IXSCAN)一起使用,表示MongoDB已经根据索引找到了文档的位置,现在正在从集合中检索这些文档。
      4. SHARD_MERGE:合并分片中结果。在分片集合中,这个阶段表示MongoDB正在合并来自不同分片的查询结果。
      5. SORT:排序。这表示MongoDB正在对查询结果进行排序。如果查询中包含排序操作(如`sort()`),并且没有使用索引进行排序(即索引顺序与排序顺序不匹配),则可能会出现这个阶段。
      6. LIMIT:限制返回数。这表示查询结果的数量被限制了(如使用`limit()`)。
      7. SKIP:跳过。这表示查询结果中的一部分文档被跳过了(如使用`skip()`)。
      8. IDHACK:针对_id进行查询。这是一个特殊的阶段,表示查询直接通过_id字段来检索文档,这通常非常快,因为_id字段总是被索引的。
      9. SHARDING_FILTER:分片中过滤掉孤立文档。在分片集合中,这个阶段用于过滤掉不属于查询请求的分片中的文档。
      10. COUNT、COUNTSCAN、COUNT_SCAN:这些阶段与计数操作相关,表示MongoDB正在计算查询结果的数量。
      11. SUBPLA:未使用到索引的*or*查询的*stage*返回。这表示查询中使用了‘or`操作符,但MongoDB没有找到可以使用的索引来优化查询。
      12. TEXT:使用全文索引进行查询时的阶段。
      13. PROJECTION:限定返回字段时的阶段。这表示查询结果中的文档只包含了指定的字段。
      
      `queryPlanner.winningPlan.stage`字段的具体值取决于查询的复杂性、集合的结构、索引的可用性等多种因素。通过分析这个字段,你可以了解MongoDB是如何执行你的查询的,并据此优化查询性能。例如,如果你发现查询正在执行全表扫描(COLLSCAN),而你预期它应该使用索引(IXSCAN),那么你可能需要检查你的索引是否正确创建,或者查询条件是否可以有效利用索引。
      
    • inputStage:这表示执行计划的输入阶段。如果inputStage中包含了IXSCAN(索引扫描),则说明查询经过了索引。

  • executionStats.totalDocsExamined:这个字段表示查询过程中扫描的文档数。如果这个数字远小于集合中的文档总数,且查询条件中包含了索引字段,那么很可能查询经过了索引。如果这个数字接近或等于集合中的文档总数,那么可能查询没有有效地使用索引。

当你在 MongoDB 中使用 explain() 方法时,默认情况下它会返回一个简化版的执行计划,称为“queryPlanner”模式,该模式不包含实际的执行统计信息。

为了获取详细的执行统计信息,包括 totalDocsExamined 这样的指标,你需要以特定的模式调用 explain() 方法。你应该使用 "executionStats" 模式来获得更完整的执行信息,这将包括实际运行查询时的统计信息。

这里是使用 "executionStats" 模式的示例:

1
db.yourCollection.find(yourQuery).explain("executionStats");

这将返回一个包含 executionStats 字段的输出,该字段将提供查询的详细执行信息,包括:

  • executionSuccess: 是否成功执行了查询。
  • nReturned: 返回的文档数量。
  • executionTimeMillis: 查询的执行时间(毫秒)。
  • totalKeysExamined: 被检查的索引键总数。
  • totalDocsExamined: 被检查的文档总数。
  • executionStages: 查询执行的各个阶段的详细信息。

确保在生产环境中谨慎使用 "executionStats" 模式,因为它实际上会执行查询并收集统计信息,这可能会对性能产生轻微的影响,尤其是在处理大量数据时。在开发和测试环境中使用此模式来调试和优化查询是最佳实践。

此外,如果你使用的是 MongoDB 的可视化工具如 MongoDB Compass,它也提供了类似的 explain() 功能,并且可以选择不同的模式来查看执行计划和统计信息。

0%