大概閱讀10分鍾
本教程是系列教程,對於初學者可以對 ES 有一個整體認識和實踐實戰。
還沒開始的同學,建議先讀一下系列攻略目錄:Springboot2.x整合ElasticSearch7.x實戰目錄
本篇幅是繼上一篇 Springboot2.x整合ElasticSearch7.x實戰(二) ,適合初學 Elasticsearch 的小白,可以跟着整個教程做一個練習。
第五章 Mapping詳解
Mapping 是整個 ES 搜索引擎中最重要的一部分之一,學會構建一個好的索引,可以讓我們的搜索引擎更高效,更節省資源。
什么是 Mapping?
Mapping 是Elasticsearch 中一種術語, Mapping 類似於數據庫中的表結構定義 schema,它有以下幾個作用:
1. 定義索引中的字段的名稱
2. 定義字段的數據類型,比如字符串、數字、布爾
3. 字段,倒排索引的相關配置,比如設置某個字段為不被索引、記錄 position(位置) 等
在 ES 早期版本,一個索引下是可以有多個 Type ,從 7.0 開始,一個索引只有一個 Type,也可以說一個 Type 有一個 Mapping 定義。
了解了什么是 Mapping 后,接下來對 Mapping 的設置坐下介紹:
Maping設置
dynamic (動態Mapping)
官網參考:https://www.elastic.co/guide/en/elasticsearch/reference/7.1/mapping.html
PUT users
{
"mappings": {
"_doc": {
"dynamic": false
}
}
}
在創建一個索引的時候,可以對 dynamic 進行設置,可以設成 false、true 或者 strict。
比如一個新的文檔,這個文檔包含一個字段,當 Dynamic
設置為 true 時,這個文檔可以被索引進 ES,這個字段也可以被索引,也就是這個字段可以被搜索,Mapping 也同時被更新;當 dynamic 被設置為 false 時候,存在新增字段的數據寫入,該數據可以被索引,但是新增字段被丟棄;當設置成 strict 模式時候,數據寫入直接出錯。
index
另外還有 index
參數,用來控制當前字段是否被索引,默認為 true,如果設為 false(有些業務場景,某些字段不希望被搜索到),則該字段不可被搜索。
# index屬性控制 字段是否可以被索引
PUT user_test
{
"mappings": {
"properties": {
"firstName":{
"type": "text"
},
"lastName":{
"type": "text"
},
"mobile" :{
"type": "text",
"index": false
}
}
}
}
index_options
參數 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_value 主要是當字段遇到 null 值時的處理策略,默認為 NULL,即空值,此時 ES 會默認忽略該值,可以通過設定該值設定字段的默認值,另外只有 KeyWord 類型支持設定 null_value。
- 示例
# 設定Null_value
DELETE users
PUT users
{
"mappings" : {
"properties" : {
"firstName" : {
"type" : "text"
},
"lastName" : {
"type" : "text"
},
"mobile" : {
"type" : "keyword",
"null_value": "NULL"
}
}
}
}
PUT users/_doc/1
{
"firstName":"Zhang",
"lastName": "Fubing",
"mobile": null
}
PUT users/_doc/2
{
"firstName":"Zhang",
"lastName": "Fubing2"
}
# 查看結果,有且僅有_id為2的記錄
GET users/_search
{
"query": {
"match": {
"mobile":"NULL"
}
}
}
_all
這個屬性現在使用很少,不做深入講解
參考官網:https://www.elastic.co/guide/cn/elasticsearch/guide/current/root-object.html
copy_to
這個屬性用於將當前字段拷貝到指定字段。
- _all在7.x版本已經被copy_to所代替
- 可用於滿足特定場景
- copy_to將字段數值拷貝到目標字段,實現類似_all的作用
- copy_to的目標字段不出現在_source中
DELETE users
PUT users
{
"mappings": {
"properties": {
"firstName":{
"type": "text",
"copy_to": "fullName"
},
"lastName":{
"type": "text",
"copy_to": "fullName"
}
}
}
}
PUT users/_doc/1
{
"firstName":"Li",
"lastName": "Sunke"
}
//沒有新建字段
GET users/_doc/1
{
"_index" : "users",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"firstName" : "Li",
"lastName" : "Sunke"
}
}
GET users/_search?q=fullName:(Li sunke)
以前的用法是:
curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
"mappings": {
"my_type": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name" # 1
},
"last_name": {
"type": "text",
"copy_to": "full_name" # 2
},
"full_name": {
"type": "text"
}
}
}
}
}
'
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d'
{
"first_name": "John",
"last_name": "Smith"
}
'
curl -XGET 'localhost:9200/my_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
"query": {
"match": {
"full_name": { # 3
"query": "John Smith",
"operator": "and"
}
}
}
}
'
- first_name(名字)和 last_name(姓氏)字段復制到full_name 字段;
- first_name(名字)和 last_name(姓氏)字段仍然可以分別查詢;
- full_name 可以通過 first_name(名字)和 last_name(姓氏)來查詢;
一些要點:
- 復制的是字段值,而不是 term(詞條)(由分析過程產生).
- _source 字段不會被修改來顯示復制的值.
- 相同的值可以復制到多個字段,通過 "copy_to": [ "field_1", "field_2" ] 來操作.
分詞器analyzer和arch_analyzer
PUT /my_index
{
"mappings": {
"properties": {
"text": {
"type": "text",
"fields": {
"english": {
"type": "text",
"analyzer": "english",
"search_analyzer": "english"
}
}
}
}
}
}
#使用_analyze 測試分詞器
GET my_index/_analyze
{
"field": "text",
"text": "The quick Brown Foxes."
}
GET my_index/_analyze
{
"field": "text.english",
"text": "The quick Brown Foxes."
}
構建Mapping方式
我們知道 Mapping 是可以通過我們插入的文檔自動生成索引,但是可能還是有一些問題。例如:生成的字段類型不正確,字段的附加屬性不滿足我們的需求。這是我們可以通過顯式Mapping的方式來解決。倆種方法:
- 參考官網api,純手寫
- 構建臨時索引;寫入一些樣本數據;通過Maping API 查詢臨時文件的動態Mapping 定義;修改后、再使用此配置創建索引;刪除臨時索引;
推薦第二種,不容易出錯,效率高。
類型自動識別
ES 類型的自動識別是基於 JSON 的格式,如果輸入的是 JSON 是字符串且格式為日期格式,ES 會自動設置成 Date 類型;當輸入的字符串是數字的時候,ES 默認會當成字符串來處理,可以通過設置來轉換成合適的類型;如果輸入的是 Text 字段的時候,ES 會自動增加 keyword 子字段,還有一些自動識別如下圖所示:
- Demo:
# 寫入文檔,查看 Mapping
PUT mapping_test/_doc/1
{
"firstName": "Chan", -- Text
"lastName": "Jackie", -- Text
"loginDate": "2018-07-24T10:29:48.103Z" -- Date
}
# Dynamic Mapping,推斷字段的類型
PUT mapping_test/_doc/1
{
"uid": "123", -- Text
"isVip": false, -- Boolean
"isAdmin": "true", -- Text
"age": 19, -- Long
"heigh": 180 -- Long
}
# 查看 Dynamic Mapping
GET mapping_test/_mapping
映射參數
mappings 中field定義選擇:
"field": {
"type": "text", //文本類型
"index": "false"// ,設置成false,字段將不會被索引
"analyzer":"ik"//指定分詞器
"boost":1.23//字段級別的分數加權
"doc_values":false//對not_analyzed字段,默認都是開啟,analyzed字段不能使用,對排序和聚合能提升較大性能,節約內存,如果您確定不需要對字段進行排序或聚合,或者從script訪問字段值,則可以禁用doc值以節省磁盤空間:
"fielddata":{"loading" : "eager" }//Elasticsearch 加載內存 fielddata 的默認行為是 延遲 加載 。 當 Elasticsearch 第一次查詢某個字段時,它將會完整加載這個字段所有 Segment 中的倒排索引到內存中,以便於以后的查詢能夠獲取更好的性能。
"fields":{"keyword": {"type": "keyword","ignore_above": 256}} //可以對一個字段提供多種索引模式,同一個字段的值,一個分詞,一個不分詞
"ignore_above":100 //超過100個字符的文本,將會被忽略,不被索引
"include_in_all":ture//設置是否此字段包含在_all字段中,默認是true,除非index設置成no選項
"index_options":"docs"//4個可選參數docs(索引文檔號) ,freqs(文檔號+詞頻),positions(文檔號+詞頻+位置,通常用來距離查詢),offsets(文檔號+詞頻+位置+偏移量,通常被使用在高亮字段)分詞字段默認是position,其他的默認是docs
"norms":{"enable":true,"loading":"lazy"}//分詞字段默認配置,不分詞字段:默認{"enable":false},存儲長度因子和索引時boost,建議對需要參與評分字段使用 ,會額外增加內存消耗量
"null_value":"NULL"//設置一些缺失字段的初始化值,只有string可以使用,分詞字段的null值也會被分詞
"position_increament_gap":0//影響距離查詢或近似查詢,可以設置在多值字段的數據上火分詞字段上,查詢時可指定slop間隔,默認值是100
"store":false//是否單獨設置此字段的是否存儲而從_source字段中分離,默認是false,只能搜索,不能獲取值
"search_analyzer":"ik"//設置搜索時的分詞器,默認跟ananlyzer是一致的,比如index時用standard+ngram,搜索時用standard用來完成自動提示功能
"similarity":"BM25"//默認是TF/IDF算法,指定一個字段評分策略,僅僅對字符串型和分詞類型有效
"term_vector":"no"//默認不存儲向量信息,支持參數yes(term存儲),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 對快速高亮fast vector highlighter能提升性能,但開啟又會加大索引體積,不適合大數據量用
}
總結一下:
- 與域數據格式及約束相關的參數:normalizer,format,ignore_above,ignore_malformed,coerce
- 與索引相關的參數:index,dynamic,enabled
- 與存儲策略相關的參數:store, fielddata,doc_values
- 分析器相關參數:analyzer,search_analyzer
- 其它參數:boost,copy_to,null_value
對於這些參數的描述主要基於筆者的理解,可能有不准確之處。實際上這些參數與ES的實現機制(如存儲結構,索引結構密切有關),只能在實際應用中去慢慢體會。
字段數據類型
ES 字段類型類似於 MySQL 中的字段類型,ES 字段類型主要有:核心類型、復雜類型、地理類型以及特殊類型,具體的數據類型如下圖所示:
核心類型
從圖中可以看出核心類型可以划分為字符串類型、數字類型、日期類型、布爾類型、基於 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"
}
}
join參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html