← MongoDB 插入文档 MongoDB 排序查询 →

Node.js MongoDB条件过滤查询:find()方法实战与正则表达式进阶

著
原创 2026-10-14 Node.js 已有人查阅

查询对象:MongoDB过滤的第一入口

在MongoDB中,find()并不是一个只能"查全部"的方法。它接受一个查询文档(querydocument)作为第一个参数,这个文档的结构与中存储的文档结构一致,只是字段值表达的是"匹配条件"而非"目标值"。理解这一点,是写好所有过滤查询的基础。

以员工employees为例,假设文档结构大致如下:

{
  _id: ObjectId("..."),
  name: "张伟",
  address: "Delhi",
  department: "研发",
  joinDate: ISODate("2025-04-12")
}

如果只关心地址为Delhi的员工,查询条件写成{address:"Delhi"}即可。这里的address是字段名,"Delhi"是等值匹配条件。驱动会把这个对象翻译成MongoDB的BSON查询指令,服务端据此过滤。

一个容易忽略的细节是:查询对象的键名区分大小写,值也区分大小写。"Delhi"与"delhi"在默认的二进制比较下不会互相匹配。如果业务中地址录入不规范,等值查询会漏掉数据,这种问题在项目里往往比语法错误更难排查。

一个可运行的过滤示例

下面这段代码演示了按地址精确过滤的完整流程。文件名用code01_query_filter.js这种带编号的命名方式,便于在教程或项目中按顺序管理示例。

const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017/MongoDatabase';

MongoClient.connect(url, function (err, db) {
  if (err) throw err;

  const query = { address: 'Delhi' };

  db.collection('employees')
    .find(query)
    .toArray(function (err, result) {
      if (err) throw err;
      console.log(result);
      db.close();
    });
});

运行:

node code01_query_filter.js

这段代码里,find(query)返回的是一个游标(Cursor),toArray()把游标中的文档一次性读入数组。控制台会打印出所有address为Delhi的员工文档。

关于连接与回调的取舍

上面的写法沿用了较早版本驱动的回调风格,MongoClient.connect成功后的db对象直接用于获取。这里有一个实际项目中值得注意的点:db.close()放在toArray回调内部,确保读取完成后才断开连接。如果放在find之后立即调用,游标可能尚未消费完,连接就被关闭,导致读取失败。这个顺序问题在并发场景下更容易暴露。

现在版本的Node.js驱动(4.x及以上)已经推荐使用Promise或async/await,并且connect返回的是MongoClient实例,获取方式变为client.db('MongoDatabase').collection('employees')。旧教程中的db.collection写法在新驱动里需要调整。如果你的项目还在用旧写法,升级驱动时要留意这一变化。

用async/await重写后大致是这样:

const { MongoClient } = require('mongodb');
const url = 'mongodb://localhost:27017';
const client = new MongoClient(url);

async function findEmployees() {
  try {
    await client.connect();
    const db = client.db('MongoDatabase');
    const result = await db.collection('employees')
      .find({ address: 'Delhi' })
      .toArray();
    console.log(result);
  } finally {
    await client.close();
  }
}

findEmployees();

连接只建立一次、在finally中统一释放,这种做法比每次查询都connect/close更适合服务端常驻进程。回调嵌套层级也随之减少,错误传递更清晰。

正则表达式:字符串字段的灵活匹配

等值匹配只能命中一致的字符串。当需求变成"地址以L开头"、"姓名包含某个字"这类模式匹配时,就需要正则表达式。MongoDB支持在查询对象中直接写入JavaScript正则字面量,驱动会将其作为BSON正则类型传给服务端。

匹配地址以字母L开头的员工:

const query = { address: /^L/ };

^表示字符串起始位置。这个查询会命中London、Lucknow、Lagos等所有以L开头的地址。相比$where或客户端过滤,正则查询在服务端执行,只把匹配结果传回,网络开销小得多。

完整示例文件命名为code02_query_regex.js:

const MongoClient = require('mongodb').MongoClient;
const url = 'mongodb://localhost:27017/MongoDatabase';

