怎样提升MongoDB里正则查询的效率

NoSQL数据库中我们很容易的可以创建一些包含数组的元素,比如一个存电影信息的数据结构可以这么写:

1
2
3
4
{
title: 'Matrix',
cast: ['Keanu Reeves', 'Carrie-Anne Moss']
}

我们要查Carrie-Anne Moss演的电影就可以这么写查询db.movies.find({cast:'Carrie-Anne Moss'})来获取匹配的文档。

Using A Regex for non-exact search queries

但不幸的是,这种理想的搜索方式在现实中只占了一小部分,而且我们需要为用户的“任性的”搜索方式做兼容。
用户可能会输入Carrie Moss或者moss carries-anne,这时候精确搜索就会查不到结果。
此时,我们需要用到MongoDB的正则查询方式,可以写成如下的查询:

1
2
3
db.movies.find({
cast: { $elemMatch: {$regex: /Moss/i, $regex: /Carrie-Anne/i}}
});

这样便可以查询到想要的结果了,但是也会有一些搜索意图之外的记录,比如Sandra Moss和Carrie-Anne Fisher。这样的查询方式最大的问题是,随着我们数据的增加,会有严重的效率问题:
× 占用大量的CPU时间
× 查询会变得非常的慢

为什么加索引不能解决这个问题

不管什么数据库,再优化性能时,首先要考虑的就是加索引。但是由于我们的查询需要忽略大小写,所以就算我们而外创建一个纯小写的数组也无法从根本上解决问题,用户输入始终是不可控的,比如Carrie-Anne Moss和Moss Carrie Anne,我们无法确定以开头的字符串,也就无法利用建好的索引。

全文索引可以救我们

创建全文索引:

1
db.movies.createIndex( { cast: "text" } );

查询结果:

1
db.movies.find( { $text: { $search: "Moss Carrie-Anne" } } );

虽然全文索引非常的快和高效,但是仍然会有一些问题:
× 如果你用多个字段建立了一个全文索引,那么查询的时候是无法用字段区分它们的。
× 查询的结果会很宽泛。

把全文搜索和正则搜索结合起来

我们都知道这样的表达式if(someFunc()&&someOtherFunc()){}如果someFunc()返回的是false,那么someOtherFunc()是不会被执行的。这个逻辑在MongoDB中也是成立的。
所以我们可以用这样的组合查询:

1
2
3
4
5
6
7
8
9
db.movies.find({
$and:[{
$text: {
$search: "Moss Carrie-Anne"
}},{
cast: {
$elemMatch: {$regex: /Moss/, $regex: /Carrie-Anne/}}
}]}
);

这样在数据量大的数据库中,效率提升尤为明显。这样的思路不仅仅可以用在MongoDB,其他数据库也可以尝试使用。

加载评论框需要科学上网