MongoDB 索引管理

什么是索引?

索引是数据库中用于提高数据检索效率的数据结构。它类似于书籍的目录,可以帮助快速定位和访问特定数据。

在数据库中,索引是根据一个或多个列的值创建的数据结构。它存储了这些列的值以及对应的数据记录的物理位置信息。通过使用索引,数据库可以更快地定位和访问满足特定查询条件的数据,而不需要扫描整个数据集。

索引可以大大提高数据库的查询性能,特别是在处理大量数据时。它可以减少数据库的 IO 操作,加快数据的检索速度。常见的索引类型包括 B 树索引、哈希索引、全文索引等,不同的索引类型适用于不同的查询场景。

注意:索引的创建和维护会占用额外的存储空间和计算资源,并且会增加数据的插入、更新和删除操作的成本。因此,在设计数据库时需要权衡索引的使用,选择合适的索引策略,以满足查询性能的需求。

MongoDB 和索引

在 MongoDB 中,索引也是用于提高数据检索效率的重要机制。MongoDB 支持多种类型的索引,包括 B 树索引、哈希索引、全文索引等。

MongoDB 的默认索引类型是 B 树索引,它适用于大多数的查询场景。B 树索引可以按照指定的字段或字段组合进行索引,以加快对这些字段的查询速度。例如,可以通过在集合上创建索引来加快对某个字段的精确匹配查询或范围查询的速度。

除了 B 树索引,MongoDB 还支持哈希索引。哈希索引适用于对字段进行哈希计算的查询,可以提供快速的等值查询。但是,哈希索引不支持范围查询,因此在使用哈希索引时需要注意查询的方式。

此外,MongoDB 还支持全文索引,用于在文本数据中进行全文搜索。全文索引可以在文本字段上创建,以支持关键字搜索、模糊匹配等操作。

注意,索引的创建和维护会占用额外的存储空间和计算资源,并且会增加数据的插入、更新和删除操作的成本。因此,在设计 MongoDB 数据库时需要权衡索引的使用,选择合适的索引策略,以满足查询性能的需求。

TTL 索引

TTL(Time-To-Live)索引是 MongoDB 中的一种特殊索引,用于自动删除过期的文档。TTL索引可以根据文档中的某个字段的时间戳来自动删除文档。TTL索引可以用于各种场景,如缓存、日志等。

MongoDB 索引管理

createIndex() 创建索引

MongoDB 使用 createIndex() 方法来创建索引。createIndex() 方法基本语法格式如下所示:

db.collection.createIndex(keys, options)

说明:

  • key  为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。

  • options  为创建索引的其他选项,可选参数列表如下:

MongoDB 索引 

createIndex() 示例

(1)为单个字段创建索引,例如:

# 查看 col 集合文档
> db.col.find()
{ "_id" : ObjectId("64e71af810366fa87109a12f"), "name" : "张三", "age" : 18, "email" : "zhangsan@outlook.com" }
{ "_id" : ObjectId("64e71af810366fa87109a130"), "name" : "李四", "age" : 22, "email" : "lisi@qq.com" }
{ "_id" : ObjectId("64e71af810366fa87109a131"), "name" : "王五", "age" : 26, "email" : "wangwu@sina.com.cn" }
{ "_id" : ObjectId("64e71af810366fa87109a132"), "name" : "赵六", "age" : 27, "email" : "zhaoliu@gmail.com" }
{ "_id" : ObjectId("64e71af810366fa87109a133"), "name" : "顾七", "age" : 30, "email" : "guqi@qq.com" }
{ "_id" : ObjectId("64e71af810366fa87109a134"), "name" : "何八", "age" : 42, "email" : "heba@outlook.com" }

# 为 name 字段创建升序索引
> db.col.createIndex({name:1})
{
       "numIndexesBefore" : 1,
       "numIndexesAfter" : 2,
       "createdCollectionAutomatically" : false,
       "ok" : 1
}

(2)为多个字段创建索引,createIndex() 方法中你也可以设置使用多个字段创建索引,在关系型数据库中称作复合索引。例如:

> db.col.createIndex({ name:1, email:-1 })
{
       "numIndexesBefore" : 2,
       "numIndexesAfter" : 3,
       "createdCollectionAutomatically" : false,
       "ok" : 1
}

(3)在后台创建索引,例如:

> db.col.createIndex({ name:1, age:-1 }, { background: true })
{
       "numIndexesBefore" : 3,
       "numIndexesAfter" : 4,
       "createdCollectionAutomatically" : false,
       "ok" : 1
}

上面示例中,通过在创建索引时加 background:true 的选项,让创建工作在后台执行。

(4)创建 TTL 索引

在 MongoDB 中,可以使用 expireAfterSeconds 选项创建 TTL 索引,例如:

db.col.createIndex({ "creatDate": 1 }, { expireAfterSeconds: 3600 })

上面示例中,col 是要创建 TTL 索引的集合名称,creatDate 是文档中的时间戳字段,expireAfterSeconds 是文档过期的时间(以秒为单位)。

a、假设您有一个名为 logs 的集合,其中包含一个名为 createdAt 的时间戳字段。您想要创建一个 TTL 索引,以自动删除 30 天前的日志。以下是使用 MongoDB 创建 TTL 索引的步骤:

db.logs.createIndex({ "createdAt": 1 }, { expireAfterSeconds: 2592000 })

此示例中,createdAt 是时间戳字段,expireAfterSeconds 是文档过期的时间,这里设置为 2592000 秒(即30天)。

b、假设您已经创建了一个 TTL 索引,用于自动删除 30 天前的日志。以下是使用 TTL 索引的步骤:

db.logs.insertOne({ "message": "This is a log message", "createdAt": new Date() })

此示例中,message 是日志消息,createdAt 是当前时间戳。30 天后,TTL 索引将自动删除 30 天前的日志。

c、设置记录在指定的日期点自动清除。假如设置 A 记录在 2019 年 1 月 22 日晚上 11 点左右删除,则在 A 记录中需添加 "ClearUpDate": new Date('Jan 22, 2019 23:00:00'),并且索引中 expireAfterSeconds 设值为 0。例如:

db.col.createIndex({ "ClearUpDate": 1 },{ expireAfterSeconds: 0 })

TTL 索引注意事项:

(1)索引关键字段必须是 Date 类型。

(2)非立即执行:扫描 Document 过期数据并删除是独立线程执行,默认 60s 扫描一次,删除也不一定是立即删除成功。

(3)TTL 仅支持单字段索引,混合索引不支持。

getIndexes() 获取索引

返回一个数组,其中包含一个文件列表,用于标识和描述集合上的现有索引,包括隐藏索引。例如:

test> db.col.getIndexes()
[
 { v: 2, key: { _id: 1 }, name: '_id_' },
 { v: 2, key: { name: 1 }, name: 'name_1' },
 { v: 2, key: { name: 1, email: -1 }, name: 'name_1_email_-1' },
 {
   v: 2,
   key: { name: 1, age: -1 },
   name: 'name_1_age_-1',
   background: true
 },
 { v: 2, key: { createDate: 180 }, name: 'createDate_180' }
]

上面查看 col 集合的所有索引。

getIndexes() 行为

  • 客户端断开

从 MongoDB 4.2 开始,如果发出 db.collection.getIndexes() 的客户端在操作完成之前断开连接,MongoDB 将使用 killOp 标记以终止 db.collection.getIndexes()。

副本集成员状态限制

从 MongoDB 4.4 开始,要在副本集成员上运行 listIndexes 操作,成员必须处于 PRIMARY 或 SECONDARY 状态。如果成员处于其他状态,如 STARTUP2,则操作会出错。

在以前的版本中,当成员处于 STARTUP2 时也会运行操作。这些操作会等到成员过渡到 RECOVERING 状态。

  • 通配符索引

从 MongoDB 6.3、6.0.5 和 5.0.16 开始,wildcardProjection 字段以提交的形式存储索引投影。早期版本的服务器可能会以规范化形式存储投影。

服务器使用索引的方式相同,但你可能会注意到 listIndexes 和 db.collection.getIndexes() 命令的输出有所不同。

  • Atlas Search 索引

getIndexes() 不返回有关 Atlas Search 索引的信息。

  • 所需访问权限

运行 db.collection.getIndexes() 时,用户必须拥有在集合上运行 listIndexes 的权限。

内置角色 read 提供了运行 db.collection.getIndexes() 的所需权限。

totalIndexSize() 索引大小

查看集合中所有索引的总大小。如果索引使用了前缀压缩(WiredTiger 的默认设置),返回的大小将反映压缩后的大小。

从 MongoDB 4.4 开始,要在副本集成员上运行 collStats 操作,成员必须处于 PRIMARY 或 SECONDARY 状态。如果成员处于其他状态,如 STARTUP2,则操作会出错。

在以前的版本中,当成员处于 STARTUP2 时也会运行操作。这些操作会等到成员过渡到 RECOVERING 状态。

示例:

test> db.col.totalIndexSize()
167936

上面示例查看 col 集合的大小。

dropIndexes() 删除索引

从集合中删除指定的一个或多个索引(_id 字段上的索引和最后剩余的分片键索引除外)。语法如下:

db.collection.dropIndexes(indexes)

该方法接受 indexes 可选参数,该参数可以是一个字符串、一个文档或者一个字符串数组,指定要删除的一个或多个索引。

如果要从集合中删除除 _id 索引以外的所有索引,请省略 indexes 参数。

如果要删除单个索引,可指定索引名称、索引规范文档(除非该索引是文本索引)或索引名称数组。

如果要删除文本索引,请指定索引名称或索引名称数组,而不是索引规范文档。

注意,要删除多个索引(从 MongoDB 4.2 开始提供),请指定一个索引名称数组。

您可以使用该方法来做如下事情:

(1)从集合中删除除 _id 以外的所有索引。例如:

db.collection.dropIndexes()

(2)从集合中删除指定索引。要指定索引,可以通过以下两种方法之一:

    a、索引规范文件(除非该索引是文本索引,在这种情况下,使用索引名称来删除):

