跳到主要内容

MongoDB 查询慢时,先别急着加机器

· 阅读需 3 分钟
一介布衣
全栈开发者

2014 年做内容站和后台系统时,MongoDB 很吸引人,原因很简单:模型灵活、起步轻、开发速度快。可一旦列表页慢下来,很多团队的第一反应都是“是不是该上副本集”“是不是该多加几台机器”。我后来发现,这种反应往往太快了。

副本集解决的是可用性、容灾和一部分读压力,不会自动把一条设计得很差的查询变快。真正决定接口体感的,通常还是索引、筛选条件和返回字段。

先分清到底慢在哪里

同样是“列表加载慢”,背后的原因可能完全不一样:

  • 写入被锁住,读请求跟着排队
  • 查询条件没有命中索引,直接全表扫
  • 结果集返回太大,网络和反序列化一起拖慢
  • 读从库时出现复制延迟,看到的是旧数据

这四种情况,处理方式完全不是一条路。所以在讨论扩容之前,我更愿意先把慢请求拆开看。

我当时的排查顺序

我通常按这个顺序来:

  1. 先看最常用的筛选条件是不是都有索引
  2. 再看排序字段是否和查询条件形成合理组合
  3. 然后检查接口是不是把不需要的大字段也一起捞出来了
  4. 最后才去看是否真的需要增加节点分担读流量

这个顺序看起来很保守,但很省钱。因为很多慢查询不是机器不够,而是根本没给数据库一条“能快起来的路”。

哪些索引最值得优先补

那几年我在后台项目里最常补的不是花哨索引,而是这些基础组合:

  • 按状态筛选再按时间倒序的列表索引
  • 用户维度下的最近记录索引
  • 需要唯一约束的业务键索引

反而有些“看上去什么都能查”的宽泛索引,最后只会增加写入成本,却不一定匹配真实查询路径。MongoDB 模型灵活,不代表索引也该放飞。

副本集什么时候才真正有价值

如果你已经确认:

  • 核心查询都命中合理索引
  • 文档大小控制得还可以
  • 返回字段已经做过裁剪
  • 主库确实顶不住读压力

这时再去谈副本集和读写分离,才是真正顺水推舟。否则只是把一条本来就不健康的查询复制到了更多机器上。

我的结论一直没怎么变

MongoDB 的灵活很好,但也容易让人误以为“慢了再补结构”。实际上,结构和索引越早想清楚,后面越稳。2014 年那一波项目里,真正把接口救回来的,通常不是先加服务器,而是先把查询路径画出来,再按路径补索引。

数据库扩容当然重要,但扩容应该建立在你已经知道系统为什么慢的前提上。否则花了钱,也不一定买到速度。