MongoDB在副本集架构下运行时,读写请求并不是每个节点都能随意处理的。当客户端把一个需要排序的查询请求发到了一个备用节点上,而该节点又不允许执行这类操作时,驱动或服务器就可能返回错误码1960。很多初次接触副本集的开发者在遇到这个错误时往往一头雾水,因为同样的代码直连单节点时完全正常,切到副本集环境就频繁报错。本文将围绕错误码1960展开,重点讲解alternate备用排序规则的含义、触发条件以及完整的排查和解决方案。

错误码1960的触发原因分析
错误码1960本质上是一个副本集角色相关的错误。MongoDB副本集由一个Primary节点和若干个Secondary节点组成,默认情况下所有的写操作和大部分读操作都必须在Primary上执行。当客户端通过readPreference配置将读请求路由到Secondary节点时,如果该Secondary处于STARTUP2、RECOVERING等不可读状态,或者排序操作依赖的数据索引在备用节点上尚未同步完成,就会触发错误。
另一种常见情况是主从切换期间。当Primary发生故障,副本集正在选举新的Primary,此时旧Primary已经降级为Secondary,但客户端连接池中还保留着到旧节点的连接。此时发出去的排序查询请求会被拒绝,错误信息中通常会包含类似"not primary and secondaryOk=false"或者直接给出1960错误码的提示。
还需要注意的是,当查询中使用了collation排序规则参数时,如果备用节点上的索引没有按照相同的collation构建,MongoDB无法利用索引完成排序,只能退化为内存排序。一旦排序的数据量超过32MB的内存限制,就会抛出SortExceededMemoryLimitNoDiskUseAllowed错误,在某些驱动版本中这个错误也会被包装成1960返回。理解这条错误链路,是解决问题的关键前提。
alternate备用排序规则的正确使用方式
MongoDB从3.4版本开始引入了collation排序规则,允许在查询、索引和聚合操作中指定不同语言环境的字符串比较规则。collation中的strength参数决定了比较的严格程度,比如strength为1时忽略大小写和音调符号,strength为3则是默认的二进制级别比较。当服务器无法按照指定的排序规则完成操作时,错误信息里提到的alternate就是collation中的一个子选项,用于控制空格和标点符号的处理策略。
具体来说,collation的alternate字段有两个可选值:non-ignorable表示空格和标点符号参与比较,shifted表示这些字符在比较时被忽略或降低权重。如果查询指定的collation与索引的collation不一致,MongoDB无法使用该索引进行排序,只能走全量扫描加内存排序的路径。下面是一个指定collation的查询示例:
// 在集合上创建带排序规则的索引
db.users.createIndex(
{ name: 1 },
{ collation: { locale: "zh", alternate: "shifted", strength: 2 } }
);
// 查询时必须使用完全相同的排序规则才能命中索引
db.users.find({ name: { $gt: "张" } })
.sort({ name: 1 })
.collation({ locale: "zh", alternate: "shifted", strength: 2 });
上面代码中,如果查询时省略了collation,或者传入的collation与索引定义有任何一项参数不一致(locale、alternate、strength都必须完全相同),MongoDB都不会使用这个索引。在备用节点上执行这样的查询时,由于排序无法借助索引完成,内存压力会显著增大,进而放大了1960错误出现的概率。所以规范的做法是:凡是依赖collation的查询,索引和查询语句中的collation定义必须保持逐字段一致。
完整的故障排查与解决方案
遇到1960错误时,第一步是确认副本集的健康状态。通过rs.status()命令查看各节点的stateStr字段,确认是否存在PRIMARY节点,以及各个SECONDARY节点是否处于RECOVERING或STARTUP2状态。如果集群正在选举或恢复中,这类错误属于瞬时故障,等选举完成后会自动恢复,但客户端需要有重试机制来应对这种情况。
// 查看副本集状态,重点关注每个成员的 stateStr
rs.status().members.forEach(function(m) {
print(m.name + " => " + m.stateStr + ", 延迟: " +
(rs.status().optimesDate.lastCommittedOpTime ? "正常" : "异常"));
});
// 查看当前节点角色
db.hello().isWritablePrimary;
第二步是检查连接字符串中的readPreference配置。如果你的业务必须读备用节点,应该使用secondaryPreferred而不是secondary,前者在所有Secondary不可用时可以回退到Primary读取,后者则会直接失败。同时建议开启retryReads=true,让驱动在网络抖动或主从切换时自动重试读请求。下面是一个推荐的连接字符串示例:
mongodb://user:pass@node1:27017,node2:27017,node3:27017/mydb?replicaSet=rs0&readPreference=secondaryPreferred&retryReads=true&retryWrites=true
第三步是优化排序本身。对于大数据量的排序查询,务必确认排序字段上有匹配的索引,包括collation也完全一致。可以通过explain查看执行计划,如果看到SORT_STAGE字样说明索引没有被利用。对于无法避免的大排序,可以在服务端设置中允许排序落盘,例如在mongod启动参数中加上--setParameter internalQueryMaxBlockingSortMemoryUsageBytes=104857600,将排序内存上限提升到100MB,或者确认allowDiskUse选项在聚合操作中开启。最后,如果业务允许,考虑调整架构,把报表类的重排序查询放到独立的从库或分析节点上,避免影响主库的写入性能。通过以上几个步骤的组合,1960错误以及它背后的备用节点排序问题基本都能得到彻底解决。
MongoDB故障码1960alternate备用排序规则MongoDB排序错误修改时间:2026-09-05 21:56:42