VPS部署MongoDB后多客户端并发运行查询延迟过高的原因及解决方案
问题核心成因
- 观察到的性能断崖式下跌,根本不是3个客户端的请求量过高,是单个全表扫描的慢查询和bot的写操作产生了持续的锁竞争:
- 从
currentOp返回的123秒慢操作看,Web服务器端对nxmain.websites集合发起了无过滤条件的聚合统计,执行计划为COLLSCAN(全集合扫描),既没有索引支撑,也没有配置超时。这个操作在遍历全集合文档的过程中,会持续持有集合级读锁,哪怕中途多次让出CPU(累计934次yield),也不会完全释放读锁。 - bot端持续执行的
find_and_modify属于写操作,需要获取对应文档/集合的写锁,和读锁是互斥关系。全表扫描一直占着读锁不放,bot的写操作就只能排队等待拿不到锁,最终耗时从毫秒级拉长到十几秒。 - 当两个服务同时运行时,全表扫描每次让出锁的间隙,bot的写操作就会插入拿锁执行,反过来全表扫描又要等写操作释放锁才能继续扫,两边互相拉扯,导致原本单独运行时毫秒级的操作,凑到一起就被拉长到几十秒甚至几分钟。
- 本来应该毫秒级返回的
ismaster心跳检测操作都要等7秒,说明Mongo的连接处理队列已经被慢查询堵死,所有进来的请求都要在队列里排队,进一步拉长Web接口的响应时间。
- 从
- 这种“单独运行所有服务都正常,凑到一起就卡”的现象,本质是单独跑bot时没有持续的读锁占用,写操作拿锁快;单独跑Web时没有bot的高频写操作抢资源,慢查询虽然逻辑上要扫全表,但不会产生互斥等待,因此依然能保持毫秒级响应;一旦两个服务同时运行,锁互斥的恶性循环就被触发。
可落地的解决方向
- 第一时间停掉/优化Web端的全表聚合统计逻辑:
- 绝对不要每次访问接口都对大集合做空
$match的全表扫描。如果需要统计集合文档总数,改用预计算方案:单独建一个计数集合,每次bot执行find_and_modify更新文档状态时,同步更新对应分类的计数,Web端直接读这个预计算的计数,完全不需要扫表。 - 如果必须要实时聚合,给聚合的匹配条件建对应索引,同时给所有数据库请求加
maxTimeMS超时(比如设为2000毫秒),超过阈值直接终止操作返回错误,杜绝单条慢查询长时间堵库的情况。
- 绝对不要每次访问接口都对大集合做空
- 做读写操作隔离:如果你的Mongo部署了副本集,把Web服务器的读请求路由到 secondary 节点,不要让Web的读流量和bot的写流量都挤在主节点抢锁。主节点专门处理bot的高频读写操作,从节点承接Web的查询流量,从根本上消除锁竞争。
- 不需要优先升级VPS配置:哪怕把CPU和内存翻一倍,无索引的全表扫描该堵还是堵,先把慢查询和锁竞争的问题解决,再评估是否需要升级配置。
补充说明:
currentOp里那个跑了7秒的ismaster操作不是性能问题的根源,它只是被堵在连接队列里的受害者,等慢查询被优化后,这个操作会自动恢复毫秒级返回。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

