Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用 MySQL 全文搜索索引的搜索 #46

Merged
merged 27 commits into from
May 12, 2024

Conversation

taoky
Copy link
Contributor

@taoky taoky commented Feb 8, 2024

解决目前搜索功能性能太差的尝试,需要在实际数据上测试性能和搜索效果(主要是点评,我没有测试的条件)。

解决思路:

  • 新增 CourseSearchCache 和 ReviewSearchCache 两张表,其中的 text 字段存储用于全文搜索的字符串,并且构建 fulltext index;
  • 在 view 中实现修改时更新 cache,同时 tests 中提供脚本来 drop/init
  • 更新时使用 jieba 分词,之后合并为以空格分割的字符串存储
  • 配置中允许切换搜索后端(原来的 like 和这个方案)
  • 搜索课程时要求必须包含对应 keyword,搜索点评时没有

如何测试:

  • MySQL 添加最短字符的配置(参考 README)
  • 初始化数据库创建 cache 表
  • 运行 python -m tests.manage_searchcache --init-course --init-review 初始化 cache 表内容

目前效果:

image
image
image

另外确实存在原来能够搜索到的,目前这个方案搜索不到的情况,比如说「计算机程序」会被 jieba 分为完整的词,但是 cut_for_search 预处理的时候分得更细,所以会没有结果。

@bojieli
Copy link
Collaborator

bojieli commented Feb 8, 2024

另外确实存在原来能够搜索到的,目前这个方案搜索不到的情况,比如说「计算机程序」会被 jieba 分为完整的词,但是 cut_for_search 预处理的时候分得更细,所以会没有结果。

这个没有看懂,cut_for_search 就是 jieba 分出来的词呀,为什么会出现 indexing 和 search query 分词结果不一致的情况呢?

@taoky
Copy link
Contributor Author

taoky commented Feb 8, 2024

另外确实存在原来能够搜索到的,目前这个方案搜索不到的情况,比如说「计算机程序」会被 jieba 分为完整的词,但是 cut_for_search 预处理的时候分得更细,所以会没有结果。

这个没有看懂,cut_for_search 就是 jieba 分出来的词呀,为什么会出现 indexing 和 search query 分词结果不一致的情况呢?

>>> list(jieba.cut_for_search('计算机程序设计'))
['计算', '算机', '计算机', '程序', '设计', '程序设计']
>>> list(jieba.cut('计算机程序设计'))
['计算机', '程序设计']
>>> list(jieba.cut('计算机程序'))
['计算机程序']

@ertuil
Copy link

ertuil commented Feb 10, 2024

我之前用elasticsearch 的时候也遇到了这些问题。我后来查了es文档,自己测试感觉最优实践是indexing的时候,同时依据最小分词和最优分词同时index,search的时候用最小分词这样命中率会比较高。

次优方式是保证search和index需要保证使用相同的index模式。
如果index和search使用不同的模式(比如分别 cut_for_search 和 cut),效果是最差的。

@taoky
Copy link
Contributor Author

taoky commented Feb 16, 2024

我本地测试感觉改得差不多了。目前的做法是:

数据库表

给 cache 表强制 collation="utf8mb4_unicode_ci" 是有必要的,因为会有 "英语交流Ⅰ"(罗马数字 1)这样的搜索,需要能对应到 "英语交流I"(大写的 i)。本地测试的时候加 mysqld 配置似乎没有生效,所以在代码里面加了 collation。

课程搜索

  • 索引的时候在原来 lcut_for_search 以外加入了每个出现的字符,类似这样:* 桥牌 基础 讲座 与 技巧 朱信龙 技 朱 讲 信 基 * 桥 础 巧 牌 龙 座
    • 这一点的考虑是,有些 query 是怎么分词都没法搜索的,比如说 "程设" -> "计",只能拆开来搜。
    • 和、与、及 这三个字在单独拆字的时候被去掉了(后面搜索也是),因为看搜索记录发现很容易搞错
  • 搜索的时候首先尝试识别课程号(长度 >= 5,只有字母和数字),如果有符合条件的项,先单独处理
    • 这个策略会把 python 这样的也包含进去
    • 课程号只做前缀匹配(LIKE XXXX%),这样至少能用索引优化
  • 然后所有的搜索词分词后空格拼接。其中上面匹配成课程号的不强制,其他的强制要求出现
    • 如果课程号搜索有结果,并且搜索词中有非课程号,那么就取交集(处理搜索课程号和老师名字的情况)
    • 否则取并集
  • 如果没有结果,那么把 query 一个一个字符拆开搜索,强制要求所有字符出现
  • 最后和 CourseRate join,排序
  • 没有按照 @ertuil 的提议来做,因为我从历史搜索分析来看感觉目前的策略足够了。

评论搜索

评论搜索的逻辑简单得多,直接分词,然后 Cache 表和 Review join 之后全文搜索、根据用户权限 filter。最后排序的时候先按照 MySQL 给出的相关度排序,相关度一致的就按照更新时间倒序。

其他的优化

  • 在调试的时候发现课程搜索中排序的 SQL 很慢,explain 发现每次排序都要把所有的评论过几遍,因此为总平均分等添加了一个一小时 TTL 的 cache
  • 对于老用户,首次加载获取通知很慢。对 to_user 添加了 index,并且发现 ORM 生成的 SQL 没有 limit,即使只要前五项,所以这里也做了修改
  • 在课程页面发现有很多评论的课程(比如说大物实验)里数据库查询比较多比较慢,因此尝试做了一些优化。本地测试 /course/1772/ 中数据库 query 的开销从 ~100ms 到了大约 50-55ms

其他

在 tests 下面添加了一些用来测试搜索的脚本。

@taoky taoky marked this pull request as ready for review February 16, 2024 07:42
@bojieli bojieli merged commit a0df7ad into USTC-iCourse:master May 12, 2024
@bojieli
Copy link
Collaborator

bojieli commented May 12, 2024

已经上线了,多谢 @taoky 的贡献

@bojieli
Copy link
Collaborator

bojieli commented May 12, 2024

现在搜索功能挺好的,我测试了都没有问题,搜索的数据库查询确实加速了不少。

现在点评搜索还没有 ranking,比如输入两个关键词,两个关键词同时匹配的点评应该排在前面。但之前和现在的搜索都没有 ranking 机制,是按照发表时间简单降序排列的。

@bojieli
Copy link
Collaborator

bojieli commented May 12, 2024

现在搜索功能挺好的,我测试了都没有问题,搜索的数据库查询确实加速了不少。

现在点评搜索还没有 ranking,比如输入两个关键词,两个关键词同时匹配的点评应该排在前面。但之前和现在的搜索都没有 ranking 机制,是按照发表时间简单降序排列的。

刚刚跟 @taoky 聊了下,发现是我测试的 query 恰好包含了中文分词出错的 case(是一个人名被拆成了两个词)。当分词没有出错的时候,已经实现了按照相关度匹配,就是多个关键词同时匹配的点评排在前面。Well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants