Elasticsearch從入門(mén)到放棄:瞎說(shuō)Mapping
前面我們聊了 Elasticsearch 的索引、搜索和分詞器,今天再來(lái)聊另一個(gè)基礎(chǔ)內(nèi)容—— Mapping。
Mapping 在 Elasticsearch 中的地位相當(dāng)于關(guān)系型數(shù)據(jù)庫(kù)中的 schema,它可以用來(lái)定義索引中字段的名字、定義字段的數(shù)據(jù)類型,還可以用來(lái)做一些字段的配置。從 Elasticsearch 7.0開(kāi)始,Mapping 中不在乎需要定義 type 信息了,具體原因可以看官方的解釋。
字段的數(shù)據(jù)類型
我們剛剛提到 Mapping 中可以定義字段的數(shù)據(jù)類型,這可能是 Mapping 最常用的功能了,所以我們先來(lái)看看 Elasticsearch 都支持哪些數(shù)據(jù)類型。
簡(jiǎn)單類型:text、keyword、date、long、double、boolean、ip 復(fù)雜類型:對(duì)象類型、嵌套類型 特殊類型:用于描述地理位置的 geo_point、geo_shape
Elasticsearch 支持的數(shù)據(jù)類型遠(yuǎn)不止這些,由于篇幅原因,這里就不一一列舉了。我找?guī)讉€(gè)工作中常見(jiàn)的來(lái)介紹一下。
首先就是字符串了,Elasticsearch 中的字符串有 text 和 keyword 兩種。其中 text 類型的字符串是可以被全文檢索的,它會(huì)被分詞器作用,
PUT?my_index
{
??"mappings":?{
????"properties":?{
??????"full_name":?{
????????"type":??"text"
??????}
????}
??}
}
在設(shè)置字段類型為 text 時(shí),還可以利用一些參數(shù)對(duì)這個(gè)字段進(jìn)行更進(jìn)一步的定制。
index:標(biāo)記這個(gè)字段是否能被搜索,默認(rèn)是 true
search_analyzer:被搜索時(shí)所使用的分詞器,默認(rèn)使用 setting 中設(shè)置的分詞器
fielddata:字段是否允許在內(nèi)存中進(jìn)行排序、聚合,默認(rèn)是 false
meta:關(guān)于字段的一些元數(shù)據(jù)
像一些id、郵箱、域名這樣的字段,我們就需要使用 keyword 類型了。因?yàn)?keyword 類型可以支持排序、聚合,并且只能支持精確查詢。
有些同學(xué)可能會(huì)把 ID 設(shè)置為數(shù)字類型,這也是沒(méi)問(wèn)題的,數(shù)字類型和 keyword 各有各的好處,使用數(shù)字類型可以進(jìn)行范圍查找,而使用 keyword 類型則有更高的查詢效率。具體用哪種還要看使用場(chǎng)景。
日期類型在 Elasticsearch 中有三種表現(xiàn)形式
可以格式化成日期類型的字符串,如 "2020-07-26"和"2015/01/01 12:10:30"這樣的毫秒級(jí)時(shí)間戳用 long 類型表示 秒級(jí)時(shí)間戳用 integer 類型表示
在 Elasticsearch 內(nèi)部,日期類型是以 long 類型的毫秒級(jí)時(shí)間戳存儲(chǔ)的,時(shí)區(qū)使用的是0時(shí)區(qū)。
我們可以自定義時(shí)間格式,默認(rèn)使用的是strict_date_optional_time||epoch_millis
「strict_date_optional_time_nanos」是通用的日期格式解析,至少要包含年份,如果要包含時(shí)間,則用T分隔,例如yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ或 yyyy-MM-dd。
如果想要同時(shí)支持多種日期格式,可以使用format字段
PUT?my_index
{
??"mappings":?{
????"properties":?{
??????"date":?{
????????"type":???"date",
????????"format":?"yyyy-MM-dd?HH:mm:ss||yyyy-MM-dd||epoch_millis"
??????}
????}
??}
}
Mapping參數(shù)
剛才我們提到配置 Mapping 的日期格式的參數(shù)format,Mapping 還提供了很多其他的參數(shù)。
analyzer boost coerce copy_to doc_values dynamic eager_global_ordinals enabled fielddata fields format ignore_above ignore_malformed index_options index_phrases index_prefixes index meta normalizer norms null_value position_increment_gap properties search_analyzer similarity store term_vector
我們來(lái)介紹幾個(gè)常用的字段。
fields
首先是fields,它可以使同一個(gè)字段通過(guò)不同的方式實(shí)現(xiàn)不同的目的。
例如,我們可以對(duì)一個(gè)字符串字段設(shè)置為text類型,用于全文檢索,同時(shí)可以利用fields設(shè)置為keyword類型,用于排序和聚合。
PUT?my-index-000001
{
??"mappings":?{
????"properties":?{
??????"city":?{
????????"type":?"text",
????????"fields":?{
??????????"raw":?{
????????????"type":??"keyword"
??????????}
????????}
??????}
????}
??}
}
查詢時(shí)我們就可以使用city進(jìn)行全文檢索,使用city.raw進(jìn)行排序和聚合。
GET?my-index-000001/_search
{
??"query":?{
????"match":?{
??????"city":?"york"?
????}
??},
??"sort":?{
????"city.raw":?"asc"?
??},
??"aggs":?{
????"Cities":?{
??????"terms":?{
????????"field":?"city.raw"?
??????}
????}
??}
}
enabled
有些時(shí)候,我們只想把某個(gè)字段作為數(shù)據(jù)存儲(chǔ)來(lái)使用,并不需要用來(lái)做搜索,這時(shí),我們就可以將這個(gè)字段禁用掉,字段被禁用以后,它所保存的值也不受 mapping 指定的類型控制。
PUT?my-index-000001
{
??"mappings":?{
????"properties":?{
??????"user_id":?{
????????"type":??"keyword"
??????},
??????"last_updated":?{
????????"type":?"date"
??????},
??????"session_data":?{?
????????"type":?"object",
????????"enabled":?false
??????}
????}
??}
}
上面的例子中,我們禁用掉了 session_data 這個(gè)字段,這時(shí),你既可以往 session_data 字段中存儲(chǔ) JSON 格式的數(shù)據(jù),也可以存儲(chǔ)非 JSON 格式的數(shù)據(jù)。
除了針對(duì)于單個(gè)字段的禁用以外,我們還可以直接禁用掉整個(gè) mapping。我們來(lái)重新創(chuàng)建一個(gè)index
PUT?my-index-000002
{
??"mappings":?{
????"enabled":?false?
??}
}
這時(shí),文檔所有的字段都不會(huì)被索引,只是用來(lái)存儲(chǔ)。
需要注意的是,無(wú)論是具體字段中還是整個(gè) mapping 的 enabled 屬性都不可以被修改,因?yàn)橐坏┰O(shè)置為 false,Elasticsearch 就不會(huì)對(duì)字段進(jìn)行索引了,也不會(huì)校驗(yàn)數(shù)據(jù)的合法性,如果產(chǎn)生了臟數(shù)據(jù)以后再設(shè)置為 true,就會(huì)造成程序錯(cuò)誤。
null_value
null 在 Elasticsearch 中是不可以被索引或搜索的,這里我們所說(shuō)的 null 并不是狹義上某種語(yǔ)言的 null,而是所有的空值。例如所有值都是 null 的數(shù)組,總之,這里的定義就是沒(méi)有值。
對(duì)于有需要搜索空值的業(yè)務(wù)怎么辦呢?Elasticsearch 為我們提供了 null_value 這個(gè)參數(shù),它可以指定一個(gè)值,搜索時(shí)使用這個(gè)值來(lái)替代空值。
舉個(gè)栗子
PUT?my-index-000001
{
??"mappings":?{
????"properties":?{
??????"status_code":?{
????????"type":???????"keyword",
????????"null_value":?"NULL"?
??????}
????}
??}
}
我們給 status_code 字段設(shè)置了 null_value 為 "NULL"。這里需要注意, null_value 的類型必須與要查找的數(shù)據(jù)類型相同,如果在這個(gè)例子中 status_code 的類型是long,那么就不能把null_value 設(shè)置為 "NULL"。
dynamic
對(duì)于新增加的字段:
dynamic 設(shè)置為 true 時(shí),一旦有新增字段的文檔寫(xiě)入,Mapping 也會(huì)被更新 dynamic 設(shè)置為 false 時(shí),Mapping 不會(huì)被更新,新增字段無(wú)法被索引,但信息會(huì)出現(xiàn)在 _source中dynamic 設(shè)置為 strict 時(shí),文檔寫(xiě)入失敗
對(duì)于已有的字段,一旦已經(jīng)有數(shù)據(jù)寫(xiě)入,就不再支持修改字段定義
Dynamic Mapping
我們?cè)趧?chuàng)建索引時(shí),可以不用手動(dòng)寫(xiě) Mappings, Elasticsearch 會(huì)幫我們自動(dòng)識(shí)別出字段的類型。我們稱之為 Dynamic Mapping。不過(guò)有時(shí)推算的可能不是很準(zhǔn)確。
Elasticsearch 自動(dòng)識(shí)別類型是基于 JSON 的。數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系如下(表格來(lái)自 elastic 官網(wǎng))
| 「JSON data type」 | 「Elasticsearch data type」 |
|---|---|
null | No field is added. |
true or false | boolean field |
| floating point number | float field |
| integer | long field |
| object | object field |
| array | Depends on the first non-null value in the array. |
| string | Either a date field (if the value passes date detection), a double or long field (if the value passes numeric detection) or a text field, with a keyword sub-field. |
Elasticsearch 支持的字段映射的數(shù)據(jù)類型在這個(gè)文檔中,除了這些,其他的類型映射都需要顯示的指定了。
關(guān)于日期類型,默認(rèn)是可以映射的,但是 Elasticsearch 只能識(shí)別幾種格式的日期yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis。如果關(guān)掉了 date_detection 開(kāi)關(guān),那么就只能識(shí)別為字符串了。
PUT?my-index-000001
{
??"mappings":?{
????"date_detection":?false
??}
}
當(dāng)然,你也可以根據(jù)需要自己指定要識(shí)別的日期格式,只需要使用 dynamic_date_formats 參數(shù)即可。
PUT?my-index-000001
{
??"mappings":?{
????"dynamic_date_formats":?["MM/dd/yyyy"]
??}
}
Elasticsearch 還提供了一種把字符串型的數(shù)字識(shí)別為數(shù)字的能力,它是由 numeric_detection 開(kāi)關(guān)控制的。
PUT?my-index-000005
{
??"mappings":?{
????"numeric_detection":?true
??}
}
PUT?my-index-000005/_doc/1
{
??"my_float":???"1.0",?
??"my_integer":?"1"?
}
在這個(gè)例子中,my_float 會(huì)被識(shí)別為 float 類型,而 my_integer 會(huì)被識(shí)別為 long 類型。
Dynamic template
dynamic template 允許我們自定義 mapping ,并應(yīng)用到具體索引上。dynamic template 的定義一般是這樣的
??"dynamic_templates":?[
????{
??????"my_template_name":?{?
????????...??match?conditions?...?
????????"mapping":?{?...?}?
??????}
????},
????...
??]
my_template_name 可以是任意字符串。
match conditions 包括match_mapping_type, match, match_pattern, unmatch, path_match, path_unmatch 這幾種。
mapping 就是指匹配到的字段應(yīng)該使用怎樣的 mapping。下面我們介紹幾種 match conditions
match_mapping_type
我們先來(lái)看一個(gè)簡(jiǎn)單的例子
PUT?my-index-000001
{
??"mappings":?{
????"dynamic_templates":?[
??????{
????????"integers":?{
??????????"match_mapping_type":?"long",
??????????"mapping":?{
????????????"type":?"integer"
??????????}
????????}
??????},
??????{
????????"strings":?{
??????????"match_mapping_type":?"string",
??????????"mapping":?{
????????????"type":?"text",
????????????"fields":?{
??????????????"raw":?{
????????????????"type":??"keyword",
????????????????"ignore_above":?256
??????????????}
????????????}
??????????}
????????}
??????}
????]
??}
}
這里我們有兩個(gè)模版,其一是使用 integer 類型來(lái)代替 long 類型,其二是將字符串類型映射為 keyword。
match 和 unmatch
這兩個(gè)比較簡(jiǎn)單,match 是指匹配到模式的字段, unmatch 是表示不匹配的字段。
PUT?my-index-000001
{
??"mappings":?{
????"dynamic_templates":?[
??????{
????????"longs_as_strings":?{
??????????"match_mapping_type":?"string",
??????????"match":???"long_*",
??????????"unmatch":?"*_text",
??????????"mapping":?{
????????????"type":?"long"
??????????}
????????}
??????}
????]
??}
}
在這個(gè)例子中,我們需要的是 long_ 開(kāi)頭的字符串,不需要 _text結(jié)尾的字符串字段。
除了以上三種之外,其他的就是 match_pattern 用來(lái)進(jìn)行正則匹配,path_match 和 path_unmatch 則是表示字段所在路徑的是否匹配。
另外 dynamic template 還支持兩種變量替換,分別是 {name} 和 {dynamic_type}。其實(shí) name 就是字段名,dynamic_type 就是檢測(cè)出的字段類型。
總結(jié)
關(guān)于 Elasticsearch 的 mapping 我們就先聊這些,我認(rèn)為 mapping 的配置是一個(gè)需要經(jīng)驗(yàn)的事情,當(dāng)你處理的 case 越來(lái)越多之后,就能比較輕松的知道如何更好的配置 mapping 了。此外,mapping 的許多字段和參數(shù)文中都沒(méi)有涉及,對(duì)于我而言,大部分都是用到了現(xiàn)查文檔,不過(guò)也還是建議大家看一看文檔,起碼遇到問(wèn)題時(shí)能知道大概查找文檔的一個(gè)方向。這樣就會(huì)比身邊人強(qiáng)不少。
