必须先建文本索引,否则 $text 查询直接报错;中文需前置分词处理;$regex 需转义防注入并限制长度;返回结果须投影 score 并按其排序,且必须用 explain 验证索引生效。

直接用 $regex 做关键词搜索,不建文本索引、不处理特殊字符、不控制返回字段,90% 以上的线上搜索接口会在高并发或含中文/标点词时出错或慢到超时。
必须先建文本索引,否则 $text 查询根本无法执行
MongoDB 的 $text 操作符强制依赖文本索引,没有它会直接报错 error: text index required。它不是可选优化项,是前提条件。
- 单字段索引:
db.articles.createIndex({ title: "text" }) - 多字段复合索引(推荐):
db.articles.createIndex({ title: "text", content: "text" })</li> <li>带权重(如标题比正文更重要):<code>db.articles.createIndex({ title: "text", content: "text" }, { weights: { title: 10, content: 2 } }) - 每个集合只能有一个文本索引;重复执行
createIndex不会报错,但也不会覆盖或更新已有索引
$text 查询要避开中文分词陷阱
MongoDB 默认的文本索引对中文支持极弱——它按空格和标点切词,不调用中文分词器。所以搜 "人工智能" 会失败,但搜 "人工 智能"(中间加空格)可能命中,这显然不可靠。
- 真实场景下,中文搜索应前置处理:用 Node.js 在查询前做简单分词(如用
nodejieba或正则粗切),再拼成$text支持的格式 - 示例(不依赖外部库):
const keywords = queryStr.replace(/[u4e00-u9fa5]/g, ' $& ').trim().split(/s+/).filter(Boolean); const searchValue = keywords.join(' '); const filter = { $text: { $search: searchValue } }; - 注意:MongoDB 会自动忽略停用词(如“的”“了”)、转小写、去重;但不会识别同义词或拼音,这点不能指望它
- 若必须精准匹配短语(如“北京天气”),
$text不适用,改用$regex+ 字段投影 +explain()看执行计划
用 $regex 时必须手动防御注入与性能崩塌
前端传来的任意字符串直接塞进 /${input}/i 是典型反模式:既可能被正则元字符(如 .*+)拖垮查询,也可能触发 ReDoS 攻击。
- 永远不要用字符串拼接构造正则:
new RegExp(input, 'i')是错的,new RegExp(escapeRegex(input), 'i')才对 - 提供
escapeRegex工具函数:function escapeRegex(string) { return string.replace(/[.*+?^${}()|[]\]/g, '\$&'); } - 限制最大查询长度(如 >50 字符截断),并在
find()后加.limit(50)防止全表扫 -
$regex无法使用普通索引加速,除非配合前缀匹配(如^abc)且字段上有前缀索引;否则默认走 collection scan
返回结果必须显式投影 + 分数排序,否则相关性无意义
启用文本搜索却不读取 score,等于买了秤却不用它称重——所有匹配文档默认按插入顺序返回,和“相关性搜索”完全背离。
- 获取并排序分数:
Article.find({ $text: { $search: "mongodb nodejs" } }, { score: { $meta: "textScore" } }).sort({ score: { $meta: "textScore" } }) - 禁止返回大字段(如
content全文):{ title: 1, excerpt: 1, score: { $meta: "textScore" }, _id: 0 } - 注意:Mongoose 中
$meta必须写在 projection 对象里,不能写在sort()的链式调用参数中,否则被忽略 - 如果用原生驱动,
cursor.project()和cursor.sort()要分开调,且sort必须传对象而非字符串
最常被跳过的一步是验证索引是否真正生效:用 db.articles.find({ $text: { $search: "test" } }).explain("executionStats") 看 indexUsed 是否为文本索引名,以及 nReturned 和 executionTimeMillis 是否合理——没这步,你写的全是纸面逻辑。


















