我们都知道在数据库中拥有适当的索引以有效地完成其工作是多么重要。我们在日常生活中一直使用索引来导入日常任务,如果没有索引,所有任务都会完成,但时间相对较长。
索引的基本工作
想象一下,我们有大量的信息,我们想看一些非常特别的东西,但我们不知道它在哪里。我们将花费大量时间来查找特定的数据。
如果我们有关于所有数据片段的某种信息,这项工作将很快完成,因为现在我们知道在哪里查找,而无需花费太多时间搜索每个特定数据的每条记录。
索引是一种特殊的数据结构,它存储一些记录信息以遍历该特定数据。可以按升序或降序创建索引,以支持有效的相等匹配和基于范围的查询操作。
索引构建策略和考虑
当我们考虑构建索引时,必须考虑许多方面,例如经常使用的关键数据集、基数、该集合中的写入比率、可用内存和存储。
如果集合中没有索引,MongoDB 将在每次执行任何可能包含数百万条记录的查询时进行一次完整的集合扫描。这不仅会减慢操作速度,还会增加其他操作的等待时间。
我们还可以同时在同一个集合上创建多个索引,从而节省大量使用 createIndexes命令扫描集合的时间。

限制
有足够的内存来容纳工作集是非常重要的。不需要所有索引都需要适合 RAM。
在 v4.0 之前,索引键限制应小于 1024 字节。从 fcv 4.2 开始 v4.2,此限制被删除。
与索引名称相同,在 fcv 4.0 及以下版本的 db 中最多可以增加 127 个字节。db v4.2 和 fcv 4.2 降低了此限制。
在任何给定的单个集合中只能创建 64 个索引。
MongoDB中的索引类型
在看各种索引类型之前,让我们看看索引名称是什么样子的。
索引的默认名称是使用下划线作为分隔符的索引键和索引中每个键的方向(即 1 或 -1)的串联。例如,在{ mobile : 1, points: -1 } 上创建的索引的名称为mobile_1_points_-1 。
我们还可以创建一个自定义的、更易读的名称
1
db.products.createIndex({ mobile: 1, points: -1 }, { name: "query for rewards points" })
索引类型
MongoDB提供各种类型的索引来支持各种数据和查询。
单字段索引:在单字段索引中,索引是在文档中的单个字段上创建的。它可以在创建索引时双向遍历,而不管排序顺序。
句法:
1
db.collection.createIndex({"<fieldName>" : <1 or -1>})
这里 1 表示按升序指定的字段,-1 表示降序。
例子:
Shell
1 | db.inventory.createIndex({productId:1}); |
复合索引:在复合索引中,我们可以在多个字段上创建索引。复合索引中列出的字段顺序很重要。例如,如果复合索引由{ userid: 1, score: -1 }组成,则索引首先按userid排序,然后在每个userid值内按score排序。
句法:
1
db.collection.createIndex({ <field1>: <1/–1>, <field2>: <1/–1>, … })
例子:
1
db.students.createIndex({ userid: 1, score: -1 })
多键索引:MongoDB 使用多键索引来索引存储在数组中的内容。当我们在包含数组值的字段上创建索引时,MongoDB 会自动为数组的每个元素创建一个单独的索引。我们不需要显式指定多键类型,因为如果索引字段包含数组值,MongoDB 会自动处理是否创建多键索引。
句法:
1
db.collection.createIndex({ <field1>: <1/–1>})
例子:
1
db.students.createIndex({ "addr.zip":1})
地理空间索引:MongoDB 提供了两个特殊的索引:返回结果时使用平面几何的2d 索引和使用球面几何返回结果的2dsphere 索引。
句法:
1
db.collection.createIndex({ <location field> : "2dsphere" })
*其中 <location field> 是一个字段,其值为 GeoJSON 对象或旧坐标对。
例子:
1
db.places.createIndex({ loc : "2dsphere" })
文本索引:使用文本索引类型,MongoDB 支持在集合中搜索字符串内容。一个集合只能有一个文本搜索索引,但该索引可以覆盖多个字段。
句法:
1
db.collection.createIndex({ <field1>: text })
例子:
1
db.reviews.createIndex({ comments: "text" })
哈希索引:如果是哈希基索引,MongoDB 创建索引字段的哈希值。这种类型的索引主要用于我们希望数据分布均匀的情况,例如在分片集群环境的情况下。
句法:
1
db.collection.createIndex({ _id: "hashed" })
从版本 4.4 开始,复合哈希索引适用
特性
唯一索引:指定后,MongoDB 将拒绝索引字段的重复值。它不允许插入另一个包含相同键值对的文档,该键值对被索引。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
> db.cust_details.createIndex({Cust_id:1},{unique:true})
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
> db.cust_details.insert({"Cust_id":"39772","Batch":"342"})
WriteResult({ "nInserted" : 1 })
> db.cust_details.insert({"Cust_id":"39772","Batch":"452"})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 11000,
"errmsg" : "E11000 duplicate key error collection: student.cust_details index: Cust_id_1 dup key: { Cust_id: \"39772\" }"
}
})
部分索引:部分索引只索引符合过滤条件的文档。
1
2
3
4
5
6
7
8
9
10
11
12
13
db.restaurants.createIndex({ cuisine: 1, name: 1 },{ partialFilterExpression: { rating: { $gt: 5 } } })
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
TTL 索引:TTL 索引是特殊的单字段索引,可用于在一段时间内从集合中自动删除文档。
1
2
3
db.eventlog.createIndex({ "lastModifiedDate": 1 }, { expireAfterSeconds: 3600 })
lastModifiedDate_1
稀疏索引:稀疏索引仅包含具有索引字段的文档的条目,即使索引字段包含空值。
1
2
3
db.addresses.createIndex({ "email": 1 }, { sparse: true })
email_1
隐藏索引:隐藏索引对查询规划器不可见,不能用于支持查询。除了对规划器隐藏之外,隐藏索引的行为类似于非隐藏索引。
创建一个新的隐藏索引:
1
db.addresses.createIndex({ pincode: 1 },{ hidden: true });
要将现有索引更改为隐藏索引(仅适用于具有 fcv 4.4 或更高版本的 db):
1
2
3
db.addresses.hideIndex({ pincode: 1 }); // Specify the index key specification document
or
db.addresses.hideIndex( "pincode_1" ); // Specify the index name
要取消隐藏任何隐藏索引:
索引名称或键可用于隐藏索引。
1
2
3
db.addresses.unhideIndex({ pincode: 1 }); // Specify the index key specification document
or
db.addresses.unhideIndex( "pincode_1" ); // Specify the index name
滚动索引建立在副本集上
从MongoDB 4.4 及更高版本开始,索引构建同时发生在所有数据承载节点上。对于因索引构建而无法容忍性能问题的工作负载,我们可以采用滚动索引构建策略的方法。
**笔记**
唯一索引
要使用以下过程创建唯一索引,您必须在此过程中停止对集合的所有写入。
如果在此过程中无法停止对集合的所有写入,请不要使用此页面上的过程。相反,通过在副本集的主节点上发出 db.collection.createIndex() 在集合上构建您的唯一索引。
操作日志大小
确保您的 oplog 足够大,以允许索引或重新索引操作完成,而不会落后太远而无法赶上。
程序
1 . 停止一个辅助并在不同的端口号上作为独立的重新启动。
在此过程中,我们将一次停止任何一个辅助节点,并从配置文件中禁用复制参数,并在配置文件中的 setParameter部分下将 disableLogicalSessionCacheRefresh 设置为true 。
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
net:
bindIp: localhost,<hostname(s)|ip address(es)>
port: 27217
# port: 27017
#replication:
# replSetName: myRepl
setParameter:
disableLogicalSessionCacheRefresh: true
我们只需要对上述设置进行更改,其余保持不变。
完成上述更改后,保存并重新启动该过程。
1
mongod --config <path/To/ConfigFile>
或者
1
sudo systemctl start mongod
现在,mongod 进程将以独立模式在端口 27217 上启动。
2.建立索引
连接到端口 27217 上的 mongod 实例。切换到所需的数据库和集合以创建索引。
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mongo –port 27217 -u ‘username’ –authenticationDatabase admin
> use student
switched to db student
> db.studentData.createIndex( { StudentID: 1 } );
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
3.重启进程mongod作为副本集成员
在所需的索引构建完成后,我们可以将节点添加回副本集成员。
撤消在上述第一步中所做的配置文件更改。使用原始配置文件重新启动 mongod 进程。
1
2
3
4
5
6
7
8
9
net:
bindIp: localhost,<hostname(s)|ip address(es)>
port: 27017
replication:
replSetName: myRepl
保存配置文件后,重启进程,让它成为次要的。
1
mongod --config <path/To/ConfigFile>
或者
1
sudo systemctl start mongod
4. 对剩余的次级重复上述过程
一旦正在进行的节点变为辅助节点并且没有延迟,则一次一个节点再次重复该过程。
- 停止一个辅助设备并作为独立设备重新启动。
- 建立索引。
- 作为副本集成员重新启动 mongod 进程。
5. 索引建立在主节点上
在所有辅助节点中完成索引构建活动后,使用与上述相同的过程在最后一个剩余节点上创建索引。
- 连接主节点并发出rs.stepDown(); 一旦成功退出,它就会成为辅助节点并选出一个新的主节点。按照从一到三的步骤来构建索引。
- 停止辅助节点并作为独立节点重新启动。
- 建立iondex。
- 作为副本集成员重新启动 mongod 进程。
滚动索引建立在分片集群上
从MongoDB 4.4 及更高版本开始,索引构建同时发生在所有数据承载节点上。对于因索引构建而无法容忍性能问题的工作负载,我们可以采用滚动索引构建策略的方法。
**笔记**
唯一索引
要使用以下过程创建唯一索引,您必须在此过程中停止对集合的所有写入。
如果在此过程中无法停止对集合的所有写入,请不要使用此页面上的过程。相反,通过在副本集的主节点上发出 db.collection.createIndex() 在集合上构建您的唯一索引。
操作日志大小
确保您的 oplog 足够大,以允许索引或重新索引操作完成,而不会落后太远而无法赶上。
程序
1.停止平衡器
为了在分片集群中以滚动方式创建索引,有必要停止平衡器,以免我们最终得到不一致的索引。
连接到 mongos 实例并运行sh.stopBalancer()以禁用平衡器。
如果正在进行任何活动迁移,平衡器将仅在正在进行的迁移完成后停止。
我们可以使用以下命令检查平衡器是否停止,
1
sh.getBalancerState()
如果平衡器停止,输出将为 false 。
2.确定集合的分布
为了以滚动方式构建索引,有必要知道集合驻留在哪些分片上。
连接到其中一个 mongos 并刷新缓存,以便我们获得要为其构建索引的分片中集合的新分布信息。
例子:
我们想在学生数据库的 studentData 集合中创建一个索引。
我们将运行以下命令来获取该集合的新分布。
1
db.adminCommand( { flushRouterConfig: "students.studentData" } );
1
db.records.getShardDistribution();
我们将获得包含集合的分片输出:
1
2
3
4
5
6
7
8
9
10
11
12
Shard shardA at shardA/s1-mongo1.net:27018,s1-mongo2.net:27018,s1-mongo3.net:27018
data : 1KiB docs : 50 chunks : 1
estimated data per chunk : 1KiB
estimated docs per chunk : 50
Shard shardC at shardC/s3-mongo1.net:27018,s3-mongo2.net:27018,s3-mongo3.net:27018
data : 1KiB docs : 50 chunks : 1
estimated data per chunk : 1KiB
estimated docs per chunk : 50
Totals
data : 3KiB docs : 100 chunks : 2
Shard shardA contains 50% data, 50% docs in cluster, avg obj size on shard : 40B
Shard shardC contains 50% data, 50% docs in cluster, avg obj size on shard : 40B
从上面的输出中,我们可以看到students.studentData 存在于shardA 和shardC 上,我们需要分别在shardA 和shardC 上建立索引。
3. 在包含集合块的分片上建立索引
在包含集合块的每个分片上执行以下过程。
3.1。停止一个辅助设备并作为独立设备重新启动
对于已识别的分片,停止其中一个辅助节点并进行以下更改。
- 将端口号更改为其他端口
- 注释掉复制参数
- 注释掉分片参数
- 在“setParameter”部分下添加skipShardingConfigurationChecks: true和 disableLogicalSessionCacheRefresh: true
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
net:
bindIp: localhost,<hostname(s)|ip address(es)>
port: 27218
# port: 27018
#replication:
# replSetName: shardA
#sharding:
# clusterRole: shardsvr
setParameter:
skipShardingConfigurationChecks: true
disableLogicalSessionCacheRefresh: true
保存配置后重启进程
1
mongod --config <path/To/ConfigFile>
或者
1
sudo systemctl start mongod
3.2. 建立索引
连接到以独立模式运行的 mongod 实例并启动索引构建过程。
在这里,我们在 StudentID 字段上按升序构建学生集合中的索引
1
2
3
4
5
6
7
8
9
10
11
12
13
> db.students.createIndex( { StudentID: 1 } )
{
"createdCollectionAutomatically" : true,
"numIndexesBefore" : 1,
"numIndexesAfter" : 2,
"ok" : 1
}
3.3. 重新启动 MongoDB 进程作为副本集节点
索引构建活动完成后,关闭实例并使用原始配置重新启动,删除参数skipShardingConfigurationChecks: true和 disableLogicalSessionCacheRefresh: true
1
2
3
4
5
6
7
8
9
10
11
12
13
net:
bindIp: localhost,<hostname(s)|ip address(es)>
port: 27018
replication:
replSetName: shardA
sharding:
clusterRole: shardsvr
保存配置后重启进程
1
mongod --config <path/To/ConfigFile>
或者
1
sudo systemctl start mongod
3.4. 对分片的剩余辅助节点重复该过程
一旦完成索引构建的节点,添加回副本集,并与其他节点同步,在其余节点上重复上述过程从 3.1 到 3.3。
3.1。停止一个辅助设备并作为独立设备重新启动
3.2. 建立索引
3.3. 重新启动 MongoDB 进程作为副本集节点
3.5. 索引建立在主要基础上
在所有辅助节点中完成索引构建活动后,使用与上述相同的过程在最后一个剩余节点上创建索引。
- 连接主节点并发出rs.stepDown(); 一旦它成功降级,就会成为辅助节点并选出一个新的主节点。按照从一到三的步骤来构建索引。
- 停止辅助节点并将其作为独立节点重新启动
- 建立索引
- 重新启动进程 mongod 作为副本集成员
4. 对其他受影响的分片重复
完成其中一个已识别分片的索引构建后,在下一个已识别分片上启动步骤三中概述的过程。
5.重启平衡器
一旦我们在所有已识别的分片上建立索引,我们就可以再次启动平衡器。
连接到分片集群中的 mongos 实例,并运行 sh.startBalancer()
1
sh.startBalancer()
结论
根据访问模式选择正确的键并拥有一个好的索引比拥有多个坏的索引要好。所以,明智地选择你的索引。
原文标题:MongoDB Index Building on ReplicaSet and Shard Cluster
原文作者:桑托什·瓦玛
原文地址:https://www.percona.com/blog/mongodb-index-building-on-replicaset-and-shard-cluster/




