1. 程式人生 > >Elasticsearch從入門到放棄:瞎說Mapping

Elasticsearch從入門到放棄:瞎說Mapping

前面我們聊了 Elasticsearch 的索引、搜尋和分詞器,今天再來聊另一個基礎內容—— Mapping。 Mapping 在 Elasticsearch 中的地位相當於關係型資料庫中的 schema,它可以用來定義索引中欄位的名字、定義欄位的資料型別,還可以用來做一些欄位的配置。從 Elasticsearch 7.0開始,Mapping 中不在乎需要定義 type 資訊了,具體原因可以看[官方的解釋](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html)。 ### 欄位的資料型別 我們剛剛提到 Mapping 中可以定義欄位的資料型別,這可能是 Mapping 最常用的功能了,所以我們先來看看 Elasticsearch 都支援哪些資料型別。 - 簡單型別:text、keyword、date、long、double、boolean、ip - 複雜型別:物件型別、巢狀型別 - 特殊型別:用於描述地理位置的 geo_point、geo_shape Elasticsearch 支援的資料型別遠不止這些,由於篇幅原因,這裡就不一一列舉了。我找幾個工作中常見的來介紹一下。 首先就是字串了,Elasticsearch 中的字串有 text 和 keyword 兩種。其中 text 型別的字串是可以被全文檢索的,它會被分詞器作用, ``` bash PUT my_index { "mappings": { "properties": { "full_name": { "type": "text" } } } } ``` 在設定欄位型別為 text 時,還可以利用一些引數對這個欄位進行更進一步的定製。 `index`:標記這個欄位是否能被搜尋,預設是 true `search_analyzer`:被搜尋時所使用的分詞器,預設使用 setting 中設定的分詞器 `fielddata`:欄位是否允許在記憶體中進行排序、聚合,預設是 false `meta`:關於欄位的一些元資料 像一些id、郵箱、域名這樣的欄位,我們就需要使用 keyword 型別了。因為 keyword 型別可以支援排序、聚合,並且只能支援精確查詢。 有些同學可能會把 ID 設定為數字型別,這也是沒問題的,數字型別和 keyword 各有各的好處,使用數字型別可以進行範圍查詢,而使用 keyword 型別則有更高的查詢效率。具體用哪種還要看使用場景。 日期型別在 Elasticsearch 中有三種表現形式 1. 可以格式化成日期型別的字串,如`"2020-07-26"`和`"2015/01/01 12:10:30"`這樣的 2. 毫秒級時間戳用 long 型別表示 3. 秒級時間戳用 integer 型別表示 在 Elasticsearch 內部,日期型別是以 long 型別的毫秒級時間戳儲存的,時區使用的是0時區。 我們可以自定義時間格式,預設使用的是`strict_date_optional_time||epoch_millis` **strict_date_optional_time_nanos**是通用的日期格式解析,至少要包含年份,如果要包含時間,則用`T`分隔,例如`yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ`或 `yyyy-MM-dd`。 如果想要同時支援多種日期格式,可以使用`format`欄位 ``` bash PUT my_index { "mappings": { "properties": { "date": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" } } } } ``` ### Mapping引數 剛才我們提到配置 Mapping 的日期格式的引數`format`,Mapping 還提供了很多其他的引數。 - 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 我們來介紹幾個常用的欄位。 #### fields 首先是`fields`,它可以使同一個欄位通過不同的方式實現不同的目的。 例如,我們可以對一個字串欄位設定為`text`型別,用於全文檢索,同時可以利用`fields`設定為`keyword`型別,用於排序和聚合。 ``` bash PUT my-index-000001 { "mappings": { "properties": { "city": { "type": "text", "fields": { "raw": { "type": "keyword" } } } } } } ``` 查詢時我們就可以使用`city`進行全文檢索,使用`city.raw`進行排序和聚合。 ``` bash GET my-index-000001/_search { "query": { "match": { "city": "york" } }, "sort": { "city.raw": "asc" }, "aggs": { "Cities": { "terms": { "field": "city.raw" } } } } ``` #### enabled 有些時候,我們只想把某個欄位作為資料儲存來使用,並不需要用來做搜尋,這時,我們就可以將這個欄位禁用掉,欄位被禁用以後,它所儲存的值也不受 mapping 指定的型別控制。 ``` bash PUT my-index-000001 { "mappings": { "properties": { "user_id": { "type": "keyword" }, "last_updated": { "type": "date" }, "session_data": { "type": "object", "enabled": false } } } } ``` 上面的例子中,我們禁用掉了 `session_data` 這個欄位,這時,你既可以往 `session_data` 欄位中儲存 JSON 格式的資料,也可以儲存非 JSON 格式的資料。 除了針對於單個欄位的禁用以外,我們還可以直接禁用掉整個 mapping。我們來重新建立一個index ``` bash PUT my-index-000002 { "mappings": { "enabled": false } } ``` 這時,文件所有的欄位都不會被索引,只是用來儲存。 需要注意的是,無論是具體欄位中還是整個 mapping 的 enabled 屬性都不可以被修改,因為一旦設定為 false,Elasticsearch 就不會對欄位進行索引了,也不會校驗資料的合法性,如果產生了髒資料以後再設定為 true,就會造成程式錯誤。 #### null_value null 在 Elasticsearch 中是不可以被索引或搜尋的,這裡我們所說的 null 並不是狹義上某種語言的 null,而是所有的空值。例如所有值都是 null 的陣列,總之,這裡的定義就是沒有值。 對於有需要搜尋空值的業務怎麼辦呢?Elasticsearch 為我們提供了 `null_value` 這個引數,它可以指定一個值,搜尋時使用這個值來替代空值。 舉個栗子 ``` bash PUT my-index-000001 { "mappings": { "properties": { "status_code": { "type": "keyword", "null_value": "NULL" } } } } ``` 我們給 `status_code` 欄位設定了 `null_value` 為 `"NULL"`。這裡需要注意, `null_value` 的型別必須與要查詢的資料型別相同,如果在這個例子中 `status_code` 的型別是long,那麼就不能把`null_value` 設定為 `"NULL"`。 #### dynamic 對於新增加的欄位: - dynamic 設定為 true 時,一旦有新增欄位的文件寫入,Mapping 也會被更新 - dynamic 設定為 false 時,Mapping 不會被更新,新增欄位無法被索引,但資訊會出現在 `_source` 中 - dynamic 設定為 strict 時,文件寫入失敗 對於已有的欄位,一旦已經有資料寫入,就不再支援修改欄位定義 ### Dynamic Mapping 我們在建立索引時,可以不用手動寫 Mappings, Elasticsearch 會幫我們自動識別出欄位的型別。我們稱之為 Dynamic Mapping。不過有時推算的可能不是很準確。 Elasticsearch 自動識別型別是基於 JSON 的。資料型別的對應關係如下(表格來自 elastic 官網) | **JSON data type** | **Elasticsearch data type** | | --------------------- | ------------------------------------------------------------ | | `null` | No field is added. | | `true` or `false` | [`boolean`](https://www.elastic.co/guide/en/elasticsearch/reference/current/boolean.html) field | | floating point number | [`float`](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html) field | | integer | [`long`](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html) field | | object | [`object`](https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html) field | | array | Depends on the first non-`null` value in the array. | | string | Either a [`date`](https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html) field (if the value passes [date detection](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#date-detection)), a [`double`](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html) or [`long`](https://www.elastic.co/guide/en/elasticsearch/reference/current/number.html) field (if the value passes [numeric detection](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html#numeric-detection)) or a [`text`](https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html) field, with a [`keyword`](https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html) sub-field. | Elasticsearch 支援的欄位對映的資料型別在這個[文件](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html)中,除了這些,其他的型別對映都需要顯示的指定了。 關於日期型別,預設是可以對映的,但是 Elasticsearch 只能識別幾種格式的日期`yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis`。如果關掉了 `date_detection` 開關,那麼就只能識別為字串了。 ``` bash PUT my-index-000001 { "mappings": { "date_detection": false } } ``` 當然,你也可以根據需要自己指定要識別的日期格式,只需要使用 `dynamic_date_formats` 引數即可。 ``` bash PUT my-index-000001 { "mappings": { "dynamic_date_formats": ["MM/dd/yyyy"] } } ``` Elasticsearch 還提供了一種把字串型的數字識別為數字的能力,它是由 `numeric_detection` 開關控制的。 ``` bash PUT my-index-000005 { "mappings": { "numeric_detection": true } } PUT my-index-000005/_doc/1 { "my_float": "1.0", "my_integer": "1" } ``` 在這個例子中,`my_float` 會被識別為 float 型別,而 `my_integer` 會被識別為 long 型別。 ### Dynamic template dynamic template 允許我們自定義 mapping ,並應用到具體索引上。dynamic template 的定義一般是這樣的 ``` bash "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` 就是指匹配到的欄位應該使用怎樣的 mapping。下面我們介紹幾種 match conditions #### match_mapping_type 我們先來看一個簡單的例子 ``` bash 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 } } } } } ] } } ``` 這裡我們有兩個模版,其一是使用 `integer` 型別來代替 `long` 型別,其二是將字串型別對映為 `keyword`。 #### match 和 unmatch 這兩個比較簡單,match 是指匹配到模式的欄位, unmatch 是表示不匹配的欄位。 ``` bash PUT my-index-000001 { "mappings": { "dynamic_templates": [ { "longs_as_strings": { "match_mapping_type": "string", "match": "long_*", "unmatch": "*_text", "mapping": { "type": "long" } } } ] } } ``` 在這個例子中,我們需要的是 `long_` 開頭的字串,不需要 `_text`結尾的字串欄位。 除了以上三種之外,其他的就是 `match_pattern` 用來進行正則匹配,`path_match` 和 `path_unmatch` 則是表示欄位所在路徑的是否匹配。 另外 dynamic template 還支援兩種變數替換,分別是 `{name}` 和 `{dynamic_type}`。其實 name 就是欄位名,dynamic_type 就是檢測出的欄位型別。 ### 總結 關於 Elasticsearch 的 mapping 我們就先聊這些,我認為 mapping 的配置是一個需要經驗的事情,當你處理的 case 越來越多之後,就能比較輕鬆的知道如何更好的配置 mapping 了。此外,mapping 的許多欄位和引數文中都沒有涉及,對於我而言,大部分都是用到了現查文件,不過也還是建議大家看一看文件,起碼遇到問題時能知道大概查詢文件的一個方向。這樣就會比身邊人強