作者簡介:《RocketMQ技術内幕》作者、中間件興趣圈微信公衆号維護者。
本文将詳細介紹Elasticsearch在建立索引映射時可指定的參數,并重點分析其含義。
1、analyzer
指定分詞器。elasticsearch是一款支援全文檢索的分布式存儲系統,對于text類型的字段,首先會使用分詞器進行分詞,然後将分詞後的詞根一個一個存儲在反向索引中,後續查詢主要是針對詞根的搜尋。
analyzer該參數可以在每個查詢、每個字段、每個索引中使用,其優先級如下(越靠前越優先):
1)字段上定義的分詞器
2)索引配置中定義的分詞器
3)預設分詞器(standard)
在查詢上下文,分詞器的查找優先為:
1)full-text query中定義的分詞器
2)定義類型映射時,字段中search_analyzer 定義的分詞器。
3)定義字段映射時analyzer定義的分詞器
4)索引中default_search中定義的分詞器。
5)索引中預設定義的分詞器
6)标準分詞器(standard)。
2、normalizer
規劃化,主要針對keyword類型,在索引該字段或查詢字段之前,可以先對原始資料進行一些簡單的處理,然後再将處理後的結果當成一個詞根存入反向索引中,舉例如下:
PUT index
{
"settings": {
"analysis": {
"normalizer": {
"my_normalizer": { // @1
"type": "custom",
"char_filter": [],
"filter": ["lowercase", "asciifolding"] // @2
}
}
}
},
"mappings": {
"_doc": {
"properties": {
"foo": {
"type": "keyword",
"normalizer": "my_normalizer" // @3
}
}
}
}
}
代碼@1:首先在settings中的analysis屬性中定義normalizer。
代碼@2:設定标準化過濾器,示例中的處理器為小寫、asciifolding。
代碼@3:在定義映射時,如果字段類型為keyword,可以使用normalizer引用定義好的normalizer。
3、 boost
權重值,可以提升在查詢時的權重,對查詢相關性有直接的影響,其預設值為1.0。其影響範圍為詞根查詢(team query),對字首、範圍查詢、全文索引(match query)不生效。
注意:不建議在建立索引映射時使用boost屬性,而是在查詢時通過boost參數指定。其主要原因如下:
1)無法動态修改字段中定義的boost值,除非使用reindex指令重建索引。
2)相反,如果在查詢時指定boost值,每一個查詢都可以使用不同的boost值,靈活。
3)在索引中指定boost值,boost存儲在記錄中,進而會降低分數計算的品質。
4、coerce
是否進行類型“隐式轉換”。es最終存儲文檔的格式是字元串。
例如存在如下字段類型:
"number_one": {
"type": "integer"
}
聲明number_one字段的類型為數字類型,那是否允許接收“6”字元串形式的資料呢?因為在JSON中,“6”用來賦給int類型的字段,也是能接受的,預設coerce為true,表示允許這種指派,但如果coerce設定為false,此時es隻能接受不帶雙引号的數字,如果在coerce=false時,将“6”指派給number_one時會抛出類型不比對異常。
可以在建立索引時指定預設的coerce值,示例如下:
PUT my_index
{
"settings": {
"index.mapping.coerce": false
},
"mappings": {
// 省略字段映射定義
}
}
5、copy_to
copy_to參數允許您建立自定義的_all字段。換句話說,多個字段的值可以複制到一個字段中例如,first_name和last_name字段可以複制到full_name字段如下:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
}
表示字段full_name的值來自 first_name + last_name。
關于copy_to重點說明:
1)字段的複制是原始值,而不是分詞後的詞根。
2)複制字段不會包含在_souce字段中,但可以使用複制字段進行查詢。
3)同一個字段可以複制到多個字段,寫法如下:"copy_to": [ "field_1", "field_2" ]
6、 doc_values
當需要對一個字段進行排序時,es需要提取比對結果集中的排序字段值集合,然後進行排序。反向索引的資料結構對檢索來說相當高效,但對排序就不那麼擅長了。
業界對排序、聚合非常高效的資料存儲格式首推列式存儲,在elasticsearch中,doc_values就是一種列式存儲結構,預設情況下絕大多數資料類型都是開啟的,即在索引時會将字段的值(或分詞後的詞根序列)加入到反向索引中,同時也會該字段的值加入doc_values中,所有該類型的索引下該字段的值用一列存儲。
doc_values的使用示例:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"status_code": {
"type": "keyword" // 預設情況下,“doc_values”:true
},
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
}
7、dynamic
是否允許動态的隐式增加字段。在執行index api或更新文檔API時,對于_source字段中包含一些原先未定義的字段采取的措施,根據dynamic的取值,會進行不同的操作:
1)true,預設值,表示新的字段會加入到類型映射中。
2)false,新的字段會被忽略,即不會存入_souce字段中,即不會存儲新字段,也無法通過新字段進行查詢。
3)strict,會顯示抛出異常,需要新使用put mapping api先顯示增加字段映射。
dynamic設定為false,也是可以通過put mapping api進行字段的新增,同樣put mapping api可以對dynamic值進行更新。
舉例說明:
PUT my_index/_doc/1
{
"username": "johnsmith",
"name": {
"first": "John",
"last": "Smith"
}
}
PUT my_index/_doc/2 // @1
{
"username": "marywhite",
"email": "[email protected]",
"name": {
"first": "Mary",
"middle": "Alice",
"last": "White"
}
}
GET my_index/_mapping // @2
代碼@1在原有的映射下,增加了username,name.middle兩個字段,通過代碼@2擷取映射API可以得知,es已經為原本不存在的字段自動添加了類型映射定義。
注意:dynamic隻對目前層級具有限制力,例如:
PUT my_index
{
"mappings": {
"_doc": {
"dynamic": false, // @1
"properties": {
"user": { // @2
"properties": {
"name": {
"type": "text"
},
"social_networks": { // @3
"dynamic": true,
"properties": {}
}
}
}
}
}
}
}
代碼@1:_doc類型的頂層不能不支援動态隐式添加字段映射。
代碼@2:但_doc的嵌套對象user對象,是支援動态隐式添加字段映射。
代碼@3:同樣對于嵌套對象social_networks,也支援動态隐式添加字段映射。
8、enabled
是否建立索引,預設情況下es會嘗試為你索引所有的字段,但有時候某些類型的字段,無需建立索引,隻是用來存儲資料即可。隻有映射類型(type)和object類型的字段可以設定enabled屬性。示例如下:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"user_id": {
"type": "keyword"
},
"last_updated": {
"type": "date"
},
"session_data": {
"enabled": false
}
}
}
}
}
PUT my_index/_doc/session_1
{
"user_id": "kimchy",
"session_data": {
"arbitrary_object": {
"some_array": [ "foo", "bar", { "baz": 2 } ]
}
},
"last_updated": "2015-12-06T18:20:22"
}
上述示例,es會存儲session_data對象的資料,但無法通過查詢API根據session_data中的屬性進行查詢。
同樣,可以通過put mapping api更新enabled屬性。
9、eager_global_ordinals
全局序列号,它以字典順序為每個惟一的術語保持遞增的編号。全局序号隻支援字元串類型(關鍵字和文本字段)。在關鍵字字段中,它們在預設情況下是可用的,但文本字段隻能fielddata=true時可用。doc_values(和fielddata)也有序号,是特定段(segment)和字段中所有詞根(term)的唯一編号。全局序号隻是在此之上建構的,它提供了段序号(segment ordinals)和全局序号(global ordinals)之間的映射,全局序号在整個分片中是唯一的。由于每個字段的全局序号與一個分片(shard)的所有段(segment)相關聯,是以當一個新的segment(段)變為可見時,需要完全重新建構它們。術語聚合依懶全局序号,首先在分片級别(shard)執行聚合,然後彙聚所有分片的結果(reduce)并将全局序号轉換為真正的詞根(字元串),合并後傳回聚合後的結果。預設情況下,全局序号是在搜尋時加載的,這對提高索引API的速度會非常有利。但是,如果您更加重視搜尋性能,,那麼在您計劃使用的聚合的字段上設定eager_global_ordinals,會對提高查詢效率更有幫助。
eager_global_ordinals的意思是預先加載全局序号。
其示例如下:
PUT my_index/_mapping/_doc
{
"properties": {
"tags": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
10、fielddata
為了解決排序與聚合,elasticsearch提供了doc_values屬性來支援列式存儲,但doc_values不支援text字段類型。因為text字段是需要先分析(分詞),會影響doc_values列式存儲的性能。es為了支援text字段高效排序與聚合,引入了一種新的資料結構(fielddata),使用記憶體進行存儲。預設建構時機為第一次聚合查詢、排序操作時建構,主要存儲反向索引中的詞根與文檔的映射關系,聚合,排序操作在記憶體中執行。是以fielddata需要消耗大量的JVM堆記憶體。一旦fielddata加載到記憶體後,它将永久存在。通常情況下,加載fielddata是一個昂貴的操作,故預設情況下,text字段的字段預設是不開啟fielddata機制。在使用fielddata之前請慎重考慮為什麼要開啟fielddata。通常text字段用來進行全文搜尋,對于聚合、排序字段,建議使用doc_values機制。
為了節省記憶體的使用,es提供了另一項機制(fielddata_frequency_filter),允許隻加載那些詞根頻率在指定範圍(最大,小值)直接的詞根與文檔的映射關系,最大最小值可以指定為絕對值,例如數字,也可以基于百分比(百分比的計算是基于整個分段(segment),其頻率分母不是分段(segment)中所有的文檔,而是segment中該字段有值的文檔)。可以通過min_segment_size參數來指定分段中必須包含的最小文檔數量來排除小段,也就是說可以控制fielddata_frequency_filter的作用範圍是包含大于min_segment_size的文檔數量的段。
11、format
在JSON文檔中,日期表示為字元串。Elasticsearch使用一組預先配置的格式來識别和解析這些字元串,并将其解析為long類型的數值(毫秒)。
日期格式主要包括如下3種方式:
1)自定義格式
2)date mesh(已在DSL查詢API中詳解)
3)内置格式
一:自定義格式
首先可以使用java定義時間的格式,例如:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
二:date mesh
某些API支援,已在DSL查詢API中詳細介紹過,這裡不再重複。
三:内置格式
elasticsearch為我們内置了大量的格式,如下:
-
epoch_millis
時間戳,機關,毫秒。
-
epoch_second
時間戳,機關,秒。
-
date_optional_time
日期必填,時間可選,其支援的格式如下:

