Lucene索引查询异常:相似/大量文档存在时无法找到目标文档
问题背景
我创建了如下文档:
{ Document document = new Document(); document.add(new TextField("id", "10384-10735", Field.Store.YES)); submitDocument(document); } { Document document = new Document(); document.add(new TextField("id", "10735", Field.Store.YES)); submitDocument(document); } for (int i = 20000; i < 80000; i += 123) { Document otherDoc1 = new Document(); otherDoc1.add(new TextField("id", String.valueOf(i), Field.Store.YES)); submitDocument(otherDoc1); Document otherDoc2 = new Document(); otherDoc2.add(new TextField("id", i + "-" + (i + 67), Field.Store.YES)); submitDocument(otherDoc2); }
即:
- 一个ID为
10384-10735的文档 - 一个ID为
10735的文档(为前者ID的后半部分) - 另外975个任意ID的文档
索引写入代码
随后使用如下代码写入索引:
final IndexWriterConfig luceneWriterConfig = new IndexWriterConfig(new StandardAnalyzer()); luceneWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND); final IndexWriter luceneDocumentWriter = new IndexWriter(luceneDirectory, luceneWriterConfig); for (Map.Entry<String, Document> indexDocument : indexDocuments.entrySet()) { final Term term = new Term(Index.UNIQUE_LUCENE_DOCUMENT_ID, indexDocument.getKey()); indexDocument.getValue().add(new TextField(Index.UNIQUE_LUCENE_DOCUMENT_ID, indexDocument.getKey(), Field.Store.YES)); luceneDocumentWriter.updateDocument(term, indexDocument.getValue()); } luceneDocumentWriter.close();
查询尝试与异常结果
索引写入完成后,我尝试通过两种方式查询ID为10384-10735的文档:TermQuery和使用StandardAnalyzer的QueryParser:
System.out.println("term query: " + index.findDocuments(new TermQuery(new Term("id", "10384-10735")))); final QueryParser parser = new QueryParser(Index.UNIQUE_LUCENE_DOCUMENT_ID, new StandardAnalyzer()); System.out.println("query parser: " + index.findDocuments(parser.parse("id:\"10384 10735\"")));
预期两种查询都能找到目标文档,但实际结果均为空:
term query: [] query parser: []
测试发现的规律
经测试发现,若减少文档数量,或移除ID为10735的文档,QueryParser即可成功找到目标文档:
term query: [] query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>]
我使用的是lucene-core和lucene-queryparser 9.3.0版本,需要确保索引能稳定找到目标文档,可接受使用QueryParser替代TermQuery,请问该问题的原因是什么?
Edit 1:findDocuments()方法实现
final TopDocs topDocs = getIndexSearcher().search(query, Integer.MAX_VALUE); final List<Document> documents = new ArrayList<>((int) topDocs.totalHits.value); for (int i = 0; i < topDocs.totalHits.value; i++) { documents.add(getIndexSearcher().doc(topDocs.scoreDocs[i].doc)); } return documents;
Edit 2:测试结果验证
完整测试的输出结果如下:
[similar id: true, many documents: true] Indexing [3092] documents term query: [] query parser: [] [similar id: true, many documents: false] Indexing [654] documents term query: [] query parser: [] [similar id: false, many documents: true] Indexing [3091] documents term query: [] query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>] [similar id: false, many documents: false] Indexing [653] documents term query: [] query parser: [Document<stored,indexed,tokenized<id:10384-10735> stored,indexed,tokenized<uldid:10384-10735>>]
可见,只要添加了ID为10735的文档,就无法找到目标文档。
问题原因分析
1. TermQuery始终失败的原因
你使用TextField存储id字段,而TextField会通过StandardAnalyzer分词处理输入内容。对于10384-10735这类带连字符的字符串,StandardAnalyzer会将其拆分为10384和10735两个独立词项,不会保留完整的10384-10735作为索引词项。而TermQuery是精确匹配索引中的单个词项,你用完整的10384-10735作为Term查询,自然无法匹配到任何索引词项,因此返回空结果。
2. QueryParser在添加10735文档后失败的核心原因
这是因为你在写入索引时使用updateDocument方法的逻辑存在致命错误:
- 你为每个文档添加了
TextField类型的Index.UNIQUE_LUCENE_DOCUMENT_ID(简称uldid)字段,该字段会被StandardAnalyzer分词处理。 - 调用
updateDocument时,传入的Term是new Term(uldid, indexDocument.getKey()),其中indexDocument.getKey()是文档的唯一ID(比如10735或10384-10735)。
当你写入ID为10735的文档时:
updateDocument会先执行词项查询,查找所有uldid字段包含10735词项的文档。- 目标文档的
uldid字段是10384-10735,被分词后包含10735词项,因此会被匹配到并删除。 - 随后添加的是ID为
10735的新文档,而非目标文档。
这就导致只要添加了10735文档,目标文档就会被意外删除,自然无法通过任何查询找到它。而移除该文档时,目标文档不会被删除,因此QueryParser能正常查询到。
解决方案
1. 修复索引写入逻辑
将Index.UNIQUE_LUCENE_DOCUMENT_ID字段改为StringField而非TextField。StringField会将整个字符串作为单个词项索引,不会进行分词处理,这样updateDocument就能通过Term精确匹配到对应的文档,避免误删其他文档:
// 替换原来的TextField为StringField indexDocument.getValue().add(new StringField(Index.UNIQUE_LUCENE_DOCUMENT_ID, indexDocument.getKey(), Field.Store.YES));
2. 针对ID字段的查询优化
如果需要精确查询id字段的完整值,建议将id字段也改为StringField,这样可以直接用TermQuery精确匹配完整ID。如果必须保留TextField类型,查询时需要使用短语查询(如id:"10384 10735"),并确保目标文档不会被误删。
内容的提问来源于stack exchange,提问作者Skyball

