MongoDB在启用排序规则(collation)进行字符串比较或建立唯一索引时,如果配置不当,很容易抛出错误码1940。这个故障码背后通常指向caseLevel与strength等排序参数的组合不符合引擎要求,尤其是在使用非空locale且需要区分大小写的场景中。理解MongoDB内部的字符串比较机制,是彻底解决该问题的前提。

错误1940的触发场景与复现方式
错误码1940一般出现在创建集合、建立索引或执行带collation的写入操作时。MongoDB要求当locale不为空且strength大于1时,caseLevel的取值必须和比较语义自洽。比如在某些locale下,若strength设为3但caseLevel为false,引擎可能认为配置矛盾并拒绝执行,从而返回1940。
我们可以通过一段简单的Node.js代码复现这个问题。下面示例尝试在指定locale为zh且strength为3、caseLevel为false的情况下创建唯一索引,在部分MongoDB版本中就会触发1940。
const { MongoClient } = require('mongodb');
async function test() {
const client = new MongoClient('mongodb://127.0.0.1:27017');
await client.connect();
const db = client.db('demo');
const col = db.collection('users');
// 错误示范:strength=3但caseLevel=false,可能报1940
try {
await col.createIndex(
{ name: 1 },
{
unique: true,
collation: { locale: 'zh', strength: 3, caseLevel: false }
}
);
} catch (e) {
console.log('错误码:', e.code); // 可能输出 1940
}
await client.close();
}
test();
从这个例子可以看出,1940并不是数据内容本身的错误,而是排序规则参数组合的校验失败。很多开发者误以为是索引字段有重复值,其实在创建阶段就被拦截。明确这一点,才能避免在应用层做无谓的重试。
caseLevel与strength的底层原理
MongoDB的collation遵循Unicode排序算法。strength参数决定比较的精细程度:1级只看基字符,2级看重音,3级看大小写等变体。caseLevel是一个独立的开关,用来在strength为1或2时强制纳入大小写差异,或者在strength为3时明确声明大小写层级参与比较。
当caseLevel设为true,系统会在比较器中加入大小写层级判断。例如字符串Abc和abc,在caseLevel为true且strength不低于3时被视为不同,可以共存于唯一索引;若caseLevel为false,在某些locale下二者可能被归并,导致后续插入触发唯一键冲突或参数校验错误。
需要注意,caseLevel不能脱离locale单独使用。如果locale为空字符串,MongoDB使用二进制比较,本身区分大小写,此时1940一般不会出现。问题多发生在显式指定了如en、zh等locale之后,参数之间隐含约束才被激活。下面的表格列出了常见组合的表现:
| locale | strength | caseLevel | 大小写是否区分 | 1940风险 |
|---|---|---|---|---|
| 空 | 忽略 | 忽略 | 是(二进制) | 无 |
| zh | 3 | false | 依版本可能不区分 | 有 |
| zh | 3 | true | 是 | 无 |
正确的索引与查询配置实践
要避免1940,最稳妥的做法是:只要业务要求区分大小写,就在collation中显式写caseLevel: true,并将strength设为3或以上。同时,所有使用该索引的查询也必须携带相同的collation,否则MongoDB不会使用此索引,导致性能下降或逻辑错误。
以下是正确的建索引与查询示例。我们在创建唯一索引时明确大小写敏感,之后查询用户时也传入一致的collation,保证计划命中且不会报错。
const { MongoClient } = require('mongodb');
async function correct() {
const client = new MongoClient('mongodb://127.0.0.1:27017');
await client.connect();
const db = client.db('demo');
const col = db.collection('users');
// 正确写法:明确caseLevel为true
await col.createIndex(
{ name: 1 },
{
unique: true,
collation: { locale: 'zh', strength: 3, caseLevel: true }
}
);
// 插入不同大小写数据
await col.insertOne({ name: 'Abc' }, { collation: { locale: 'zh', strength: 3, caseLevel: true } });
await col.insertOne({ name: 'abc' }, { collation: { locale: 'zh', strength: 3, caseLevel: true } });
// 查询也要带相同collation
const r = await col.find(
{ name: 'Abc' },
{ collation: { locale: 'zh', strength: 3, caseLevel: true } }
).toArray();
console.log(r);
await client.close();
}
correct();
如果历史集合已经用错误参数建了索引,应先删除旧索引再按新参数重建。在生产环境,建议通过滚动方式操作,避免写阻塞。此外,在聚合管道中使用collation也要在每个阶段保持一致,否则中间结果排序错位也会间接引发类似的校验异常。把caseLevel当作显式契约而非可省略默认值,是规避1940的核心经验。