db.collection.dropIndexes( { a: 1, b: 1 } )

上面示例中,匹配 a 和 b 字段均为升序的索引。

    b、索引名称:

db.collection.dropIndexes( "a_1_b_1" )

上面示例中,将删除名为 a_1_b_1 的索引。

(3)从集合中删除指定索引。(从 MongoDB 4.2 开始可用)。要指定要删除的多个索引,请向该方法传递一个索引名称数组:

db.collection.dropIndexes([ "a_1_b_1", "a_1", "a_1__id_-1" ])

上面示例,将删除三个索引。如果索引名称数组中包含一个不存在的索引,则该方法会出错,不会删除任何指定的索引。

dropIndexes() 方法行为

从 MongoDB 6.0 开始,如果您尝试使用它来删除最后一个剩余的分片键兼容索引,db.collection.dropIndexes() 会引发错误。将 “*” 传递给 db.collection.dropIndexes() 会删除除 _id 索引和最后一个剩余的分片键兼容索引(如果存在)之外的所有索引。

从 MongoDB 5.2 开始,您可以使用 db.collection.dropIndexes() 删除同一集合上的现有索引,即使另一个索引正在进行生成。在早期版本中,如果在索引构建过程中尝试删除其他索引,会导致 "BackgroundOperationInProgressForNamespace" 错误。

  • 仅终止相关查询

从 MongoDB 4.2 开始,dropIndexes() 操作仅终止使用要删除的索引的查询。这可能包括将索引视为查询规划的一部分的查询。

在 MongoDB 4.2 之前,删除集合上的索引将杀死集合上的所有打开查询。

  • 资源锁定

已在 4.2 版中更改。

db.collection.dropIndexes() 会在操作期间获得指定集合的独占锁。对该集合的所有后续操作都必须等到 db.collection.dropIndexes() 释放锁。

在 MongoDB 4.2 之前,db.collection.dropIndexes() 会获得父数据库上的独占锁,从而阻塞对数据库及其所有集合的所有操作,直到操作完成。

  • 索引名称

如果传递给该方法的索引名数组包含一个不存在的索引,则该方法会出错,不会丢弃任何指定的索引。

    • _id 索引:不能删除 _id 字段的默认索引。

    • 文本索引:要删除文本索引,请指定索引名称而不是索引规范文档。

  • 停止进行中的索引构建

从 MongoDB 4.4 开始,如果 db.collection.dropIndexes() 指定的索引仍在构建中时,db.collection.dropIndexes() 会尝试停止正在进行的索引构建。停止索引构建与放弃已构建索引的效果相同。在 MongoDB 4.4 之前的版本中,db.collection.dropIndexes() 会返回错误信息。

对于副本集,在主数据库中运行 db.collection.dropIndexes()。主节点会停止索引构建,并创建相关的 “abortIndexBuild” oplog 条目。复制了 “abortIndexBuild” oplog 条目的次副本会停止正在进行的索引构建并丢弃构建任务。

  • 隐藏索引

从 4.4 版开始,MongoDB 增加了从查询规划器中隐藏或取消隐藏索引的功能。通过向规划器隐藏索引,用户可以在不实际丢弃索引的情况下评估丢弃索引的潜在影响。

如果在评估后,用户决定丢弃索引,那么用户就可以丢弃隐藏的索引;也就是说,不需要先解除隐藏才能丢弃索引。

但是,如果影响是负面的,用户可以取消隐藏索引,而不必重新创建已放弃的索引。而且,由于索引在隐藏时会得到全面维护,因此一旦取消隐藏,索引就能立即投入使用。

dropIndexes() 方法示例

# 查看 col 集合中所有的索引
test> db.col.getIndexes()
[
 { v: 2, key: { _id: 1 }, name: '_id_' },
 { v: 2, key: { name: 1 }, name: 'name_1' },
 { v: 2, key: { name: 1, email: -1 }, name: 'name_1_email_-1' },
 {
   v: 2,
   key: { name: 1, age: -1 },
   name: 'name_1_age_-1',
   background: true
 },
 { v: 2, key: { createDate: 180 }, name: 'createDate_180' }
]

# 删除名为 “name_1” 的索引
test> db.col.dropIndex("name_1")
{ nIndexesWas: 5, ok: 1 }

# 获取 col 中的所有索引,名为 name_1 的索引已经被删除
test> db.col.getIndexes()
[
 { v: 2, key: { _id: 1 }, name: '_id_' },
 { v: 2, key: { name: 1, email: -1 }, name: 'name_1_email_-1' },
 {
   v: 2,
   key: { name: 1, age: -1 },
   name: 'name_1_age_-1',
   background: true
 },
 { v: 2, key: { createDate: 180 }, name: 'createDate_180' }
]

# 删除 col 集合下面所有的索引,除了 _id_ 索引
test> db.col.dropIndexes()
{
 nIndexesWas: 4,
 msg: 'non-_id indexes dropped for collection',
 ok: 1
}

# 查看 col 集合全部索引,发现 _id_ 索引没有被删除
test> db.col.getIndexes()
[ { v: 2, key: { _id: 1 }, name: '_id_' } ]
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号