MongoDB Academy · 课时

正则查询与模式匹配

您将对字符串字段应用正则表达式,实现灵活的文本模式搜索。

第 4 / 4 课13 个步骤

正则查询与模式匹配 是 CoddyKit 上的免费 MongoDB Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 MongoDB Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 MongoDB Academy 课程共包含 4 节课。

需要进行模式匹配的场景

有时您需要查找字符串字段包含、以某个模式开头或以某个模式结尾的文档,但并不知道确切值。像 { name: 'Alice' } 这样的简单相等过滤器只能匹配完全相同的字符串。正则表达式(regex)可以让您在 MongoDB 中灵活地匹配字符串字段的模式。

MongoDB 支持通过 $regex 运算符使用正则表达式,也支持直接在查询过滤器中传入 JavaScript 正则表达式字面量。这两种方式都可以使用,只是在设置不区分大小写等选项时,语法略有不同。

MongoDB 中的基本正则表达式语法

在 MongoDB 查询中使用正则表达式有两种方式:

  • JS 正则表达式字面量:{ name: /alice/i } ——写法简洁,标志位放在结束斜杠之后
  • $regex 运算符:{ name: { $regex: 'alice', $options: 'i' } } ——写法更详细,但与其他运算符组合时必须使用这种形式

两种形式产生的结果完全相同。简单查询使用 JS 字面量语法;如果需要从字符串变量动态构建模式,或将其与同一字段上的其他运算符组合,请使用 $regex 运算符形式。

// JS regex literal - concise
db.users.find({ name: /alice/i });
// 'i' flag = case-insensitive

// $regex operator - equivalent
db.users.find({ name: { $regex: 'alice', $options: 'i' } });

// Dynamic pattern from variable:
const searchTerm = req.query.name;
const escapedTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape special chars
db.users.find({ name: { $regex: escapedTerm, $options: 'i' } });

正则表达式选项:i、m、s、x

MongoDB 支持四个正则表达式选项标志:

  • i ——不区分大小写:/alice/i 可匹配 “Alice”、“ALICE” 和 “alice”
  • m ——多行模式:^ 和 $ 匹配每一行的开头和结尾,而不是整个字符串的开头和结尾
  • s ——点号匹配全部:. 可匹配包括换行符在内的任意字符
  • x ——扩展模式:忽略模式中的空白字符(便于编写带注释的易读模式)

最常用的标志是 i。m 标志适合搜索博客内容等多行文本字段。多个标志可以组合使用:{ $options: 'im' }。

// Case-insensitive search for 'javascript'
db.courses.find({ title: { $regex: 'javascript', $options: 'i' } });
// Matches: 'JavaScript', 'JAVASCRIPT', 'javascript', 'JavaScript Tutorial'

// Multiline match: ^ anchors to start of each line
db.articles.find({ content: { $regex: '^Introduction', $options: 'm' } });
// Matches articles where any line starts with 'Introduction'

// Combined flags
db.posts.find({ body: { $regex: 'mongodb', $options: 'si' } });
// Case-insensitive + dot matches newlines in body

定位符:匹配开头和结尾

使用正则表达式定位符匹配字符串字段的开头或结尾:

  • ^ 定位到开头:/^Admin/ 可匹配以 “Admin” 开头的字符串
  • $ 定位到结尾:/\.pdf$/ 可匹配以 “.pdf” 结尾的字符串

有一个非常重要的性能事实:带插入符号的前缀正则表达式(/^prefix/)可以使用该字段上的常规 B-tree 索引——MongoDB 能够在索引中直接定位到该前缀。因此,前缀搜索比字符串中间位置的搜索快得多。在大型集合上,如果没有文本索引,切勿使用前导通配模式(/.*pattern/ 或不带 ^ 的 /pattern/)。

// Prefix match - CAN use index (very efficient)
db.users.find({ username: /^admin/i });
// Matches: 'admin', 'administrator', 'Admin123'
// Uses the index on username (if it exists)

// Suffix match - CANNOT use regular index (slower)
db.files.find({ filename: /\.pdf$/i });
// Matches: 'report.pdf', 'invoice.PDF'
// Must scan all documents - add text index if used frequently

// Mid-string - CANNOT use index
db.products.find({ description: /wireless/i });
// Must scan all documents - use $text index for this

字符类和量词

