MongoDB 在副本集上创建索引时,默认行为是由主节点发起索引构建任务,并通过复制机制将构建指令同步到所有从节点。这种集中式构建虽然简单,但会同时占用全部节点的 CPU、磁盘 IO 和锁资源。滚动索引构建策略就是为了把影响降到最低,每次只在一个节点上创建索引。不过一旦步骤安排不当,很容易收到错误码 2220,错误名称是 IndexBuildAlreadyInProgress。这个错误并不是索引数据损坏,而是并发控制层面的拒绝:目标命名空间上已经有一个索引构建任务在运行,新的 createIndexes 请求被直接拦截。理解这个错误的产生条件,是安全实施滚动索引构建的前提。

一、错误码 2220 的产生机制
MongoDB 内部通过 IndexBuildsCoordinator 来协调副本集成员上的索引构建。当你在某个节点上执行 db.collection.createIndex() 时,服务端会先检查该集合的命名空间上是否已经存在进行中的索引构建。如果发现相同命名空间已经有一个 active 状态的索引构建任务,无论新的索引规范是什么,都会立即返回失败,错误码就是 2220。
这种设计是为了防止同一个节点上同时运行多个索引构建任务,导致磁盘写放大、锁竞争加剧,甚至产生不一致的索引元数据。需要注意的是,2220 并不区分索引定义是否相同。即便你两次提交的是完全一样的索引键和选项,只要第一次构建还没有结束,第二次提交同样会触发这个错误。这也是滚动索引构建中最常见的坑:监控脚本或运维人员重复执行了创建命令。
下面的返回示例展示了完整的错误结构:
{
"ok" : 0,
"errmsg" : "Index build already in progress",
"code" : 2220,
"codeName" : "IndexBuildAlreadyInProgress"
}
在 MongoDB 4.2 及以后的版本中,索引构建过程增加了可中断和可恢复能力,但并发限制依然存在。特别是在滚动构建场景中,节点可能刚从副本集剥离出来,但之前的构建任务并没有完全清理,就会在重新执行 createIndexes 时撞上 2220。
二、滚动索引构建的标准流程与常见触发点
滚动索引构建的核心思路是:不要在主节点上一次性构建并同步给所有从节点,而是将每个从节点依次摘除副本集,以 standalone 模式启动,在独立进程上创建索引,创建完成后再重新加入副本集。最后再处理原来的主节点。这样做的好处是,任意时刻只有一个节点在执行索引构建,其他节点继续对外提供服务,业务感知非常低。
标准流程大致如下:先从副本集中移除一个从节点,通常使用 rs.remove("host:port"),然后关闭该节点的 mongod 进程,以不带 --replSet 参数的方式重启,进入 standalone 模式。接着在 standalone 实例上执行 db.collection.createIndex()。完成后关闭 standalone 实例,恢复 --replSet 参数并启动,再通过 rs.add() 把它加回副本集。等待该节点完成初始同步和状态变为 SECONDARY 后,再处理下一个节点。
很多 2220 错误的出现,正是因为节点虽然被从副本集配置中移除,但 mongod 进程没有完全以 standalone 模式重启,或者重启后仍然加载了旧的副本集配置。此时节点内部仍然保留着 IndexBuildsCoordinator 的协调状态,一旦检测到之前的构建任务残留,就会拒绝新构建。另一个常见触发点是脚本没有等待上一步完成,比如在上一个节点还没有变回 SECONDARY 时就对下一个节点发起索引构建,而主节点上的同步任务此时正在向多个节点分发,出现了并发冲突。
如果你使用的是 MongoDB 4.4 及以上版本,也可以考虑使用 createIndexes 命令的 commitQuorum 参数。它允许你指定需要多少个副本集成员完成索引构建后,主节点才提交该索引。虽然这不是传统意义上的逐节点滚动,但能够减少对全部节点同时占用的压力。要避免 2220,仍然要确保在调用前没有旧的构建任务在跑。
三、定位并清理卡住的索引构建
出现 2220 后,第一步不是盲目重试,而是先确认当前集合上是否真的还有进行中的索引构建。可以使用 db.currentOp() 过滤索引构建命令:
db.currentOp(
{
"active" : true,
"ns" : /^test\.orders$/,
"command.createIndexes" : { $exists: true }
}
)
返回结果中如果还有匹配的文档,说明确实存在构建任务。此时可以记录下 opid,如果确认该任务已经卡死,可以使用 db.killOp(opid) 强制终止。终止之后,还需要检查是否留下了半成品索引。MongoDB 在索引构建失败或终止后,通常会自动清理临时构建数据,但在极端情况下(如磁盘满、进程崩溃后重启)可能会出现残留。可以通过 db.collection.getIndexes() 查看索引列表,确认没有多余或状态异常的索引。
如果 db.currentOp() 查不到任何构建任务,但再次创建索引仍然返回 2220,就要检查节点日志。搜索 IndexBuildAlreadyInProgress 或 buildIndex 关键字,通常能找到之前失败构建的上下文。对于从节点,还可以执行 rs.status() 查看成员健康状态,确认节点是否仍在执行 RECOVERING 或 STARTUP2 状态,这些状态下索引构建的协调逻辑可能尚未完全恢复。
清理完成后,建议先在一个从节点上以测试集合验证 createIndex() 能正常返回,再继续正式的滚动构建流程。这样可以避免在后续节点上重复踩坑。
四、副本集与分片集群中的避坑清单
在副本集环境中,规避 2220 主要依赖严格的顺序控制和状态确认。每次在节点上执行 createIndexes 前,先通过 db.currentOp() 检查目标集合没有构建任务;每次执行后,等待该节点完成构建并且状态恢复正常,再进入下一节点。同时避免在主节点和其他从节点上同时执行创建命令,因为主节点发起的构建会同步到所有在线从节点,如果某个从节点正在被手动操作,就会产生冲突。
在分片集群中,索引构建的复杂度更高。每个分片都有自己的主节点和副本集,需要在每个分片的主节点上分别执行索引创建。此时除了要处理每个分片内部的滚动构建,还要先暂停平衡器,避免在构建期间发生数据迁移。可以使用 sh.stopBalancer() 暂停平衡器,待所有分片完成索引构建后再启动。2220 错误在分片场景中同样会出现,而且因为涉及多个分片,排查范围更大,建议通过脚本统一检查所有分片主节点上的 currentOp 状态。
最后,对于生产环境,建议将滚动索引构建过程脚本化,并加入明确的重试和确认机制。每次重试前必须清理历史构建任务,而不是简单地把 createIndex() 命令再跑一遍。理解 2220 的保护逻辑,就能把滚动索引构建从容易出错的临时操作,变成稳定可控的日常维护手段。
MongoDB故障码2220滚动索引构建副本集索引维护修改时间:2026-09-19 08:28:31