-
basic_date
其格式表達式為 :yyyyMMdd
-
basic_date_time
其格式表達式為:yyyyMMdd'T'HHmmss.SSSZ
-
basic_date_time_no_millis
其格式表達式為:yyyyMMdd'T'HHmmssZ
-
basic_ordinal_date
4位數的年 + 3位(day of year),其格式字元串為yyyyDDD
-
basic_ordinal_date_time
其格式字元串為yyyyDDD'T'HHmmss.SSSZ
-
basic_ordinal_date_time_no_millis
其格式字元串為yyyyDDD'T'HHmmssZ
-
basic_time
其格式字元串為HHmmss.SSSZ
-
basic_time_no_millis
其格式字元串為HHmmssZ
-
basic_t_time
其格式字元串為'T'HHmmss.SSSZ
-
basic_t_time_no_millis
其格式字元串為'T'HHmmssZ
-
basic_week_date
其格式字元串為xxxx'W'wwe,4為年 ,然後用'W', 2位week of year(所在年裡周序号) 1位 day of week。
-
basic_week_date_time
其格式字元串為xxxx'W'wwe'T'HH:mm:ss.SSSZ.
-
basic_week_date_time_no_millis
其格式字元串為xxxx'W'wwe'T'HH:mm:ssZ.
-
date
其格式字元串為yyyy-MM-dd
-
date_hour
其格式字元串為yyyy-MM-dd'T'HH
-
date_hour_minute
其格式字元串為yyyy-MM-dd'T'HH:mm
-
date_hour_minute_second
其格式字元串為yyyy-MM-dd'T'HH:mm:ss
-
date_hour_minute_second_fraction
其格式字元串為yyyy-MM-dd'T'HH:mm:ss.SSS
- date_hour_minute_second_millis
- date_time
- date_time_no_millis
-
hour
其格式字元串為HH
-
hour_minute
其格式字元串為HH:mm
-
hour_minute_second
其格式字元串為HH:mm:ss
-
hour_minute_second_fraction
其格式字元串為HH:mm:ss.SSS
- hour_minute_second_millis
-
ordinal_date
其格式字元串為yyyy-DDD,其中DDD為 day of year。
-
ordinal_date_time
其格式字元串為yyyy-DDD‘T’HH:mm:ss.SSSZZ,其中DDD為 day of year。
-
ordinal_date_time_no_millis
其格式字元串為yyyy-DDD‘T’HH:mm:ssZZ
-
time
其格式字元串為HH:mm:ss.SSSZZ
-
time_no_millis
其格式字元串為HH:mm:ssZZ
-
t_time
其格式字元串為'T'HH:mm:ss.SSSZZ
-
t_time_no_millis
其格式字元串為'T'HH:mm:ssZZ
-
week_date
其格式字元串為xxxx-'W'ww-e,4位年份,ww表示week of year,e表示day of week。
-
week_date_time
其格式字元串為xxxx-'W'ww-e'T'HH:mm:ss.SSSZZ
-
week_date_time_no_millis
其格式字元串為xxxx-'W'ww-e'T'HH:mm:ssZZ
-
weekyear
其格式字元串為xxxx
-
weekyear_week
其格式字元串為xxxx-'W'ww,其中ww為week of year。
-
weekyear_week_day
其格式字元串為xxxx-'W'ww-e,其中ww為week of year,e為day of week。
-
year
其格式字元串為yyyy
-
year_month
其格式字元串為yyyy-MM
-
year_month_day
溫馨提示,日期格式時,es建議在上述格式之前加上strict_字首。
12、ignore_above
超過ignore_above設定的字元串不會被索引或存儲。對于字元串數組,将分别對每個數組元素應ignore_above,超過ignore_above的字元串元素将不會被索引或存儲。目前測試的結果為:對于字元串字元長度超過ignore_above會存儲,但不索引(也就是無法根據該值去查詢)。
其測試效果如下:
public static void create_mapping_ignore_above() { // 建立映射
RestHighLevelClient client = EsClient.getClient();
try {
CreateIndexRequest request = new CreateIndexRequest("mapping_test_ignore_above2");
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("lies")
.field("type", "keyword") // 建立關鍵字段
.field("ignore_above", 10) // 設定長度不能超過10
.endObject()
.endObject()
.endObject();
// request.mapping("user", mapping_user);
request.mapping("_doc", mapping);
System.out.println(client.indices().create(request, RequestOptions.DEFAULT));
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
public static void index_mapping_ignore_above() { // 索引資料
RestHighLevelClient client = EsClient.getClient();
try {
IndexRequest request = new IndexRequest("mapping_test_ignore_above2", "_doc");
Map<String, Object> data = new HashMap<>();
data.put("lies", new String[] {"dingabcdwei","huangsw","wuyanfengamdule"});
request.source(data);
System.out.println(client.index(request, RequestOptions.DEFAULT));
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
public static void search_ignore_above() { // 查詢資料
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("mapping_test_ignore_above2");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(
// QueryBuilders.matchAllQuery() // @1
// QueryBuilders.termQuery("lies", "dingabcdwei") // @2
// QueryBuilders.termQuery("lies", "huangsw") // @3
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
代碼@1:首先查詢所有資料,其_souce字段的值為:"_source":{"lies":["dingabcdwei","huangsw","wuyanfengamdule"]},表名不管字元串的值是否大于ignore_above指定的值,都會存儲。
代碼@2:用超過ignore_above長度的值嘗試去搜尋,發現無法比對到記錄,表明并未加入到反向索引中。
代碼@3:用未超過ignore_above長度的值嘗試去搜尋,發現能比對到記錄。
注意:在es中,ignore_above的長度是字元的長度,而es其底層實作lucene是使用位元組進行計算的,故,如果要回報到lucnce,請注意關系。
13、ignore_malformed
試圖将錯誤的資料類型索引到字段中,預設情況下會抛出異常,并拒絕整個文檔。ignore_malformed參數,如果設定為真,允許錯誤被忽略。格式不正确的字段不建立索引,但是文檔中的其他字 段正常處理。可以建立索引時,設定index.mapping.ignore_malformed 配置項來定義索引級别的預設值,其優先級為 字段級、索引級。
14、index
定義字段是否索引,true:代表索引,false表示不索引(則無法通過該字段進行查詢),預設值為true。
-
index_options
控制文檔添加到反向索引的額外内容,其可選擇值如下:
- docs:文檔編号添加到反向索引。
- freqs:文檔編号與通路頻率。
- positions:文檔編号、通路頻率、詞位置(順序性),proximity 和phrase queries 需要用到該模式。
-
offsets:文檔編号,詞頻率,詞偏移量(開始和結束位置)和詞位置(序号),高亮顯示,需要設定為該模式。
預設情況下,被分析的字元串(analyzed string)字段使用positions,其他字段使用docs;
15、fields
fields允許對同一索引中同名的字段進行不同的設定,舉例說明:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"city": {
"type": "text", // @1
"fields": { // @2
"raw": {
"type": "keyword" // @3
}
}
}
}
}
}
}
@1:上述映射為city字段,定義類型為text,使用全文索引。
@2:為city定義多字段,city.raw,其類型用keyword。
主要就可以用user進行全文比對,也可以用user.raw進行聚合、排序等操作。另外一種比較常用的場合是對該字段使用不同的分詞器。
16、norms
字段的評分規範,存儲該規範資訊,會提高查詢時的評分計算效率。雖然規範對計分很有用,但它也需要大量磁盤(通常是索引中每個字段的每個文檔一個位元組的順序,甚至對于沒有這個特定字段的文檔也是如此)。從這裡也可以看出,norms适合為過濾或聚合的字段。
注意,可以通過put mapping api 将norms=true更新為norms=false,但無法從false更新到true。
17、null_value
将顯示的null值替換為新定義的額值。用如下示例做一個說明:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"status_code": {
"type": "keyword",
"null_value": "NULL" // @1
}
}
}
}
}
PUT my_index/_doc/1
{
"status_code": null // @2
}
PUT my_index/_doc/2
{
"status_code": [] // @3
}
GET my_index/_search
{
"query": {
"term": {
"status_code": "NULL" // @4
}
}
}
代碼@1:為status_code字段定義“NULL”為空值null;
代碼@2:該處,存儲在status_code為 null_value中定義的值,即"NULL"
代碼@3:空數組不包含顯式null,是以不會被null_value替換。
代碼@4:該處查詢,會查詢出文檔1。其查詢結果如下:
{
"took":4,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":1,
"max_score":0.2876821,
"hits":[
{
"_index":"mapping_test_null_value",
"_type":"_doc",
"_id":"RyjGEmcB-TTORxhqI2Zn",
"_score":0.2876821,
"_source":{
"status_code":null
}
}
]
}
}
null_value具有如下兩個特點:
null_value需要與字段的資料類型相同。例如,一個long類型的字段不能有字元串null_value。
null_value隻會索引中的值(反向索引),無法改變_souce字段的值。
18、position_increment_gap
針對多值字段,值與值之間的間隙。舉例說明:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"names": {
"type": "text",
"position_increment_gap": 0 // @1
// "position_increment_gap": 10 // @2
}
}
}
}
}
PUT my_index/_doc/1
{
"names": [ "John Abraham", "Lincoln Smith"]
}
names字段是個數組,也即ES中說的多值字段
當position_increment_gap=0時,es的預設使用标準分詞器,分成的詞根為:
position 0 : john
position 1 : abraham
position 2 : lincoln
position 3 : smith
當position_increment_gap = 10時,es使用預設分詞器,分成的詞根為:
position 0 : john
position 11 : lincoln 這是第二個詞,等于上一個詞的position + position_increment_gap。
position 12 : smith
針對如下下查詢:
GET my_index/_search
{
"query": {
"match_phrase": {
"names": "Abraham Lincoln"
}
}
}
針對position_increment_gap=0時,能比對上文檔,如果position_increment_gap=10,則無法比對到文檔,因為abraham與lincoln的位置相差10,如果要能比對到該文檔,需要在查詢時設定slop=10,該參數在前面的DSL查詢部分已詳細介紹過。
19、properties
為映射類型建立字段定義。
20、search_analyzer
通常,在索引時和搜尋時應用相同的分析器,以確定查詢中的術語與反向索引中的術語具有相同的格式,如果想要在搜尋時使用與存儲時不同的分詞器,則使用search_analyzer屬性指定,通常用于ES實作即時搜尋(edge_ngram)。
21、similarity
指定相似度算法,其可選值:
-
BM25
目前版本的預設值,使用BM25算法。
-
classic
使用TF/IDF算法,曾經是es,lucene的預設相似度算法。
-
boolean
一個簡單的布爾相似度,當不需要全文排序時使用,并且分數應該隻基于查詢條件是否比對。布爾相似度為術語提供了一個與它們的查詢boost相等的分數。
22、store
預設情況下,字段值被索引以使其可搜尋,但它們不存儲。這意味着可以查詢字段,但無法檢索原始字段值。通常這并不重要。字段值已經是_source字段的一部分,該字段預設存儲。如果您隻想檢索單個字段或幾個字段的值,而不是整個_source,那麼這可以通過字段過濾上下文(source filting context來實作,在某些情況下,存儲字段是有意義的。例如,如果您有一個包含标題、日期和非常大的内容字段的文檔,您可能隻想檢索标題和日期,而不需要從大型_source字段中提取這些字段,es還提供了另外一種提取部分字段的方法,stored_fields,stored_fields過濾,隻支援字段的store定義為ture,該部分内容已經在Elasticsearch Doc api時,_souce過濾部分詳細介紹過,這裡不過多介紹。
23、term_vector
term_vector包含分析過程産生的術語的資訊,包括:
- 術語清單。
- 每一項的位置(或順序)。
-
開始和結束字元偏移量。
term_vector可取值:
-
no
不存儲term_vector資訊,預設值。
-
yes
隻存儲字段中的值。
-
with_positions
存儲字段中的值與位置資訊。
-
with_offsets
存儲字段中的值、偏移量
-
with_positions_offsets
存儲字段中的值、位置、偏移量資訊。