Elasticsearch 中關鍵字和文字的差異
1. 概述
在使用 Elasticsearch 時,最常見的挑戰之一是為要儲存的資料選擇合適的欄位類型。特別是字串字段,需要格外注意。 Elasticsearch提供兩種主要選項: keyword和text.選擇正確的欄位類型至關重要。否則,我們可能會遇到意外的搜尋結果、聚合失敗,甚至效能問題。
本文將探討keyword和text欄位的差異。然後,我們將解釋何時使用哪種欄位類型,以便設計更可靠、更有效率的 Elasticsearch 映射。
2. text欄位類型
在 Elasticsearch 中, text欄位類型用於全文搜尋。在建立索引之前,Elasticsearch 會分析內容,包括分詞、轉換為小寫,並根據分析器的不同,應用諸如同義詞處理和停用詞移除等步驟。
讓我們以以下文件為例來說明搜尋案例:
{
"type": "article",
"title": "Using Elasticsearch with Spring Boot",
"status": "IN_PROGRESS"
}
我們將標題列對應為text欄位類型,其他兩列對應為keyword類型。分析完成後,該列的索引結果如下所示:
["using", "elasticsearch", "with", "spring", "boot"]
我們可以使用匹配運算子來實現全文搜尋行為:
{
"query": {
"match": {
"title": "spring elasticsearch"
}
}
}
要使用 Elasticsearch Java API 用戶端執行全文搜索,我們可以先定義一個表示要索引的文件的模型。在 Java 中, record非常適合這種情況:
public record Article(String type, String title, String status) {
}
接下來,我們將使用客戶端定義搜尋查詢。
SearchResponse<Article> response = client.search(s -> s
.index("index")
.query(q -> q
.match(m -> m
.field("title")
.query("spring elasticsearch")
.operator(Operator.And) // require both terms
)
),
Article.class
);
此查詢使用全文分析在標題欄位中搜索,並且要求同時存在術語 spring 和 elasticsearch。
text欄位類型在字串值方面存在一些限制。例如,它不支援排序或聚合。這是因為文字欄位針對搜尋進行了最佳化,並未儲存實現高效排序和聚合所需的資料結構。此外, text欄位也不適用於精確匹配,因為 Elasticsearch 儲存的是分析後的詞元,而不是原始值。
3. keyword字段類型
當我們將欄位定義為keyword時,Elasticsearch 會按原樣儲存值,不會進行任何分析,例如分詞或轉換為小寫。這使得keyword欄位非常適合精確匹配、過濾、排序和聚合。
例如,我們將狀態欄位定義為keyword類型。使用我們的範例文檔,Elasticsearch 將按如下方式索引該值:
["IN_PROGRESS"]
要查找具有特定狀態的文檔,我們使用術語查詢,它執行精確匹配:
{
"query": {
"term": {
"status": "IN_PROGRESS"
}
}
}
現在,讓我們使用 Java 客戶端定義查詢:
SearchResponse<Article> response = client.search(s -> s
.index("index")
.query(q -> q
.term(t -> t
.field("status")
.value("IN_PROGRESS")
)
),
Article.class
);
需要注意的是,由於keyword欄位區分大小寫且要求完全匹配,因此搜尋「in_progress」(小寫)將不會傳回任何結果,即使其實際值為「IN_PROGRESS」。在使用keyword欄位時,理解這項特性至關重要。
使用keyword類型可以對欄位進行聚合,從而獲取關於文件的準確資訊。例如,如果我們想按狀態取得文件數量,可以定義以下查詢:
{
"size": 0,
"aggs": {
"by_status": {
"terms": {
"field": "status"
}
}
}
}
使用 Java 客戶端:
SearchResponse<Void> response = elasticsearchClient.search(s -> s.index("index")
.size(0)
.aggregations("by_status", a -> a.terms(t -> t.field("status"))), Void.class);
response.aggregations()
.get("by_status")
.sterms()
.buckets()
.array()
.forEach(b -> System.out.println(b.key()
.stringValue() + " -> " + b.docCount()));
此查詢會依照文件的確切狀態值對文件進行分組,並傳回每個群組的計數,從而可以輕鬆地基於分類資料產生報表或儀表板。
4. 選擇text還是keyword
現在我們已經了解了這兩種類型之間的區別,就可以推斷哪種更適合我們的具體情況。例如,如果我們需要進行全文搜索, text類型是最佳選擇。如果我們需要進行精確匹配搜尋或基於資料進行聚合, keyword類型則更合適。
排序是使用keyword類型的另一個原因。 Elasticsearch 只能對每個文件只有一個可比較值的欄位進行排序。由於關鍵字欄位按原樣儲存(未進行詞法分析),因此它們非常適合排序。讓我們來看看查詢語句:
{
"sort": [
{
"status": "asc"
}
]
}
使用 Java 客戶端:
SearchResponse<Article> response = elasticsearchClient.search(s -> s.index("index")
.sort(so -> so.field(f -> f.field("status")
.order(SortOrder.Asc))), Article.class);
同樣,雖然text類型並非設計用於聚合,但keyword類型也不適合長篇自由文字。以這種方式使用關鍵字類型只會增加索引大小,而無法提供相關性分數。
了解這些差異有助於我們建立更可靠的 Elasticsearch 索引,實現更好的效能,並產生準確的搜尋結果和聚合結果。
5. 多字段輸入
一種常見且建議的做法是使用多字段索引,將相同欄位同時索引為text和keyword 。這種方法既支援全文搜索,又支援對相同邏輯欄位進行精確匹配、排序和聚合。
以下是一個映射範例:
{
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
透過這種映射,Elasticsearch 會建立標題欄位的兩個索引版本,使我們能夠靈活地以不同的方式使用同一個欄位。例如,我們可以對已分析的版本執行全文搜尋:
{
"query": {
"match": {
"title": "spring elasticsearch"
}
}
}
然後使用關鍵字「版本」對結果進行排序:
{
"query": {
"match": {
"title": "spring elasticsearch"
}
},
"sort": [
{
"title.keyword": "asc"
}
]
}
使用 Java 用戶端,我們還可以將這兩種功能結合起來:
SearchResponse<Article> response = client.search(s -> s
.index("index")
.query(q -> q
.match(m -> m
.field("title")
.query("spring elasticsearch")
)
)
.sort(so -> so
.field(f -> f
.field("title.keyword")
.order(SortOrder.Asc)
)
),
Article.class
);
同樣,我們可以按keyword版本進行聚合,以獲得精確的值計數:
SearchResponse<Article> response = client.search(s -> s
.index("index")
.size(0)
.aggregations("popular_titles", a -> a
.terms(t -> t
.field("title.keyword")
.size(10)
)
),
Article.class
);
response.aggregations()
.get("popular_titles")
.sterms()
.buckets()
.array()
.forEach(b -> System.out.println(
b.key().stringValue() + " -> " + b.docCount()
));
這種模式對於產品名稱、文章標題或使用者生成內容等欄位尤其有用,因為這些欄位需要同時具備搜尋和分析功能。但是,我們需要注意的是,多字段會增加儲存需求,因為 Elasticsearch 會對資料進行兩次索引。
6. 結論
本文探討了 Elasticsearch 中text和keyword欄位類型之間的主要差異。我們回顧了每種欄位類型的搜尋工作原理,討論了它們的適用場景,並解釋了選擇正確的欄位類型如何帶來更準確的搜尋結果和更快的查詢速度。
我們還研究了多字段模式,該模式允許我們將同一字段同時索引為text和keyword 。這種方法既能靈活地執行全文搜索,又能支援對相同邏輯欄位進行精確匹配、排序和聚合。
與往常一樣,本教程的完整程式碼可在 GitHub 上找到。