一文搞懂 Elasticsearch 之 Mapping
阿新 • • 發佈:2020-03-18
這篇文章主要介紹 Mapping、Dynamic Mapping 以及 ElasticSearch 是如何自動判斷欄位的型別,同時介紹 Mapping 的相關引數設定。
首先來看下什麼是 Mapping:
## 什麼是 Mapping?
在[一篇文章帶你搞定 ElasticSearch 術語](https://www.tianheyu.top/archives/elasticsearch-term)中,我們講到了 Mapping 類似於資料庫中的表結構定義 `schema`,它有以下幾個作用:
- **定義索引中的欄位的名稱**
- **定義欄位的資料型別**,比如字串、數字、布林
- **欄位,倒排索引的相關配置**,比如設定某個欄位為不被索引、記錄 position 等
在 ES 早期版本,一個索引下是可以有多個 Type 的,從 7.0 開始,一個索引只有一個 Type,也可以說一個 Type 有一個 Mapping 定義。
在瞭解了什麼是 Mapping 之後,接下來對 Mapping 的設定做下介紹:
### Mapping 設定
```
PUT users
{
"mappings": {
"_doc": {
"dynamic": false
}
}
}
```
在建立一個索引的時候,可以對 `dynamic` 進行設定,可以設成 `false`、`true` 或者 `strict`。
![Dynamic Mappings 設定](https://img-blog.csdnimg.cn/20200316224959884.png)
比如一個新的文件,這個文件包含一個欄位,當 Dynamic 設定為 `true` 時,這個文件可以被索引進 ES,這個欄位也可以被索引,也就是這個欄位可以被搜尋,Mapping 也同時被更新;當 dynamic 被設定為 `false` 時候,存在新增欄位的資料寫入,該資料可以被索引,但是新增欄位被丟棄;當設定成 `strict` 模式時候,資料寫入直接出錯。
另外還有 `index` 引數,用來控制當前欄位是否被索引,預設為 `true`,如果設為 `false`,則該欄位不可被搜尋。
引數 `index_options` 用於控制倒排索引記錄的內容,有如下 4 種配置:
- doc:只記錄 `doc id`
- freqs:記錄 `doc id` 和 `term frequencies`
- positions:記錄 `doc id`、`term frequencies` 和 `term position`
- offsets:記錄 `doc id`、`term frequencies`、`term position` 和 `character offects`
另外,`text` 型別預設配置為 `positions`,其他型別預設為 `doc`,記錄內容越多,佔用儲存空間越大。
`null_value` 主要是當欄位遇到 `null` 值時的處理策略,預設為 `NULL`,即空值,此時 ES 會預設忽略該值,可以通過設定該值設定欄位的預設值,另外只有 KeyWord 型別支援設定 `null_value`。
`copy_to` 作用是將該欄位的值複製到目標欄位,實現類似 `_all` 的作用,它不會出現在 `_source` 中,只用來搜尋。
除了上述介紹的引數,還有許多引數,大家感興趣的可以在官方文件中進行檢視。
在學習了 Mapping 的設定之後,讓我們來看下欄位的資料型別有哪些吧!
## 欄位資料型別
ES 欄位型別類似於 MySQL 中的欄位型別,ES 欄位型別主要有:核心型別、複雜型別、地理型別以及特殊型別,具體的資料型別如下圖所示:
![欄位資料型別](https://img-blog.csdnimg.cn/20200317003814117.png)
### 核心型別
從圖中可以看出核心型別可以劃分為字串型別、數字型別、日期型別、布林型別、基於 BASE64 的二進位制型別、範圍型別。
#### 字串型別
其中,在 ES 7.x 有兩種字串型別:`text` 和 `keyword`,在 ES 5.x 之後 `string` 型別已經不再支援了。
`text` 型別適用於需要被全文檢索的欄位,例如新聞正文、郵件內容等比較長的文字,`text` 型別會被 Lucene 分詞器(Analyzer)處理為一個個詞項,並使用 Lucene 倒排索引儲存,**text 欄位不能被用於排序**,如果需要使用該型別的欄位只需要在定義對映時指定 JSON 中對應欄位的 `type` 為 `text`。
`keyword` 適合簡短、結構化字串,例如主機名、姓名、商品名稱等,**可以用於過濾、排序、聚合檢索,也可以用於精確查詢**。
#### 數字型別
數字型別分為 `long、integer、short、byte、double、float、half_float、scaled_float`。
數字型別的欄位在滿足需求的前提下應當儘量選擇範圍較小的資料型別,欄位長度越短,搜尋效率越高,對於浮點數,可以優先考慮使用 `scaled_float` 型別,該型別可以通過縮放因子來精確浮點數,例如 12.34 可以轉換為 1234 來儲存。
#### 日期型別
在 ES 中日期可以為以下形式:
- 格式化的日期字串,例如 2020-03-17 00:00、2020/03/17
- 時間戳(和 1970-01-01 00:00:00 UTC 的差值),單位毫秒或者秒
> 即使是格式化的日期字串,ES 底層依然採用的是時間戳的形式儲存。
#### 布林型別
JSON 文件中同樣存在布林型別,不過 JSON 字串型別也可以被 ES 轉換為布林型別儲存,前提是字串的取值為 `true` 或者 `false`,布林型別常用於檢索中的過濾條件。
#### 二進位制型別
二進位制型別 `binary` 接受 BASE64 編碼的字串,預設 `store` 屬性為 `false`,並且不可以被搜尋。
#### 範圍型別
範圍型別可以用來表達一個數據的區間,可以分為5種:`integer_range、float_range、long_range、double_range` 以及 `date_range`。
### 複雜型別
複合型別主要有物件型別(object)和巢狀型別(nested):
#### 物件型別
JSON 字串允許巢狀物件,一個文件可以巢狀多個、多層物件。可以通過物件型別來儲存二級文件,不過由於 Lucene 並沒有內部物件的概念,ES 會將原 JSON 文件扁平化,例如文件:
```
{
"name": {
"first": "wu",
"last": "px"
}
}
```
實際上 ES 會將其轉換為以下格式,並通過 Lucene 儲存,即使 `name` 是 `object` 型別:
```
{
"name.first": "wu",
"name.last": "px"
}
```
#### 巢狀型別
巢狀型別可以看成是一個特殊的物件型別,可以讓物件陣列獨立檢索,例如文件:
```
{
"group": "users",
"username": [
{ "first": "wu", "last": "px"},
{ "first": "hu", "last": "xy"},
{ "first": "wu", "last": "mx"}
]
}
```
`username` 欄位是一個 JSON 陣列,並且每個陣列物件都是一個 JSON 物件。如果將 `username` 設定為物件型別,那麼 ES 會將其轉換為:
```
{
"group": "users",
"username.first": ["wu", "hu", "wu"],
"username.last": ["px", "xy", "mx"]
}
```
可以看出轉換後的 JSON 文件中 `first` 和 `last` 的關聯丟失了,如果嘗試搜尋 `first` 為 `wu`,`last` 為 `xy` 的文件,那麼成功會檢索出上述文件,但是 `wu` 和 `xy` 在原 JSON 文件中並不屬於同一個 JSON 物件,應當是不匹配的,即檢索不出任何結果。
巢狀型別就是為了解決這種問題的,巢狀型別將陣列中的每個 JSON 物件作為獨立的隱藏文件來儲存,每個巢狀的物件都能夠獨立地被搜尋,所以上述案例中雖然表面上只有 1 個文件,但實際上是儲存了 4 個文件。
### 地理型別
地理型別欄位分為兩種:經緯度型別和地理區域型別:
#### 經緯度型別
經緯度型別欄位(geo_point)可以儲存經緯度相關資訊,通過地理型別的欄位,可以用來實現諸如查詢在指定地理區域內相關的文件、根據距離排序、根據地理位置修改評分規則等需求。
#### 地理區域型別
經緯度型別可以表達一個點,而 `geo_shape` 型別可以表達一塊地理區域,區域的形狀可以是任意多邊形,也可以是點、線、面、多點、多線、多面等幾何型別。
### 特殊型別
特殊型別包括 IP 型別、過濾器型別、Join 型別、別名型別等,在這裡簡單介紹下 IP 型別和 Join 型別,其他特殊型別可以檢視官方文件。
#### IP 型別
IP 型別的欄位可以用來儲存 IPv4 或者 IPv6 地址,如果需要儲存 IP 型別的欄位,需要手動定義對映:
```
{
"mappings": {
"properties": {
"my_ip": {
"type": "ip"
}
}
}
}
```
#### Join 型別
Join 型別是 ES 6.x 引入的型別,以取代淘汰的 `_parent` 元欄位,用來實現文件的一對一、一對多的關係,主要用來做父子查詢。
Join 型別的 Mapping 如下:
```
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
```
其中,`my_join_field` 為 Join 型別欄位的名稱;`relations` 指定關係:`question` 是 `answer` 的父類。
例如定義一個 ID 為 1 的父文件:
```
PUT my_join_index/1?refresh
{
"text": "This is a question",
"my_join_field": "question"
}
```
接下來定義一個子文件,該文件指定了父文件 ID 為 1:
```
PUT my_join_index/_doc/2?routing=1&refresh
{
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}
```
再瞭解完欄位資料型別後,再讓我們看下什麼是 Dynamic Mapping?
## 什麼是 Dynamic Mapping?
Dynamic Mapping 機制使我們不需要手動定義 Mapping,ES 會**自動根據文件資訊來判斷欄位合適的型別**,但是有時候也會推算的不對,比如地理位置資訊有可能會判斷為 `Text`,當型別如果設定不對時,會導致一些功能無法正常工作,比如 Range 查詢。
### 型別自動識別
ES 型別的自動識別是基於 JSON 的格式,如果輸入的是 JSON 是字串且格式為日期格式,ES 會自動設定成 `Date` 型別;當輸入的字串是數字的時候,ES 預設會當成字串來處理,可以通過設定來轉換成合適的型別;如果輸入的是 `Text` 欄位的時候,ES 會自動增加 `keyword` 子欄位,還有一些自動識別如下圖所示:
![型別自動識別](https://img-blog.csdnimg.cn/20200316222833869.png)
下面我們通過一個例子是看看是怎麼型別自動識別的,輸入如下請求,建立索引:
```
PUT /mapping_test/_doc/1
{
"uid": "123",
"username": "wupx",
"birth": "2020-03-16",
"married": false,
"age": 18,
"heigh": 180,
"tags": [
"java",
"boy"
],
"money": 999.9
}
```
然後使用 `GET /mapping_test/_mapping` 檢視,結果如下圖所示:
![](https://img-blog.csdnimg.cn/20200316231640595.png)
可以從結果中看出,ES 會根據文件資訊自動推算出合適的型別。
哦豁,萬一我想修改 Mapping 的欄位型別,能否更改呢?讓我們分以下兩種情況來探究下:
### 修改 Mapping 欄位型別?
如果是新增加的欄位,根據 Dynamic 的設定分為以下三種狀況:
- 當 Dynamic 設定為 `true` 時,一旦有新增欄位的文件寫入,Mapping 也同時被更新。
- 當 Dynamic 設定為 `false` 時,索引的 Mapping 是不會被更新的,新增欄位的資料無法被索引,也就是無法被搜尋,但是資訊會出現在 `_source` 中。
- 當 Dynamic 設定為 `strict` 時,文件寫入會失敗。
另外一種是欄位已經存在,這種情況下,ES 是不允許修改欄位的型別的,因為 ES 是根據 Lucene 實現的倒排索引,一旦生成後就不允許修改,如果希望改變欄位型別,必須使用 Reindex API 重建索引。
不能修改的原因是如果修改了欄位的資料型別,會導致已被索引的無法被搜尋,但是如果是增加新的欄位,就不會有這樣的影響。
# 總結
本文主要介紹了 Mapping 和 Dynamic Mapping,同時對欄位型別做了詳細介紹,也介紹了在 ES 中是如何對欄位型別做推算的,瞭解了 Mapping 的相關引數設定。
在公眾號【**武培軒**】回覆【**es**】獲取思維導圖以及原始碼。
> 參考文獻
>
> 《Elasticsearch技術解析與實戰》
>
> Elastic Stack從入門到實踐
>
> Elasticsearch核心技術與實戰
>
> https://www.elastic.co/guide/en/elasticsearch/reference/7.1/mapp