Secondary节点不能执行createIndex(),因MongoDB强制索引创建必须在Primary发起并同步;前台建索引会阻塞读写,应使用background:true并在低峰期操作;聚合索引需按$match等值字段、$sort字段顺序及方向严格设计;Secondary默认不响应读请求,须配置readPreference并验证执行节点。

为什么不能直接在Secondary节点上执行 createIndex()
副本集的从节点(Secondary)默认禁止写操作,包括建索引。即使你用 mongosh 连上 Secondary 并执行 db.collection.createIndex(),也会立即报错:not master 或 not primary。MongoDB 要求所有索引创建必须由 Primary 发起,并由副本集同步到所有数据节点。强行绕过(比如改配置、关复制)会破坏一致性,不被允许。
必须在Primary上执行,但要避开业务高峰期
在已有大量数据的集合上建复合索引,默认是前台阻塞式(background: false),会锁表、阻塞读写,对聚合类查询密集的物联网日志场景尤其危险。正确做法是:
- 确认当前 Primary 节点(用
rs.status()查stateStr为PRIMARY的成员) - 使用
background: true选项,避免长时间阻塞:db.logs.createIndex({ "device_id": 1, "timestamp": -1, "event_type": 1 }, { background: true }) - 避开凌晨低峰期以外的时间段 —— 即使后台建索引,仍会持续占用 I/O 和内存,可能拖慢主节点响应
- 若集合超大(>100GB),考虑先在单节点测试索引构建耗时:
db.runCommand({collStats: "logs"})看文档数和平均大小
聚合专用索引设计:字段顺序和排序方向不能反
针对聚合管道(如 $match + $sort + $group)的索引,不是“有索引就行”,字段顺序直接影响是否命中。例如以下聚合:
db.logs.aggregate([
{ $match: { device_id: "D-789", event_type: "alert" } },
{ $sort: { timestamp: -1 } },
{ $limit: 50 }
])
对应最优索引是:db.logs.createIndex({ "device_id": 1, "event_type": 1, "timestamp": -1 })。关键点:
- 等值字段(
$match中的device_id、event_type)放前面,升序降序不影响等值查询 - 排序字段(
$sort的timestamp)紧接其后,且方向必须与$sort一致(这里用-1) - 不能把
timestamp放最前——那样$match条件无法利用索引前缀,索引基本失效 - 避免在该索引上加
unique: true,除非业务逻辑真需要字段组合唯一(多数聚合场景不需要)
建完索引后,Secondary节点不会自动启用只读聚合优化
Secondary 默认不响应读请求。即使索引已同步完成,你也必须显式开启读能力,并指定读偏好(read preference):
- 在连接字符串中加
?readPreference=secondaryPreferred,或代码里设置readPreference: 'secondaryPreferred' - 确保从节点配置了
slaveOk: true(MongoDB 6.0+ 已废弃该参数,改用readPreference控制) - 验证是否真走 Secondary:在聚合命令后加
.explain("executionStats"),检查host字段是否为 Secondary 的 IP - 注意:如果 Primary 不可用,Secondary 上的聚合查询可能因 oplog 同步延迟而返回旧数据——这对实时性要求高的告警聚合不可接受
建索引本身不难,难的是让聚合真正落到 Secondary 上跑,同时不拖垮 Primary。很多人卡在 readPreference 配置或索引字段顺序上,而不是语法错误。











