为什么两个数组字段的复合索引会报错?这篇用真实的错误逐个验证哪些情况允许、哪些拒绝,再对比两种绕开办法的查询代价。
test> db.createCollection("t"); db.t.createIndex({a: 1, b: 1})
a_1_b_1
test> db.t.insertOne({_id: 1, a: [1, 2, 3], b: "x"})
{ acknowledged: true, insertedId: 1 }
test> db.t.insertOne({_id: 2, a: 1, b: ["x", "y"]})
{ acknowledged: true, insertedId: 2 }复合索引里同一个文档最多只有一个被索引的字段是数组;一个数组加一个标量没问题。
test> try { db.t.insertOne({_id: 3, a: [1, 2], b: ["x", "y"]}) } catch (e) { ({code: e.code, name: e.codeName, message: e.message}) }
{
code: 171,
name: undefined,
message: 'cannot index parallel arrays [b] [a]'
}
test> db.t.updateOne({_id: 1}, {$set: {b: ["p", "q"]}}) // 把标量改成数组,让 a 和 b 同时是数组
{
code: 171,
message: 'Plan executor error during update :: caused by :: cannot index parallel arrays [b] [a]'
}
test> db.t.find().toArray()
[
{ _id: 1, a: [ 1, 2, 3 ], b: 'x' },
{ _id: 2, a: 1, b: [ 'x', 'y' ] }
]插入或更新让两个字段同时是数组,报 171 cannot index parallel arrays。索引不会展开成笛卡尔积,这样条目数会爆炸。
test> try { db.u.createIndex({a: 1, b: 1}) } catch (e) { ({code: e.code, name: e.codeName, message: e.message.slice(0, 150)}) }
{
code: 171,
name: 'CannotIndexParallelArrays',
message: 'Index build failed: 420fa910-bf98-4c68-8826-cd95ea44c52b: Collection test.u ( e3a2d792-34d6-4983-b6e4-ada0dc9c5920 ) :: caused by :: collection scan s'
}
test> db.u.getIndexes().map(i => i.name)
[ '_id_' ]集合里已经有这样的文档时,建索引直接失败,索引不会被创建。
test> db.createCollection("s"); db.s.createIndex({"x.a": 1, "x.b": 1})
x.a_1_x.b_1
test> db.s.insertOne({x: [{a:1,b:2},{a:3,b:4}]}) // 同一个数组 x 的两个子字段,只算一个数组
{ acknowledged: true, insertedId: 1 }
test> db.s.insertOne({x: [{a: [1,2], b: [3,4]}]}) // x.a 与 x.b 都是数组里的数组
cannot index parallel arrays [b] [a]同一个数组的两个子字段(x.a、x.b,x 是数组)只算一个数组;各自是数组才是并行数组。
test> db.v.find({a: 3, b: 2}) // a、b 各一个单字段 multikey 索引
{
plan: 'FETCH > IXSCAN(a_1)',
nReturned: 1714,
keysExamined: 6000,
docsExamined: 6000,
ms: 8
}
test> db.v.getIndexes().map(i => i.name)
[ '_id_', 'a_1', 'b_1' ]
test> db.v.createIndex({a: 1, b: 1}) // 两个都是数组的集合上建复合索引
MongoServerError: Index build failed: 653a4a12-6145-4a4d-833d-c9bcc2f7ac83: Collection test.v ( d76cba83-4382-44ea-947b-433e6070a430 ) :: caused by :: collection scan sto拆成两个单字段 multikey 索引,查询只能用其中一个,另一个条件在 FETCH 后过滤(检查 6000 个键)。
test> db.w.find({pairs: {$elemMatch: {a: 3, b: 2}}}) // 把 a、b 的组合展开成 pairs 子文档数组
{
plan: 'FETCH > IXSCAN(pairs.a_1_pairs.b_1)',
nReturned: 1714,
keysExamined: 1714,
docsExamined: 1714,
ms: 4
}
v 集合每个文档的 a、b 各 2 个元素;w 集合把它们展开成 2 × 2 = 4 个 {a, b} 子文档:
test> db.w.validate({full: true}).keysPerIndex
{ _id_: 30000, 'pairs.a_1_pairs.b_1': 120000 }把组合展开成子文档数组,用 $elemMatch 查询,界限精确(1714 个键),代价是索引条目是文档数的 4 倍。