Elasticsearch從入門到放棄:瞎說Mapping
阿新 • • 發佈:2020-08-05
前面我們聊了 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 的許多欄位和引數文中都沒有涉及,對於我而言,大部分都是用到了現查文件,不過也還是建議大家看一看文件,起碼遇到問題時能知道大概查詢文件的一個方向。這樣就會比身邊人強