今天在线上生产环境中碰到一个Solr的查询条件无法匹配到查询结果的问题,问题虽小,但是找到问题的过程确实比较周折,还好最终问题只是一层窗户纸,这里记录以下,以作备忘。
问题是这样的,业务方告诉我有一个查询条件,没有办法匹配到目标记录。查询条件是:name:Y9砵仔糕吕托 收到问题,于是就开始了我的排错之路。
首先,确认了一下name字段原始的文本字段是“Y9砵仔糕吕托”,对应的列的名称是 name,field type是一个自定义类型,类型为:
<fieldType name="like" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false" omitNorms="true" omitPositions="true"> <analyzer type="index"> <tokenizer class="solr.PatternTokenizerFactory" pattern=",\s*"/> <filter class="com.dfire.tis.solrextend.fieldtype.pinyin.AllWithNGramTokenFactory"/> <filter class="solr.StandardFilterFactory"/> <filter class="solr.TrimFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.PatternTokenizerFactory" pattern=",\s*"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
这个filetype为了实现用户在搜索引擎上实现类似数据like查询的需求(当然solr上也能使用wildcardQuery,但是wildcardQuery比较耗费性能,数据量小应该没有问题,数据量大的话就要斟酌一下了)。
AllWithNGramTokenFactory这个filter类型扩展了一下Solr默认的NGram类型,在分析的时候额外多加了一个整个field的字面量作为Term语汇单元。
为了排查问题,先写了一个小程序,打印一下索引文件上name 字段term为“Y9砵仔糕吕托” 的记录到底存在不存在,写了以下一段代码:
private void readIteraveTerm() throws Exception { EmbeddedSolrServer server ; SolrCore core = server.getCoreContainer().getCore("supplygood"); IndexReader rootreader = core.getSearcher().get().getIndexReader(); LeafReader reader = null; Terms terms = null; TermsEnum termEnum = null; PostingsEnum posting = null; BytesRef term = null; int docid = 0; for (LeafReaderContext leaf : rootreader.getContext().leaves()) { reader = leaf.reader(); liveDocs = reader.getLiveDocs(); terms = reader.terms("name"); termEnum = terms.iterator(); int count = 0; String find = "Y9砵仔糕吕托"; if ((termEnum.seekExact(new BytesRef(find)))) { System.out.println(termEnum.term().utf8ToString()); posting = termEnum.postings(posting); do { docid = posting.nextDoc(); System.out.println("docid:" + ("" + docid) + "_" + leaf.docBase); } while (docid != PostingsEnum.NO_MORE_DOCS); } } }
这段代码的执行结果证明,在索引上”Y9砵仔糕吕托”为Term的记录是存在,那奇怪的是为什么,通过查询条件 name:Y9砵仔糕吕托 找不到对应的记录呢?
又想起调试一下Solr的代码,最终底层Lucene是会通过 TermQuery这个类来执行查询的:
package org.apache.lucene.search; public class TermQuery extends Query { ..... public TermQuery(Term t) { term = Objects.requireNonNull(t); perReaderTermState = null; } }
所以在TermQuery类的构造函数上加了一个调试断点,启动程序进行条件,看看构造函数上传入的参数Term到底是一个什么值,结果看到的构造函数参数为“y9砵仔糕吕托”,奇怪,怎么大写的Y变成小写了。然后通过“y9砵仔糕吕托”到索引上找果然是没有这个Term作为的记录的,然后再看了一下Schema的fieldType,果然在query的分析配置中有<filter class="solr.LowerCaseFilterFactory"/>这个配置项,也就是说在查询阶段,会将name列中含有的大写字母统统转化成小写字母,而在索引生成的时候并没有做这大写转小写的操作,所以很自然的就会发生本文一开头说的问题。
将FiledType改成下面这个样子就好了:
<fieldType name="like" class="solr.TextField" positionIncrementGap="100" autoGeneratePhraseQueries="false" omitNorms="true" omitPositions="true"> <analyzer type="index"> <tokenizer class="solr.PatternTokenizerFactory" pattern=",\s*"/> <filter class="com.dfire.tis.solrextend.fieldtype.pinyin.AllWithNGramTokenFactory"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.TrimFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.PatternTokenizerFactory" pattern=",\s*"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType>
在analyzer中也加了一个<filter class="solr.LowerCaseFilterFactory"/>过滤器问题就解决了,哈哈