从节点默认不支持建索引,必须先临时提升为副本集主节点:secondary节点因设计限制禁止createIndex,需通过rs.stepUp()临时升主、background:true建索引、rs.stepDown()及时降级,且索引仅本地有效、不复制,读路由须客户端显式配置readPreference或tags。

从节点默认不支持建索引,必须先临时提升为副本集主节点
MongoDB 副本集的 secondary 节点默认禁止执行 createIndex,直接运行会报错:not master。这不是权限问题,而是设计限制:索引构建会触发大量磁盘 I/O 和内存占用,可能影响复制延迟和从节点稳定性。所以想“在从节点上单独建索引”,本质是临时把它变成主节点来操作——但要注意,它只是你本地视角的“主”,不影响其他节点角色。
实操前确认:rs.status() 查看目标节点状态,确保它健康且能被选举;检查 rs.conf() 中该节点的 priority 值(建议 ≥1),否则即使手动 rs.stepUp() 也可能失败。
- 连接到目标从节点的
mongoshell(不是 mongos) - 执行
rs.stepUp(),等待几秒,再用db.isMaster().ismaster确认返回true - 此时可安全运行
db.collection.createIndex({ field: 1 }, { background: true })——务必加background: true,否则会阻塞整个节点的读写 - 索引建完后,立即执行
rs.stepDown(60)(参数是秒数,让出主位并避免被立刻重选)
建完索引后,该节点仍不能直接服务读请求,除非显式配置 readPreference
即使索引已存在,应用发来的查询默认还是走主节点(除非你改了驱动配置)。MongoDB 不会因为某个 secondary 有特定索引就自动把匹配查询路由过去——它没有“索引感知路由”机制。所以“分担读压力”这一步,完全依赖客户端控制。
以 Node.js 的 mongodb 驱动为例,查询时需显式指定 readPreference:
db.collection.find({ field: "value" }).readPref("secondaryPreferred")
注意:secondaryPreferred 是最常用选项,它会在 secondary 可用时优先发过去;如果所有 secondary 不可用,则 fallback 到 primary。但如果你只希望**固定打到这个刚建好索引的节点**,就得用 readPreferenceTags 配合节点标签(比如给该节点加 tag { "role": "index-heavy" }),并在查询中指定:{ "readPreference": { "mode": "nearest", "tags": [{ "role": "index-heavy" }] } }。
- 标签需在
rs.reconfig()中提前设置,且要重启节点或等配置同步生效 -
nearest模式下,带 tag 的查询只会发给匹配标签的节点,不 fallback - 别忘了验证:在目标节点上运行
db.currentOp({ "secs_running": { "$gt": 0 } }),看是否有查询正在执行
背景建索引期间,复制延迟可能飙升,secondary 上的读可能变慢甚至超时
background: true 并不等于“零影响”。它只是让建索引过程让出锁、分片执行,但依然会持续扫描全表、写入索引数据块、触发 page fault。尤其当集合很大或内存不足时,secondary 的 oplog 应用线程可能因资源争抢而滞后。
典型现象:rs.status() 显示该节点的 optimeDate 落后 primary 几分钟,同时 db.serverStatus().metrics.repl.buffer.count 持续上涨。这时发往它的读请求可能因等待最新数据而卡住(尤其用了 readConcern: "majority")。
- 建索引前,用
db.collection.stats().size和.countDocuments()评估数据量级 - 监控
db.currentOp({ "secs_running": { "$gt": 30 } }),重点看secs_running和ns字段 - 如果 oplog 延迟已 >30 秒,建议暂停建索引,先调大
replWriterThreadCount或清理慢查询
索引不会自动同步到其他 secondary,也不能靠复制机制“补上”
这是最容易误解的一点:你在某一个 secondary 上建的索引,永远不会出现在其他副本集成员上。MongoDB 的 oplog 只记录文档增删改,不记录 DDL(如 createIndex)。也就是说,这个索引纯属该节点本地私有。
好处是你可以做差异化优化——比如 A 节点专跑 report 查询,建复合索引;B 节点专跑 realtime 查询,建 TTL 索引。但代价是运维复杂度陡增:每次扩缩容、故障恢复、版本升级,都得人工核对各节点索引一致性。
- 用
db.collection.getIndexes()在每个节点上单独检查,别只查 primary - 自动化脚本里必须包含
rs.slaveOk()+db.getSiblingDB("admin").runCommand({ "listDatabases": 1 })来遍历所有节点 - 如果某节点意外降级又重登,之前建的索引还在,但若它被重新初始化(比如删除 data 目录再 sync),索引就彻底丢失了
索引是节点级的,不是副本集级的——这个前提一旦忽略,后面所有分担读压力的设计都会失效。真正难的不是命令怎么敲,而是理清“哪个节点有什么索引”“查询是否真打到了它”“延迟高了怎么归因”。











