mongodb 查询优化方法:
基本方法:
1 尽可能添加索引,走索引查询,防止回表可以构建联合索引
2 索引不宜过多,一般不超过7个。看业务情况。写越多,索引越少。或垂直拆分表,或做数据备份。主写从查。
3 加缓存,能部分情况下防止尖刺产生。如果是冷数据,那么业务上一般是允许较慢的查询。
4 简单表亿级数据量,4c8g阿里云副本集。 单次查询100ms以内。
5 联合索引最左匹配(少用索引交集)
1 通过解释语言或者查看慢日志
2 关注以下字段:
keysExamined 索引扫的行数(越小越好)
keysExaminedBySizeInBytes 索引查询到的数据量(越小越好)
docsExamined 文档扫的行数(越小越好)
docsExaminedBySizeInBytes 文档查询到的数据量(越小越好)
numYield 让步次数(越小越好)
storage.timeReadingMicros 从磁盘中读取的耗时 (越小越好)
planSummary 执行的方案 (尽可能走索引)
nreturned 返回的行数 (看情况返回,一般分页为20个)
responseLength 返回的字节大小 (越小越好,可以只拿需要的数据)
以下为各种场景的举例:
1 COLLSCAN转为IXSCAN
如下:
单表83203行数据
全表扫描COLLSCAN,扫了全部文档,拉取文档83203多个。并且是从db的磁盘中拉取的数据且耗时5164微秒。由于耗时多,且有写语句,导致让步次数650次。总耗时117ms。
优化方案:给ts添加索引
{
"op": "query",
"ns": "***********",
"command": {
"find": "***********",
"filter": {},
"sort": {
"ts": -1
},
"limit": 20,
},
"keysExamined": 0,
"keysExaminedBySizeInBytes": 0,
"docsExamined": 83203,
"docsExaminedBySizeInBytes": 4100295,
"hasSortStage": true,
"numYield": 650,
"nreturned": 20,
"storage": {
"data": {
"bytesRead": {
"$numberLong": "4519673"
},
"timeReadingMicros": {
"$numberLong": "5164"
}
}
},
"responseLength": 1378,
"millis": 117,
"planSummary": "COLLSCAN"
}
2 大表(3.7亿数据),IXSCAN查询
如下:
单表379840512行数据
索引命中,已经没有优化空间。numYield让步次数较少,证明是写较少。每批次500个。
优化方案:
1 减少batchSize值。每批少拿,多拉几次。
2 数据横向切分(试情况而定)
{
"op": "getmore",
"command": {
"getMore": {
},
"batchSize": 500,
},
"originatingCommand": {
"find": "********",
"filter": {
"device.$id": {
"$oid": "5e3f999ba659686487339268"
}
},
"sort": {
"ts": -1
},
"batchSize": 500,
},
"keysExamined": 402,
"keysExaminedBySizeInBytes": 11658,
"docsExamined": 402,
"docsExaminedBySizeInBytes": 228458,
"cursorExhausted": true,
"numYield": 10,
"nreturned": 402,
"flowControl": {},
"storage": {
"data": {
"bytesRead": {
"$numberLong": "41694033"
},
"timeReadingMicros": {
"$numberLong": "97588"
}
}
},
"responseLength": 230584,
"millis": 113,
"planSummary": "IXSCAN { device.$id: -1, ts: -1 }",
"replRole": {
"stateStr": "PRIMARY",
"_id": 13
}
}
3 IXSCAN查询,但是索引没有走对
如下:
使用了成本最小的索引
解决方案:
1 不存在更好的索引的时候:精确创建索引
2 存在更好的索引的时候:
2.1 hint明确指明使用
2.2 可能索引区分度很低,这时候意义不大,分库或者改变数据模型,或者上缓存
{
"op": "query",
"command": {
"find": "**************",
"filter": {
"apply.$id": {
"$oid": "5714e03ad92cc517ea3e29de"
}
},
"limit": {
"$numberLong": "21"
},
"sort": {
"time.create": -1
},
},
"keysExamined": 1412,
"keysExaminedBySizeInBytes": 40948,
"docsExamined": 1412,
"docsExaminedBySizeInBytes": 1805370,
"hasSortStage": true,
"cursorExhausted": true,
"numYield": 15,
"nreturned": 21,
"storage": {
"data": {
"bytesRead": {
"$numberLong": "45511371"
},
"timeReadingMicros": {
"$numberLong": "149800"
}
}
},
"responseLength": 39381,
"protocol": "op_msg",
"millis": 162,
"planSummary": "IXSCAN { apply.$id: 1, time.update: -1 }",
"replRole": {
"stateStr": "PRIMARY",
"_id": 13
}
}
4 交集索引
如下:
存在deviceid的索引,subscriber.key的索引,但是mondo并不会使用,会使用其他的。
优化方案:
使用联合索引替代,核心业务一定要
{
"op": "query",
"ns": "*******",
"command": {
"find": "**********",
"filter": {
"deviceid": {
"$oid": "5d6b49474cfed7134d9c228e"
},
"subscriber.key": "5d86e1c062100f57d1c2cecf_8562"
},
},
"keysExamined": 1628,
"keysExaminedBySizeInBytes": 83028,
"docsExamined": 1628,
"docsExaminedBySizeInBytes": 363910,
"cursorExhausted": true,
"numYield": 25,
"nreturned": 2,
"responseLength": 679,
"millis": 137,
"planSummary": "IXSCAN { subscriber.key: -1, ts: -1 }",
}
5 超大表ixscan索引命中
如下:
主键查询,20亿数据
优化方案:
1 水平分库
2 使用缓存
{
"op": "query",
"ns": "*******",
"command": {
"find": "**********",
"filter": {
"_id": {
"$oid": "5d6b49474cfed7134d9c228e"
}
},
},
"keysExamined": 1,
"keysExaminedBySizeInBytes": 243,
"docsExamined": 1,
"docsExaminedBySizeInBytes": 243,
"cursorExhausted": true,
"numYield": 10,
"nreturned": 1,
"responseLength": 679,
"millis": 111,
"planSummary": "IXSCAN { _id: -1 }",
}
6 ixscan索引命中,但是写多
如下:
主键查询,2亿数据,写也多,导致numYield较多。
优化方案:
1 垂直分库
2 使用缓存
{
"op": "query",
"ns": "*******",
"command": {
"find": "**********",
"filter": {
"_id": {
"$oid": "5d6b49474cfed7134d9c228e"
}
},
},
"keysExamined": 1,
"keysExaminedBySizeInBytes": 269,
"docsExamined": 1,
"docsExaminedBySizeInBytes": 63,
"cursorExhausted": true,
"numYield":257,
"nreturned": 1,
"responseLength": 679,
"millis": 101,
"planSummary": "IXSCAN { _id: -1 }",
}