数组字段上的索引会生成多少条索引条目?它占多大?$elemMatch 为什么更快?这篇用 validate 数条目、用 explain 看检查的键数,并量一下写入代价。
t = ['x','y','z'] 索引条目 3
t = ['x'] 索引条目 1
t = [] 索引条目 1
t = 字段缺失 索引条目 1
t = 标量 'x' 索引条目 1
t = ['x','x','x'] 索引条目 1
t = [['x','y'],'z'] 索引条目 2
t = [null, 'x'] 索引条目 2
把这 8 个文档放进同一个集合建索引 t_1,条目数 = 12(上面逐个相加 = 12)
test> db.a.find({t: "x"}).explain("executionStats")
{
isMultiKey: true,
multiKeyPaths: { t: [ 't' ] },
keysExamined: 5,
nReturned: 5
}数组字段的索引按元素生成条目,同一个文档里重复的元素只算一条;空数组、字段缺失、标量各一条。explain 里 isMultiKey 为 true。
每个数组元素数 索引条目 索引大小(字节) 每个文档平均 1 10,000 114,688 11 B 5 50,000 499,712 50 B 20 200,000 1,908,736 191 B 100 1,000,000 8,835,072 884 B
索引条目数和大小随数组长度线性增长:每个数组 100 个元素的 10,000 个文档,索引 8.8 MB,是 1 个元素时的 77 倍。
[a,a,a,b] 一个文档,索引条目数 = 2(元素 4 个,不同值 2 个)
数组里重复的值在索引里只存一条,[a,a,a,b] 只有 2 条。
test> db.p.find({tags: "a3"})
{
plan: 'FETCH > IXSCAN(tags_1)',
nReturned: 5000,
keysExamined: 5000,
docsExamined: 5000,
ms: 4
}
test> db.p.find({tags: {$all: ["a3", "b2"]}})
{
plan: 'FETCH > IXSCAN(tags_1)',
nReturned: 714,
keysExamined: 5000,
docsExamined: 5000,
ms: 3
}
test> db.p.find({tags: ["a3", "b2", "c0"]}) // 整个数组相等
{
plan: 'FETCH > IXSCAN(tags_1)',
nReturned: 238,
keysExamined: 5001,
docsExamined: 5000,
ms: 3
}$all 与整个数组相等的查询只用其中一个元素定界,其余条件在 FETCH 后过滤,所以检查的键数和文档数与单值查询一样多。
test> db.p.countDocuments({"items.k": "a3", "items.v": 1}) // 可以由不同元素各满足一个
2143
test> db.p.countDocuments({items: {$elemMatch: {k: "a3", v: 1}}}) // 同一个元素同时满足
1000
test> db.p.find({"items.k": "a3", "items.v": 1}) 的计划
{
plan: 'FETCH > IXSCAN(items.k_1_items.v_1)',
nReturned: 2143,
keysExamined: 5000,
docsExamined: 5000,
ms: 4
}
test> db.p.find({items: {$elemMatch: {k: "a3", v: 1}}}) 的计划
{
plan: 'FETCH > IXSCAN(items.k_1_items.v_1)',
nReturned: 1000,
keysExamined: 1000,
docsExamined: 1000,
ms: 1
}普通条件可以由数组里不同的元素各满足一个;$elemMatch 要求同一个元素满足,索引界限更紧,检查的键和文档数只有前者的 1/5。
每个文档 1 个元素:插入 10,000 个文档(带 t 上的索引)耗时 24 ms 每个文档 100 个元素:插入 10,000 个文档(带 t 上的索引)耗时 821 ms
每个元素都要在索引里插一条,批量插入每个文档 100 个元素比 1 个元素慢了约 34 倍。