正则表达式提供了功能丰富的模式构造:

  • . ——任意字符(除非设置了 s 标志,否则不包括换行符)
  • [abc] ——字符类:匹配 a、b 或 c
  • [a-z] ——范围:任意小写字母
  • \d ——任意数字(等同于 [0-9])
  • \w ——任意单词字符([a-zA-Z0-9_])
  • *、+、? ——零次或多次、一次或多次、零次或一次
  • {n}、{n,m} ——恰好 n 次、出现 n 到 m 次
// Phone number pattern: +1-XXX-XXX-XXXX or similar
db.users.find({ phone: /^\+?\d{1,3}[-. ]?\(?\d{3}\)?[-. ]?\d{3}[-. ]?\d{4}$/ });

// 6-character alphanumeric codes
db.coupons.find({ code: /^[A-Z0-9]{6}$/ });
// Matches: 'SALE99', 'DISC25', 'FREE01'

// Email basic validation
db.users.find({ email: { $regex: '^[\\w.]+@[\\w.]+\\.[a-z]{2,}$', $options: 'i' } });

正则表达式与文本索引:了解差异

正则表达式查询和 MongoDB 的 $text 运算符用途不同,性能特征也有很大差异:

  • 正则表达式:按字符模式匹配。可用于任何字符串字段,无需特殊索引(不过带前缀定位符的查询可以使用 B-tree 索引)。适合格式验证、前缀搜索,或在小型集合中进行模式匹配,以及执行带索引前缀的查询。
  • $text / 文本索引:通过分词、词干提取和停用词过滤进行全文关键词搜索。需要文本索引。在大型文本字段(文章、描述、评论)中进行关键词搜索时,速度快得多。

切勿在大型且未建立索引的集合上使用 /.*keyword.*/i——这会扫描整个集合,可能非常缓慢。

// Use REGEX for: format matching, prefix searches on indexed field
db.orders.find({ orderNumber: /^ORD-2024/ }); // Prefix - can use index

// Use $text for: keyword search in content fields
// First: create a text index
db.articles.createIndex({ content: 'text', title: 'text' });
// Then: full-text search
db.articles.find({ $text: { $search: 'mongodb performance' } });
// Much faster than /mongodb.*performance/i on large collections

ReDoS:安全风险

ReDoS(正则表达式拒绝服务)是一种攻击:恶意用户构造输入字符串,使编写不当的正则表达式需要指数级时间进行求值。如果您未进行转义就将用户输入直接传入正则表达式,攻击者可能会让数据库查询长时间无法完成。

在正则表达式中使用用户输入之前,务必对用户输入进行转义,将所有特殊正则表达式字符(. * + ? ^ $ { } ( ) [ ] | \ )替换为对应的转义形式。许多库(例如 Node.js 中的 escape-string-regexp)会自动完成这项工作。此外,对于面向用户的搜索输入,优先使用 $text,而不是原始正则表达式。

// DANGEROUS: raw user input in regex (ReDoS risk)
const input = req.query.search; // User controls this
db.products.find({ name: { $regex: input } }); // NEVER DO THIS

