MongoDB 模糊查询慢的问题以及相关解决方案的探索

最近公司有一个项目,数据库用的是 MongoDB,其中一个 Collection 的有 1300 万条数据,需求中需要对这个 Collection 支持指定 field 和 keyword 的搜索功能。一开始的实现就是简单的 and 查询的追加,但是发现查询速度很慢,需要优化。MongoDB 的查询慢不是一直都能体现出来,只有在查询的语句比较复杂或者查询出来的结果比较少比较靠后的情况下才会出现。

例如下面 2 种模糊查询的情况:

  1. db.getCollection('mapping').find({"mapName":{"$regex": '.*office.*'}}).limit(50),这个查询 3s 左右出来结果,因为有大量数据可以匹配这个查询,MongoDB查找到 50 条数据后就不会继续往下查找了;
  2. db.getCollection('mapping').find({"mapName":{"$regex": '.*microsoft office.*'}}).limit(50),这个查询要 20s 以上才能出结果,因为能够匹配的数据量少,MongoDB 会一直往下查询直到找到 50 条为止。

尝试在网上找了一些解决方案,下面是一些收获。

创建索引,并强制命中索引

单独为某个字段添加索引,如果是精确查询,带来的性能提升是很大的。但是目前 cms-backend 查询的实现方式是用正则来模糊匹配的,基于正则的模糊匹配不会命中索引,但是 可以通过 hint 来强制命中索引。

例如,查询 mapping 表的 mapName 字段:

  1. db.mapping.find({'mapName': 'Wireshark 2.0.7 (32-bit)'}).limit(50),命中索引,1ms 出结果,没有索引的情况下要 8 秒以上才能出结果;
  2. db.mapping.find({'mapName': {'$regex': 'Wireshark.*'}}).limit(50),没有命中索引,13s 左右出结果;
  3. db.mapping.find({'mapName': {'$regex': 'Wireshark.*'}}).limit(50).hint('mapName_-1'), 强制命中索引,8s 左右出结果;
  4. db.mapping.find({'mapName': {'$regex': '.*Wireshark.*'}}).limit(50) 没有命中索引,14s 左右出结果;
  5. db.mapping.find({'mapName': {'$regex': '.*Wireshark.*'}}).limit(50).hint('mapName_-1'), 强制命中索引,8s 左右出结果;

全文索引

MongoDB 的全文索引自带分词功能,通过 db.mapping.createIndex({'mapName':'text'}) 创建全文索引后,可以通过 db.mapping.find({'$text': {'$search': 'office'}) 来根据全文索引搜索,但是效果不理想,需要 11s 左右才能出结果。

加大MongoDB所在服务器内存

原本 MongoDB 所在的服务器内存总共大小是 6G,让网络那边帮忙增加了 10G 的内存,在经过多次查询之后,MongoDB 使用了 13G 的内存,整个 MongoDB 的数据库文件加上索引文件,也不到 2G,这里不是很明白为什么 MongoDB 会占用这么高的内存。按理来说 MongoDB 会将数据缓存到内存里面,读取速度应该会有一些提升,但是查询速度并没有明显提升。

MongoDB Count() 查询非常的慢

MongoDB 的 Count() 查询支持放入查询语句,在没有任何查询语句的情况下,Count() 非常快,但是一旦在里面加了查询,例如 db.mapping.count({'mapName': {'$regex': '.*office.*'}}), 就很慢。

之前分页需要 Count() 的总数来计算总页数,现在 Mapping 表对应 CMS 的列表页面已经改成用“上一页”和“下一页”这种分页方式了,在没有输入搜索条件的情况下,列表页面显示结果非常快。其实所有数据库的 Count() 查询都不快,但是感觉 MongoDB 的 Count() 查询确实是慢一点。

MongoDB 搭配一些搜索引擎工具来查询数据

网上看到的关于解决 MongoDB 查询速度慢的方案,大部分都是搭配一些搜索引擎工具,例如Lucence、Sphinx、Elasticsearch。项目这边已经有 Elasticsearch,从另外一个同事知道现在 Elasticsearch 是有 Mapping 表的数据。Elasticsearch 的查询确实很快,但是如果搭配 Elasticsearch 来查询,那每次往 Database 里面添加/删除/更新数据,都要通知 Elasticsearch 更新索引,这个相比直接使用 MongoDB 查询,会带来其他工作量。总的来说 MongoDB 只是一款存储数据的工具,想要更好更快的浏览数据但是得搭配其它工具使用。

本文链接:https://blog.wardchan.com/posts/poor-performance-for-mongodb-fuzzy-query.html参与评论 »

--EOF--

Comments