需求
支持文章、用户的搜索能力,按相关度或时间排序。
调研
支持搜索,其实最好是支持全文索引,并且按照相关度、时间等加权排序,但这样实现起来就是一个真正的搜索引擎了。成本很难度都比较高。基于我们现有的Mongo , 最开始,也是最简单的方法是使用正则进行模糊搜索,可以基于query 分词做进一步优化。排序能支持时间排序,但无法支持相关度排序。最近发现,Mongo 其实支持了$text 全文检索能力,并且支持相关度排序,但遗憾的是不支持中文。另外不能指定特定字段进行全文检索,只能对整个doc 的全部 text 索引字段进行检索。
但还是希望尽可能的只使用一个数据库完成搜索功能。
实现
在MongoDB中,进行模糊搜索时,默认情况下,搜索结果是按照文档的存储顺序返回的,而不是按照相关度排序的。这意味着搜索结果的排序可能不符合你的期望,尤其是当你希望按照搜索关键词与文档的匹配程度进行排序时。
要按相关度排序,你可以使用MongoDB的文本搜索功能(Text Search),通过创建文本索引并使用 $text
操作符进行搜索。文本搜索功能允许你对文档中的文本字段进行全文搜索,并且提供了相关度排序的功能。你可以使用 $meta
投影操作符来获取每个匹配文档的相关度分数,然后根据这些分数对搜索结果进行排序。
下面是一个简单的示例,演示如何在MongoDB中进行文本搜索并按相关度排序:
// 创建文本索引 db.collection.createIndex({ fieldName: "text" }); // 执行文本搜索并按相关度排序 db.collection.find( { $text: { $search: "your_search_query" } }, { score: { $meta: "textScore" } } ).sort({ score: { $meta: "textScore" } });
在这个示例中,fieldName
是你要进行文本搜索的字段名,your_search_query
是你的搜索查询。这个查询将返回按照相关度排序的结果列表,其中每个文档都有一个 score
字段,表示其与搜索查询的相关度。
每一个collection 可以对多个字段创建索引,但最后搜索的时候,也是对所有字段搜索,不能只搜索特定字段。
// 创建文本索引,包括多个字段 db.collection.createIndex({ field1: "text", field2: "text", field3: "text" });
另外需要主要,针对text 类似索引的创建,一个collection 只能创建一次,在使用命令直接对Mongo db创建的时候,会报错提示索引已经存在。但使用mongoose 创建索引,似乎没有报错,如果检索不符合预期,记得查看是否真的创建成功了对应的索引。
{ "ok" : 0, "errmsg" : "An equivalent index already exists with a different name and options. Requested index: { v: 2, key: { _fts: \"text\", _ftsx: 1 }, name: \"content_text\", weights: { content: 1 }, default_language: \"english\", language_override: \"language\", textIndexVersion: 3 }, existing index: { v: 2, key: { _fts: \"text\", _ftsx: 1 }, name: \"tokenized_content_text_tokenized_title_text\", background: true, weights: { tokenized_content: 1, tokenized_title: 1 }, default_language: \"english\", language_override: \"language\", textIndexVersion: 3 }", "code" : 85, "codeName" : "IndexOptionsConflict" }
看了一些文章,觉得手动支持中文检索,其实原理很简单。因为英文分词是空格分隔的,如果提前把中文分词好,使用空格连接,那就能假装成英文,使用英文检索的能力匹配。
{ content: { type: String, }, tokenized_content: { type: String, text: true, weight: 2 }, // 为原理的字段创建一个影子字段 }
为了兼容性比较好,我为需要全文检索的字段创建影子字段,保存分词好的中文,并且实现了一个mongo 插件,在增删改查 content 字段的时候,都会重新分词和更新tokenized_content 字段。 分词使用的是jieba 分词,最终效果还不错。