MongoClient.connect(url, function (err, db) {
  if (err) throw err;

  const query = { address: /^L/ };

  db.collection('employees')
    .find(query)
    .toArray(function (err, result) {
      if (err) throw err;
      console.log(result);
      db.close();
    });
});

运行:

node code02_query_regex.js

正则的使用边界与性能考量

正则只能作用于字符串类型的字段。如果对数值字段或日期字段使用正则,MongoDB不会报错,但也不会匹配到任何文档——这种"静默失败"在排查时容易让人困惑。

另一个关键点是索引。普通等值查询可以走B树索引,而正则查询只有在模式以^开头且不区分大小写时,才有可能利用索引做前缀扫描。像/Delhi/这种非锚定正则,会退化为全扫描。数据量达到几十万条以上时,这种查询的延迟会明显上升。

在项目里,我通常会把频繁用到的前缀查询单独抽出来,并在对应字段上建立索引。例如地址前缀查询多,就为address建索引;如果业务允许,把地址规范化成枚举值再做等值匹配,往往比正则更稳定。正则更适合后台检索、运营筛选这类对延迟不敏感的场景。

查询条件的组合方式

实际业务很少只有一个条件。MongoDB的查询对象支持在同一层级写多个字段,它们之间是逻辑与的关系:

const query = {
  address: /^L/,
  department: '研发'
};

这表示"地址以L开头,并且部门是研发"。如果需要逻辑或,用$or:

const query = {
  $or: [
    { address: /^L/ },
    { address: /^D/ }
  ]
};

$or的每个元素都是一个独立查询对象,理解这一点后,复杂条件可以层层嵌套。$or中的每个分支如果都能走索引,整体效率才理想;否则可能出现一个分支全表扫描拖慢整个查询的情况。

现在框架中的惯用写法

在Express、Koa、NestJS这类框架里,查询条件通常来自请求参数,需要先做校验再拼装查询对象。直接把req.query透传给find()是危险的,因为客户端可能传入$where、$ne等操作符,造成注入风险。

比较稳妥的做法是显式提取字段并做类型检查:

function buildEmployeeQuery(params) {
  const query = {};
  if (typeof params.address === 'string' && params.address.trim()) {
    query.address = new RegExp('^' + escapeRegExp(params.address.trim()));
  }
  if (typeof params.department === 'string' && params.department.trim()) {
    query.department = params.department.trim();
  }
  return query;
}

function escapeRegExp(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

escapeRegExp这一步很有必要。用户输入的字符串可能包含正则元字符,如果不转义,轻则查询结果异常,重则构造出消耗大量CPU的复杂正则。这是很多教程示例里不会提到、但线上项目必须处理的问题。

本节课程知识要点

  • find()的第一个参数是查询对象,字段名与文档结构对应,值表达匹配条件。

  • 等值查询区分大小写,数据录入不规范时需要额外处理。

  • 正则只能用于字符串字段,锚定^开头的正则有利用索引的可能,非锚定正则会全扫描。

  • 旧版回调写法中db.close()的位置影响读取结果,现在驱动推荐async/await与连接复用。

  • 组合条件默认是逻辑与,逻辑或用$or,每个分支的索引情况决定整体性能。

  • 来自外部的查询参数需要显式校验和转义,避免操作符注入与正则拒绝服务。

练习与思考

  1. 把code01_query_filter.js改写为async/await版本,并加入try/finally确保连接释放。

  2. 写一个查询,检索地址以L或D开头、且部门为研发的员工。

  3. 思考:如果address字段上已有索引,查询条件写成/^L/i(忽略大小写)时,索引还能被有效利用吗?为什么?

  4. 在escapeRegExp的基础上,给buildEmployeeQuery增加一个按姓名模糊匹配的参数,并说明为什么推荐前缀匹配而非任意位置匹配。

find()的过滤能力看着简单,真正拉开差距的是对查询对象语义、索引行为和输入安全的把握。把等值查询和正则匹配用对场景,再配合连接复用与参数校验,这套数据访问层在中小规模项目里已经足够稳。后续如果要处理聚合、分页或事务,也可以在这个基础上平滑扩展。

← MongoDB 插入文档 MongoDB 排序查询 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号