【问题标题】:Lucene phrase query with wildcards带通配符的 Lucene 短语查询
【发布时间】:2014-09-29 13:41:29
【问题描述】:

我想出了使用以下代码以编程方式创建查询以搜索带有通配符的短语的解决方案:

public static Query createPhraseQuery(String[] phraseWords, String field) {
    SpanQuery[] queryParts = new SpanQuery[phraseWords.length];
    for (int i = 0; i < phraseWords.length; i++) {
        WildcardQuery wildQuery = new WildcardQuery(new Term(field, phraseWords[i]));
        queryParts[i] = new SpanMultiTermQueryWrapper<WildcardQuery>(wildQuery);
    }
    return new SpanNearQuery(queryParts,       //words
                             0,                //max distance
                             true              //exact order
    );
}

创建示例并调用 toString() 方法会输出:

String[] phraseWords = new String[]{"foo*", "b*r"};
Query phraseQuery = createPhraseQuery(phraseWords, "text");
System.out.println(phraseQuery.toString());

输出:

spanNear([SpanMultiTermQueryWrapper(text:foo*), SpanMultiTermQueryWrapper(text:b*r)], 0, true)

这对大多数情况来说效果很好,而且速度足够快。例如,如果我创建这样的查询并使用它进行搜索,它将输出所需的结果,例如:

Sentence with foo bar.
Foolies beer drinkers.
...

不是这样的:

Bar fooes.
Foo has bar.

我已经提到,在大多数情况下,查询的工作速度足够快。目前我有一个大小为 aprox 的索引。 200GB,平均搜索时间在 0.1 到 3 秒之间。取决于许多因素,例如:缓存、与短语中单个单词匹配的文档子集的大小,因为 lucene 将在已建立的术语之间执行集合交集。

示例: 假设我想查询短语“an* karenjin*”(我将其拆分为 ["an*", "karenjin*"],然后使用 createPhraseQuery 方法创建查询)并且我希望它匹配包含以下内容的句子:"ana karenjina ", "ani karenjinoj", "ane karenjine", ...(克罗地亚语语法不同)。

这个查询非常慢,我没有等待足够长的时间来获得结果(超过 1 小时),并且有时会导致 GC 开销限制超出异常。 这种行为在某种程度上是意料之中的,因为“an*”本身匹配大量文档。我知道我可以在 30-40 秒(更快但仍然很慢)内查询“an? karanjin*”。

这就是我困惑的地方。 如果我只查询“karenjin*”,它会在 1 秒内给出结果。因此,我尝试使用 WildcardQuery 和 QueryWrapperFilter 查询“an* karenjin*”并使用过滤器“karenjin*”。而且它仍然是不可接受的缓慢(我在它返回任何东西之前杀死了进程)。

文档说过滤器减少了查询的搜索空间。所以我尝试使用过滤器:

Filter filter = new QueryWrapperFilter(new WildcardQuery(new Term("text", "karanjin*")));

并查询:

Query query = createPhraseQuery(new String[]{"an*", "karenjin*"}, "text");

比搜索,(经过几次热身查询):

Sort sort = new Sort(new SortField("insertTime", SortField.Type.STRING, true));
TopDocs docs = searcher.search(query, filter, 100, sort);

好的,我的问题是什么?

查询是怎么回事:

 Query query = new WildcardQuery(new Term("text", "karanjin*"));

很快,但使用上述过滤器仍然很慢?

【问题讨论】:

    标签: lucene query-performance phrase


    【解决方案1】:

    是的,通配符可能会影响性能,尤其是当它们匹配很多术语时,但您所描述的确实令人惊讶。很难确定为什么会发生这种情况,但可以尝试一下。

    我假设:

    Query query = new WildcardQuery(new Term("text", "an*"));
    

    就其本身而言,它的表现非常糟糕,如前所述。由于您要查找的通配符都是前缀样式查询,因此最好使用 PrefixQuery

    Query query = new PrefixQuery(new Term("text", "an"));
    

    虽然我认为这不会有太大的不同。可能会有所不同的是改变你的重写方法。您可以尝试限制Terms 查询被重写的数量:

    Query query = new PrefixQuery(new Term("text", "an"));
    //or
    //Query query = new WildcardQuery(new Term("text", "an*"));
    query.setRewriteMethod(new MultiTermQuery.RewriteMethod.TopTermsRewrite(10));
    

    【讨论】:

    • 感谢您的建议,我会尽量限制条款的数量,看看效果如何。我期待它会快很多。但结果可能不完整。这是时间和结果之间的权衡。
    • 我会试试的。并且根据实际操作中的预定 Lucene,WildcardQuery 将在内部被识别并优化为 PrefixQuery,如果它以 * 结尾,或者如果没有通配符,甚至优化为 TermQuery。
    • 我相信这是正确的,但我更希望该逻辑存在于解析中,但我没有在那里看到它。不过可能是重写本身的一部分。
    • 在当前版本的 Lucene 中,PrefixQuery 和 WildcardQuery 都扩展了 AutomatonQuery,并且它们生成的自动机是相同的,因此选择其中一个并没有明显的好处。
    猜你喜欢
    • 2013-08-07
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 1970-01-01
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多