震撼揭秘:线上MongoDB慢查询终极优化实战解析
背景
研发反馈指出,线上某个页面的响应速度异常缓慢,达到了16秒,严重影响了业务的正常运行。经过与研发的沟通得知,该页面调用的数据集合只会保留7天的数据,集合有6000万条记录。针对过期数据的处理,使用了根据 create_time 字段创建的过期索引,以自动使数据失效。此外,数据集合还通过 company_id 字段进行了哈希分片。
问题排查
慢语句分析
在后台拿到了慢查询语句,如下:
db.visitor.find({
"company_id": 13272,
"create_time": {
"$gte": ISODate("2024-04-11T00:00:00.000+0800"),
"$lte": ISODate("2024-04-11T23:59:59.000+0800")
}
});
db.visitor.find({
"company_id": 13272,
"create_time": {
"$gte": ISODate("2024-04-12T00:00:00.000+0800"),
"$lte": ISODate("2024-04-18T23:59:59.000+0800")
}
});
很简单的一个查询,语句上没有再优化的必要了,如果索引都在不应该出现这种十多秒的耗时,接下来开始分析索引。
索引分析
索引如下:
db.getCollection("visitor").createIndex({
"company_id": "hashed"
}, {
name: "company_id_hashed"
});
db.getCollection("visitor").createIndex({
"company_id": NumberInt("1")
}, {
name: "company_id_1"
});
db.getCollection("visitor").createIndex({
"create_time": NumberInt("1")
}, {
name: "create_time_1",
expireAfterSeconds: NumberInt("604800")
});
- company_id_hashed:创建集合分片使用的hash索引
- company_id_1:普通查询的索引
- create_time_1:过期时间的索引
根据研发团队的反馈和对数据的分析,我们发现当前集合使用 company_id_hashed 索引进行分片存在问题。哈希索引对等值查询最为友好,但对于范围查询支持不佳。由于 company_id 是公司维度字段,相同数据较多,因此使用哈希分片并不合适。建议直接创建 company_id 和 create_time 的联合范围分片键。这样不仅能够友好地支持范围查询,还能更细粒度地拆分数据,提高查询和写入的效率。
针对当前情况就这点数据量,按理说会用到索引的,不应该执行耗时16s,接下来执行计划分析。
Explain执行计划
winningPlan
"inputStage": {
"stage": "FETCH",
"filter": {
"$and": [
{
"company_id": {
"$eq": 13272
}
},
{
"create_time": {
"$lte": ISODate("2024-04-17T15:59:59.000Z")
}
},
{
"create_time": {
"$gte": ISODate("2024-04-10T16:00:00.000Z")
}
}
]
},
"inputStage": {
"stage": "IXSCAN",
"keyPattern": {
"company_id": "hashed"
},
"indexName": "company_id_hashed",
"isMultiKey": false,
"isUnique": false,
"isSparse": false,
"isPartial": false,
"indexVersion": NumberInt("2"),
"direction": "forward",
"indexBounds": {
"company_id": [
"[7977521071453068053, 7977521071453068053]"
这部分显示只用到了company_id_hashed索引,没有用到create_time_1索引。