elasticsearch(6) 對映和分析
類似關係型資料庫中每個欄位都有對應的資料型別,例如nvarchar、int、date等等,elasticsearch也會將文件中的欄位對映成對應的資料型別,這一對映可以使ES自動生成的,也是可以由我們自定義的。不同的對映關係是會影響到我們的搜尋查詢功能。
GET /_search?q=2014 # 12 results GET /_search?q=2014-09-15 # 12 results ! GET /_search?q=date:2014-09-15 # 1 result GET /_search?q=date:2014 # 0 results !
分析上面的查詢語句,我們可以發現一個很奇怪的現象。為什麼通過q=2014可以查到12條結果,但通過date欄位查詢年份q=date:2014卻沒有結果呢?我們推測就是因為不同的欄位對映的型別不同而導致的。
- 使用分析工具
通過_mapping api可以分析型別的對映
GET /gb/_mapping/tweet
{ "gb": { "mappings": { "tweet": { "properties": { "date": { "type": "date", "format": "strict_date_optional_time||epoch_millis" }, "name": { "type": "string" }, "tweet": { "type": "string" }, "user_id": { "type": "long" } } } } } }
我們可以看到date欄位是date型別的,而q=2014查詢的是_all欄位,我們知道這個欄位是String型別的。我們可能會猜測 date
欄位和 string
欄位 索引方式不同,所以搜尋結果也不一樣。
但實際上雖然不同的資料型別的索引方式會有所不同,但最大的區別是一個是代表精確值的欄位(包括String型別),另一個是代表全文的欄位。這個區別非常重要,這正是ES和其他關係型資料庫最大的不同。ES會將全文域的所有詞條形成倒排索引,這也是ES搜尋的基礎。
- 倒排索引
elsaticsearch使用一種稱為倒排索引的結構,它適用於快速的全文搜尋。一個倒排索引由文件中所有不重複詞的列表構成,對於其中每個詞,有一個包含它的文件列表。
例如,假設我們有兩個文件,每個文件的 content
域包含如下內容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
為了建立倒排索引,我們首先將每個文件的 content
域拆分成單獨的 詞(我們稱它為 詞條
或 tokens
),建立一個包含所有不重複詞條的排序列表,然後列出每個詞條出現在哪個文件。結果如下所示:
Term Doc_1 Doc_2 ------------------------- Quick | | X The | X | brown | X | X dog | X | dogs | | X fox | X | foxes | | X in | | X jumped | X | lazy | X | X leap | | X over | X | X quick | X | summer | | X the | X | ------------------------
如果我們想要搜尋 quick brown
,我們只需要查詢包含每個詞條的文件:
Term Doc_1 Doc_2 ------------------------- brown | X | X quick | X | ------------------------ Total | 2 | 1
由上圖可得知,匹配的文件有兩個,但是第一個文件要比第二個匹配度更好。但這也引發出了一些問題。
對於一個搜尋操作來說:
1、使用者可能不關心詞條的大小寫,Quick和quick可能是一樣的
2、由於詞法的變化,例如單複數的變化,對於使用者來說可能也是不關心的,例如dog和dogs
3、同義詞,一些同義詞儘管不同,但實際上表達的含義是相同的,我們希望能夠匹配出來。
解決方案:
對於輸入和輸出事先做預處理,也就是在倒排索引前對文件統一處理,例如把Quick統一成quick,把dogs統一成dog。搜尋時,對搜尋的內容也做統一處理,例如Quick dogs轉化成quick dog。這樣通過倒排索引就可以解決上面幾個問題,優化搜尋。這個過程就是分析。
- 分析和分析器
所謂分析器實際上是把三個功能封裝在了一個包裡
1、字元過濾器
首先,字串按順序通過每個字元過濾器。他們的任務是在分詞前整理字串。一個字元過濾器可以用來去掉HTML,或者將 &
轉化成 `and`。
2、分詞器
字串被分詞器分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文字拆分成詞條。
3、Token過濾器
詞條按順序通過每個Token過濾器。這個過程可能會改變詞條(例如,小寫化 Quick
),刪除詞條(例如, 像 a,and,the
等無用詞),或者增加詞條(例如,像 jump
和 leap
這種同義詞)
- 內建分析器
ES附帶了可以直接使用的預包裝的分析器
示例文字
"Set the shape to semi-transparent by calling set_trans(5)"
1、標準分析器
標準分析器是Elasticsearch預設使用的分析器。它是分析各種語言文字最常用的選擇。它根據Unicode聯盟定義的單詞邊界劃分文字。刪除絕大部分標點。最後,將詞條小寫。
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
2、簡單分析器
set, the, shape, to, semi, transparent, by, calling, set, trans
3、空格分析器
空格分析器在任何不適字母的地方分割文字,將詞條小寫。
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
4、語言分析器
特定語言分析器可用於很多語言。它們可以考慮指定語言的特點。例如, 英語
分析器附帶了一組英語無用詞(常用單詞,例如 and
或者the
,它們對相關性沒有多少影響),它們會被刪除。 由於理解英語語法的規則,這個分詞器可以提取英語單詞的詞幹。
set, shape, semi, transpar, call, set_tran, 5
這裡set_trans變為了set_tran,calling變為了call
- 測試分析器
有些時候很難理解分詞的過程和實際被儲存到索引中的詞條,你可以使用 analyze
API 來看文字是如何被分析的。在訊息體裡,指定分析器和要分析的文字:
GET 127.0.0.1:9200/_analyze
{ "analyzer": "standard", "text": "our Fingertips owns the power of changing the world" }
響應
{ "tokens": [ { "token": "our", "start_offset": 0, "end_offset": 3, "type": "<ALPHANUM>", "position": 0 }, { "token": "fingertips", "start_offset": 4, "end_offset": 14, "type": "<ALPHANUM>", "position": 1 }, { "token": "owns", "start_offset": 15, "end_offset": 19, "type": "<ALPHANUM>", "position": 2 }, { "token": "the", "start_offset": 20, "end_offset": 23, "type": "<ALPHANUM>", "position": 3 }, { "token": "power", "start_offset": 24, "end_offset": 29, "type": "<ALPHANUM>", "position": 4 }, { "token": "of_changing", "start_offset": 30, "end_offset": 41, "type": "<ALPHANUM>", "position": 5 }, { "token": "the", "start_offset": 42, "end_offset": 45, "type": "<ALPHANUM>", "position": 6 }, { "token": "world", "start_offset": 46, "end_offset": 51, "type": "<ALPHANUM>", "position": 7 } ] }
從中我們可以看到分析器是怎麼幫我們分詞的。token
是實際儲存到索引中的詞條。 position
指明詞條在原始文字中出現的位置。 start_offset
和end_offset
指明字元在原始字串中的位置。
- 核心簡單域型別
elasticsearch支援一下簡單域型別
1、字串:string
2、整數:byte,short,integer,long
3、浮點型:float,double
4、布林型:boolean
5、日期:date
當你索引一個包含新域的文件(之前未曾出現), Elasticsearch 會使用動態對映,通過JSON中基本資料型別,嘗試猜測域型別,使用如下規則:
JSON type |
域 type |
布林型: |
|
整數: |
|
浮點數: |
|
字串,有效日期: |
|
字串: |
|
這意味著如果通過“123”索引一個數字,那麼它會被對映成為string,但如果這個域已經是long,那麼ES會嘗試將這個字串轉化為long,如果無法轉化,則丟擲一個異常。
- 自定義域對映
自定義對映可以做到
1、全文字串域和精確值字串域的區別
2、使用特定的語言分析器
3、優化域以適應部分匹配
4、指定自定義資料格式
域屬性:
- type
域最重要的屬性是type.對於不是string的域,一般只需要設定type
{ "number_of_clicks": { "type": "integer" } }
預設, string
型別域會被認為包含全文。就是說,它們的值在索引前,會通過 一個分析器,針對於這個域的查詢在搜尋前也會經過一個分析器。
- index
index屬性控制了怎樣所以字串,值可以為:
1、analyzed
首先分析字串,然後索引它。換句話說,以全文索引這個域。
2、not_analyzed
索引這個域,所以可以搜尋到它,但索引指定的精確值。不對它進行分析。
3、no
這個域不會被搜尋到。
{ "tag": { "type": "string", "index": "not_analyzed" } }
- analyzer
對於 analyzed
字串域,用 analyzer
屬性指定在搜尋和索引時使用的分析器。預設, Elasticsearch 使用 standard
分析器, 但你可以指定一個內建的分析器替代它,例如 whitespace
、 simple
和 english:
{ "tweet": { "type": "string", "analyzer": "english" } }
更新對映
首次 建立一個索引的時候,可以指定型別的對映。你也可以使用 /_mapping
為新型別(或者為存在的型別更新對映)增加對映。
注意:儘管你可以增加一個已經存在的對映,但無法修改存在的域對映。如果一個域的對映已經存在,那麼該域的資料可能已經被索引。如果你意圖修改這個域的對映,索引的資料可能會出錯,不能被正常的搜尋。
- 建立索引時指定對映
PUT /gb { "mappings": { "tweet" : { "properties" : { "tweet" : { "type" : "string", "analyzer": "english" }, "date" : { "type" : "date" }, "name" : { "type" : "string" }, "user_id" : { "type" : "long" } } } } }
這段程式碼指定了tweet型別的對映,把tweet屬性使用english分析器,是一個全文string型別。date為日期型別,name為全文string型別使用standard分析器,user_id是長整型。
- 新增一個屬性,並指定對映
PUT /gb/_mapping/tweet { "properties" : { "tag" : { "type" : "string", "index": "not_analyzed" } } }
新增了一個tag屬性,為一個精確值string型別,即不對這個欄位作分析
我們來測試一下
GET /gb/_analyze?field=tweet&text=Black-cats
GET /gb/_analyze?field=tag&text=Black-cats
可以看到對於tweet屬性來說,索引為black和cat,而對於Black-cat 來說索引為Black-cat