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

Mongoose查询MongoDB时分页并前置修改数据的优化方案

Mongoose查询结果优化:分页场景下的字段修改(如日期格式化)

问题背景

使用Mongoose查询MongoDB数据时,需保留现有分页逻辑,同时对返回数据做修改(比如日期格式化)。当前通过map遍历结果实现,但担心大数据量下的性能问题,寻求更优方案。

现有查询代码:

const count = await UserModel.countDocuments();
const rows = await UserModel.find({ name:{$regex: search, $options: 'i'}, status:10 })
      .sort([["updated_at", -1]])
      .skip(page * perPage)
      .limit(perPage)
      .exec();

res.json({ count, rows });

当前修改逻辑:

res.json({ count, rows: rows.map(el => ({...el, created_at:'format date here'})) });

更优方案

1. 利用Mongoose Schema虚拟字段(Virtuals)

在Schema中定义虚拟字段,自动处理日期格式化,查询时无需手动遍历,全局生效。

步骤:
修改你的UserModel Schema,添加虚拟字段并配置toJSON选项:

const userSchema = new mongoose.Schema({
  // 原有字段定义
  created_at: Date,
  updated_at: Date,
  name: String,
  status: Number
}, {
  // 配置toJSON,让虚拟字段在JSON序列化时被包含
  toJSON: { virtuals: true }
});

// 定义格式化后的日期虚拟字段
userSchema.virtual('formatted_created_at').get(function() {
  // 这里替换成你的日期格式化逻辑,比如用dayjs或原生Date方法
  return this.created_at.toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit'
  });
});

const UserModel = mongoose.model('User', userSchema);

查询时直接返回:

const count = await UserModel.countDocuments();
const rows = await UserModel.find({ name:{$regex: search, $options: 'i'}, status:10 })
      .sort([["updated_at", -1]])
      .skip(page * perPage)
      .limit(perPage)
      .exec();

// 返回的rows会自动包含formatted_created_at字段
res.json({ count, rows });

优势: 全局统一处理,无需每次查询后手动遍历,代码更简洁;虚拟字段不存储在数据库,不占用存储空间。


2. 使用聚合管道(Aggregation Pipeline)

将过滤、排序、分页、字段修改(日期格式化)全部放在数据库层面完成,减少客户端内存消耗,尤其适合大数据量场景。

代码示例:

const aggregationResult = await UserModel.aggregate([
  // 过滤条件
  { $match: { name: { $regex: search, $options: 'i' }, status: 10 } },
  // 排序
  { $sort: { updated_at: -1 } },
  // 分页:先skip再limit
  { $skip: page * perPage },
  { $limit: perPage },
  // 格式化日期,同时保留原有字段
  {
    $addFields: {
      formatted_created_at: {
        $dateToString: {
          format: "%Y-%m-%d %H:%M:%S",
          date: "$created_at",
          timezone: "Asia/Shanghai" // 按需设置时区
        }
      }
    }
  }
]);

// 单独统计总数
const count = await UserModel.countDocuments({ name:{$regex: search, $options: 'i'}, status:10 });

res.json({ count, rows: aggregationResult });

进阶:用$facet同时获取数据和总数
如果想减少一次数据库请求,可以用$facet:

const [result] = await UserModel.aggregate([
  { $match: { name: { $regex: search, $options: 'i' }, status: 10 } },
  { $sort: { updated_at: -1 } },
  {
    $facet: {
      rows: [
        { $skip: page * perPage },
        { $limit: perPage },
        {
          $addFields: {
            formatted_created_at: {
              $dateToString: {
                format: "%Y-%m-%d %H:%M:%S",
                date: "$created_at",
                timezone: "Asia/Shanghai"
              }
            }
          }
        }
      ],
      count: [
        { $count: "total" }
      ]
    }
  }
]);

const count = result.count[0]?.total || 0;
res.json({ count, rows: result.rows });

优势: 所有处理逻辑在数据库端完成,客户端无需遍历处理,减少内存占用;适合复杂的数据转换场景。


3. 为字段添加自定义Getter

在Schema的字段上直接定义getter函数,自动格式化字段值,查询时直接返回处理后的数据。

代码示例:

const userSchema = new mongoose.Schema({
  created_at: {
    type: Date,
    // 定义getter函数
    get: function(value) {
      return value.toLocaleString('zh-CN', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
        hour: '2-digit',
        minute: '2-digit'
      });
    }
  },
  updated_at: Date,
  name: String,
  status: Number
}, {
  // 确保getter在JSON序列化时生效
  toJSON: { getters: true }
});

const UserModel = mongoose.model('User', userSchema);

查询时直接返回:

const count = await UserModel.countDocuments();
const rows = await UserModel.find({ name:{$regex: search, $options: 'i'}, status:10 })
      .sort([["updated_at", -1]])
      .skip(page * perPage)
      .limit(perPage)
      .exec();

// 返回的created_at已经是格式化后的字符串
res.json({ count, rows });

优势: 针对单个字段处理,逻辑简单直接;无需额外修改查询代码。


方案选择建议

  • 如果需要全局统一处理某个字段的格式化,优先选虚拟字段或自定义Getter;
  • 如果涉及复杂的数据转换、多字段处理,或者追求极致性能(大数据量),优先选聚合管道;
  • 原有的map遍历方案在小数据量下足够简单,但大数据量下会增加客户端内存开销,建议根据场景替换。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 12:55:15