引言
从我接触MongoDB数据库以来,那会儿主流还是3.6的时代, 接收到的知识就是在给集合添加索引的时候需要在后台创建索引,如果在前台创建索引会阻塞集合所在的数据库,导致整个库不可读写,实际上在4.2版本已经无需再遵守这样的规则。接下来进行一下简单测试。
MongoDB版本:6.0
总集合记录数37717894,表上只有默认的_id索引

一. 测试全表扫描
使用executionStats模式查看实际执行情况
set01 [direct: primary] test> db.user_mail_info.find({user_id:6000000}).explain("executionStats") { explainVersion: '1', queryPlanner: { namespace: 'test.user_mail_info', indexFilterSet: false, parsedQuery: { user_id: { '$eq': 6000000 } }, queryHash: '93EECBE7', planCacheKey: '93EECBE7', maxIndexedOrSolutionsReached: false, maxIndexedAndSolutionsReached: false, maxScansToExplodeReached: false, winningPlan: { stage: 'COLLSCAN', filter: { user_id: { '$eq': 6000000 } }, direction: 'forward' }, rejectedPlans: [] }, executionStats: { executionSuccess: true, nReturned: 1, executionTimeMillis: 36574, totalKeysExamined: 0, totalDocsExamined: 37717894, executionStages: { stage: 'COLLSCAN', filter: { user_id: { '$eq': 6000000 } }, nReturned: 1, executionTimeMillisEstimate: 4033, works: 37717896, advanced: 1, needTime: 37717894, needYield: 0, saveState: 37720, restoreState: 37720, isEOF: 1, direction: 'forward', docsExamined: 37717894 } }, command: { find: 'user_mail_info', filter: { user_id: 6000000 }, '$db': 'test' }, serverInfo: { host: 'ycf-test04', port: 27101, version: '6.0.4', gitVersion: '44ff59461c1353638a71e710f385a566bcd2f547' }, serverParameters: { internalQueryFacetBufferSizeBytes: 104857600, internalQueryFacetMaxOutputDocSizeBytes: 104857600, internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600, internalDocumentSourceGroupMaxMemoryBytes: 104857600, internalQueryMaxBlockingSortMemoryUsageBytes: 104857600, internalQueryProhibitBlockingMergeOnMongoS: 0, internalQueryMaxAddToSetBytes: 104857600, internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600 }, ok: 1, lastCommittedOpTime: Timestamp({ t: 1678417161, i: 2 }), '$clusterTime': { clusterTime: Timestamp({ t: 1678417168, i: 1 }), signature: { hash: Binary(Buffer.from("0ae2c0a2e59e7b56a87a19b7caa8281bd855cda4", "hex"), 0), keyId: Long("7202923325218095128") } }, operationTime: Timestamp({ t: 1678417161, i: 2 }) } set01 [direct: primary] test>
耗时:36S
二. 测试前台创建索引
创建索引

2.1 连续INSERT
for (var i = 1; i <= 100000; i++) {
print(i);
db.user_mail_info.insert({
mailid: i,
user_id: i+ 2,
reward: "我是一个航天员"
});
}

结论:不影响连续insert
2.2 连续update
for (var i = 1; i <= 100000; i++) {
print(i);
db.user_mail_info.update({user_id: i}, {$set: {mail_id: i}});
}

结论:不影响连续update
三. 添加索引时间测试
如下两种测试前提,集合中现有数据量大约为3kw
MongoDB版本:>= 4.4
| 无任务操作 | 连续insert | |
| 前台添加索引耗时 | 耗时:115S | 耗时:146S |
| 后台添加索引耗时 | 耗时:119S | 耗时:147S |
MongoDB版本:< 4.2
| 创建索引方式 | 当前集合读写是否被阻塞 | 当前集合所在库是否会被阻塞 |
| 前台 | 是,无法查询/更新/插入/删除 | 是,无法查询/更新/插入/删除 |
| 后台 | 否 | 否 |
结论
https://www.mongodb.com/docs/manual/core/index-creation/
Starting in MongoDB 4.2, index builds use an optimized build process that holds an exclusive lock on the collection at the beginning and end of the index build. The rest of the build process yields to interleaving read and write operations
从4.2版本开始:创建索引只需要要创建最开始和结束的时候获得一个排他锁,其他真正的过程是不阻塞读写的(突然联想到MySQL 5.7的Online DDL原理), 并且前台和后台创建效率已经相差不大。
低于4.2版本:前台创建索引会阻塞当前表和当前表所在的库,速度会更快,对服务器性能影响较小,后台则不会阻塞读写,速度比前台创建索引要满,并且对服务器影响性能稍小。