// SAFE: escape user input before using in regex
function escapeRegex(str) {
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
const safeInput = escapeRegex(req.query.search);
db.products.find({ name: { $regex: safeInput, $options: 'i' } });

// BETTER: Use $text for user-facing search
db.products.find({ $text: { $search: req.query.search } });

数组字段上的正则表达式

将正则表达式应用于数组字段时,MongoDB 会检查数组中是否有任意元素匹配该模式,这与数组相等查询的包含语义相同。因此,无需特殊运算符,就可以搜索标签、类别或其他字符串数组。

例如,包含 tags: ['node.js', 'mongodb', 'backend'] 的文档会被 { tags: /^mongo/ } 返回,因为 “mongodb” 以 “mongo” 开头。正则表达式会分别应用于每个元素。

// Document: { tags: ['node.js', 'mongodb', 'backend-dev'] }

// Find products where any tag starts with 'mongo'
db.products.find({ tags: /^mongo/i });
// Returns the document (tags contains 'mongodb')

// Find articles where any category contains 'tech'
db.articles.find({ categories: /tech/i });
// Matches: categories containing 'technology', 'tech-news', 'fintech'

// Index on array field improves regex prefix searches
db.articles.createIndex({ categories: 1 });

实际搜索示例

典型的电子商务搜索功能会将正则表达式与其他过滤器结合起来,提供符合上下文且不区分大小写的产品搜索。该查询会匹配名称中任意位置包含搜索词的产品,并按类别和库存状态进行过滤。

对于小型目录(少于 50,000 个产品),使用正则表达式搜索是可以接受的。对于更大的目录或完整描述搜索,Atlas Search(基于 Lucene)能够提供更优秀的相关性排序、词干提取和性能。

// Search endpoint: GET /products?q=wireless&category=Electronics
app.get('/products', async (req, res) => {
  const filter = { stock: { $gt: 0 } };

  if (req.query.q) {
    const escaped = req.query.q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    filter.name = { $regex: escaped, $options: 'i' };
  }
  if (req.query.category) {
    filter.category = req.query.category;
  }

  const products = await db.collection('products')
    .find(filter)
    .project({ name: 1, price: 1, _id: 1 })
    .limit(50)
    .toArray();

  res.json(products);
});

正则表达式性能总结

MongoDB 中正则表达式性能的关键规则:

  • 以 ^ 开头(/^prefix/):可以使用 B-tree 索引。正则表达式中性能最佳。
  • 区分大小写的前缀(/^prefix/):可以充分利用索引顺序。
  • 不区分大小写的前缀(/^prefix/i):可以部分使用索引,但仍比没有定位符快得多。
  • 不带定位符的包含匹配(/pattern/):扫描整个集合——应改用文本索引。
  • 后缀(/pattern$/):扫描整个集合或使用文本索引。

如果不确定,请使用 explain('executionStats') 进行测试,并查看是 IXSCAN 还是 COLLSCAN。

// Check if regex query uses an index
db.users.find({ username: /^alice/i })
  .explain('executionStats');
// winningPlan.stage should be 'IXSCAN' if username is indexed

// Compare index scan vs collection scan:
// /^alice/ on indexed field: totalDocsExamined ~= nReturned (efficient)
// /alice/  on any field: totalDocsExamined = all documents (slow!)

部署前测试正则表达式模式

在生产集合上运行正则表达式查询之前,请针对示例文档测试该模式,确认它能匹配您希望匹配的内容,同时不会匹配您不希望匹配的内容。MongoDB shell 允许您使用较小的 limit() 快速测试;JavaScript 的 String.prototype.test() 或正则表达式字面量则可以让您在 Node.js 中验证模式,然后再将其嵌入查询。

还应使用 explain() 检查性能:需要扫描整个集合的正则表达式应改为文本查询,或者应结合集合大小进行评估。对于新集合,请从设计之初就考虑搜索需求——一开始就添加文本索引,而不是事后再补充。

// Step 1: Test regex in Node.js before using in query
const pattern = /^wireless/i;
const testStrings = ['Wireless Headphones', 'wireless mouse', 'Wired Keyboard'];
testStrings.forEach(s => console.log(s, '=>', pattern.test(s)));
// 'Wireless Headphones' => true
// 'wireless mouse' => true
// 'Wired Keyboard' => false

// Step 2: Test on production with limit
db.products.find({ name: /^wireless/i }).limit(5);

// Step 3: Check performance
db.products.find({ name: /^wireless/i }).explain('executionStats');

快速检查

测试您对本课 MongoDB 和 NoSQL 数据库相关概念的理解。

课程回顾

本课中您学到了:正则表达式查询使用 /pattern/flags 或 $regex 运算符进行灵活的字符串模式匹配——最重要的标志是用于不区分大小写匹配的 i;带前缀定位符的正则表达式(/^prefix/)可以使用 B-tree 索引来高效查找,而字符串中间位置的模式则需要完整扫描;在将用户输入插入正则表达式之前,务必进行转义,以防止 ReDoS 攻击——更好的做法是将 $text 运算符与文本索引结合起来,用于面向用户的搜索。接下来我们将学习使用 $set、$unset 和其他更新运算符更新文档。

免费开始

用 AI 导师学习 JavaScript — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
30
课程
120

常见问题解答

「正则查询与模式匹配」课时是免费的吗?

是的 — 「正则查询与模式匹配」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 MongoDB Academy 课程的其余内容,请升级到 CoddyKit PRO。 MongoDB Academy 课程共包含 4 节课。

「正则查询与模式匹配」这节课中我会学到什么?

您将对字符串字段应用正则表达式,实现灵活的文本模式搜索。 你通过在浏览器中直接运行的动手代码来练习 MongoDB Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 MongoDB Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 MongoDB Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「正则查询与模式匹配」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 MongoDB Academy 课中编写并运行代码吗?

能。每节 MongoDB Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 比较运算符:$eq、$gt、$lt、$in
  2. 逻辑运算符:$and、$or、$nor、$not
  3. 元素运算符与类型检查
  4. 正则查询与模式匹配
← 返回 MongoDB Academy