You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NodeJS+Express调用MongoDB查询万级文档速度过慢的优化求助

优化方案

1. 实现分页查询

一次性返回1万+条数据会导致数据库查询耗时、内存占用飙升,且网络传输体积过大,分页是最直接的优化手段。优先推荐基于有序字段的游标分页(避免skip在大数据量下的性能损耗),或者基础分页方案。

基础分页示例:

export async function main(req, res) {
  try {
    const page = parseInt(req.query.page) || 1;
    const pageSize = parseInt(req.query.pageSize) || 100;
    
    const tokens = await tokenModel.find({})
      .lean()
      .limit(pageSize)
      .skip((page - 1) * pageSize);
    
    const total = await tokenModel.countDocuments({});
    
    res.json({
      data: tokens,
      pagination: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize)
      }
    });
  } catch(err) {
    res.status(500).json({ message: err.message });
    console.error('err', err.message);
  }
}

游标分页(大数据量场景更高效):

利用文档的有序字段(如_id或createdAt)替代skip,避免跳过大量文档带来的性能损耗:

import mongoose from 'mongoose';

export async function main(req, res) {
  try {
    const pageSize = parseInt(req.query.pageSize) || 100;
    const lastId = req.query.lastId;
    
    let query = {};
    if (lastId) {
      query._id = { $gt: mongoose.Types.ObjectId(lastId) };
    }
    
    const tokens = await tokenModel.find(query)
      .lean()
      .limit(pageSize + 1);
    
    const hasNext = tokens.length > pageSize;
    if (hasNext) tokens.pop();
    
    res.json({
      data: tokens,
      hasNext,
      lastId: tokens.length ? tokens[tokens.length - 1]._id : null
    });
  } catch(err) {
    res.status(500).json({ message: err.message });
    console.error('err', err.message);
  }
}

2. 字段投影(只返回必要字段)

默认find({})会返回文档所有字段,通过投影仅返回前端需要的字段,可大幅减少数据传输体积和数据库查询时间:

// 只返回name、value、createdAt三个字段,排除默认返回的_id
const tokens = await tokenModel.find({}, { name: 1, value: 1, createdAt: 1, _id: 0 })
  .lean()
  .limit(pageSize);

3. 启用响应压缩

通过Express的compression中间件对JSON响应做gzip压缩,可将传输体积压缩至原大小的1/3~1/5,对移动端弱网络环境提升显著:

npm install compression

在Express入口文件中配置:

const compression = require('compression');
// 务必放在路由注册前
app.use(compression());

4. 缓存查询结果

如果数据非实时更新,用Redis缓存查询结果,避免重复查询数据库:

import redis from 'redis';
const client = redis.createClient(); // 按实际Redis配置调整

export async function main(req, res) {
  try {
    const cacheKey = 'tokens_list';
    const cachedData = await client.get(cacheKey);
    
    if (cachedData) {
      res.json(JSON.parse(cachedData));
      return;
    }
    
    const tokens = await tokenModel.find({})
      .lean()
      .limit(100);
    
    // 缓存5分钟
    await client.setEx(cacheKey, 300, JSON.stringify(tokens));
    
    res.json(tokens);
  } catch(err) {
    res.status(500).json({ message: err.message });
    console.error('err', err.message);
  }
}

5. 流式传输数据

若必须返回大量数据,用MongoDB游标流分块发送,避免一次性加载所有数据到内存,同时让客户端逐步接收数据:

export async function main(req, res) {
  try {
    res.setHeader('Content-Type', 'application/json');
    res.write('[');
    
    let firstDoc = true;
    const cursor = tokenModel.find({}).lean().cursor();
    
    for await (const doc of cursor) {
      if (!firstDoc) res.write(',');
      firstDoc = false;
      res.write(JSON.stringify(doc));
    }
    
    res.write(']');
    res.end();
  } catch(err) {
    res.status(500).json({ message: err.message });
    console.error('err', err.message);
  }
}

6. 数据库层面优化

  • 给常用查询字段加索引:即使是全表查询,若有固定排序需求,给排序字段加索引可提升遍历效率;
  • 配置MongoDB内存:确保MongoDB有足够内存缓存常用数据,减少磁盘IO;
  • 升级MongoDB版本:新版本在查询计划、内存管理上有性能优化。

内容的提问来源于stack exchange,提问作者plat_winz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 03:25:18