1.分頁
舉個例子,上面的
_search
查詢中,total給出數據的總量,但是,實際顯示出來的只有10條。那么,如果我們顯示更多的數據呢和 SQL 使用
LIMIT
關鍵字返回單個 page
結果的方法相同,Elasticsearch 接受 from
和 size
參數:size
顯示應該返回的結果數量,默認是10
from
顯示應該跳過的初始結果數量,默認是0
如果每頁展示 5 條結果,可以用下面方式請求得到 1 到 3 頁的結果:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
理解為什么深度分頁是有問題的,我們可以假設在一個有 5 個主分片的索引中搜索。 當我們請求結果的第一頁(結果從 1 到 10 ),每一個分片產生前 10 的結果,并且返回給協調節點,協調節點對 50 個結果排序得到全部結果的前 10 個。
現在假設我們請求第 1000 頁--結果從 10001 到 10010 。所有都以相同的方式工作除了每個分片不得不產生前10010個結果以外。 然后協調節點對全部 50050 個結果排序最后丟棄掉這些結果中的 50040 個結果。
可以看到,在分布式系統中,對結果排序的成本隨分頁的深度成指數上升。這就是 web 搜索引擎對任何查詢都不要返回超過 1000 個結果的原因
2. 映射和分析
當擺弄索引里面的數據時,我們發現一些奇怪的事情。一些事情看起來被打亂了:在我們的索引中有12條推文,其中只有一條包含日期 2014-09-15 ,但是看一看下面查詢命中的 總數 (total):
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 !
為什么在_all
字段查詢日期返回所有推文,而在 date
字段只查詢年份卻沒有返回結果?為什么我們在 _all
字段和 date
字段的查詢結果有差別?
推測起來,這是因為數據在 _all
字段與 date
字段的索引方式不同。所以,通過請求 gb
索引中 tweet
類型的映射(或模式定義),讓我們看一看 Elasticsearch 是如何解釋我們文檔結構的:
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"
}
}
}
}
}
}
基于對字段類型的猜測, Elasticsearch 動態為我們產生了一個映射。這個響應告訴我們date
字段被認為是date
類型的。由于_all
是默認字段,所以沒有提及它。但是我們知道_all
字段是string
類型的。所以date
字段和string
字段索引方式不同,因此搜索結果也不一樣。這完全不令人吃驚。你可能會認為核心數據類型 strings、numbers、Booleans 和 dates 的索引方式有稍許不同。沒錯,他們確實稍有不同。
但是,到目前為止,最大的差異在于代表精確值(它包括string
字段)的字段和代表全文的字段。這個區別非常重要——它將搜索引擎和所有其他數據庫區別開來。
3.精確值和全文
Elasticsearch 中的數據可以概括的分為兩類:精確值和全文。 精確值 如它們聽起來那樣精確。 例如日期或者用戶 ID,但字符串也可以表示精確值,例如用戶名或郵箱地址。對于精確值來講,Foo
和foo
是不同的,2014
和2014-09-15
也是不同的。另一方面,全文是指文本數據(通常以人類容易識別的語言書寫),例如一個推文的內容或一封郵件的內容.
精確值很容易查詢。結果是二進制的:要么匹配查詢,要么不匹配。查詢全文數據要微妙的多。我們問的不只是“這個文檔匹配查詢嗎”,而是“該文檔匹配查詢的程度有多大?”換句話說,該文檔與給定查詢的相關性如何?我們很少對全文類型的域做精確匹配。相反,我們希望在文本類型的域中搜索。為了促進這類在全文域中的查詢,Elasticsearch 首先 分析 文檔,之后根據結果創建 倒排索引 。
4.倒排索引
例如,假設我們有兩個文檔,每個文檔的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
兩個文檔都匹配,但是第一個文檔比第二個匹配度更高。如果我們使用僅計算匹配詞條數量的簡單相似性算法,那么,我們可以說,對于我們查詢的相關性來講,第一個文檔比第二個文檔更佳。但是,我們目前的倒排索引有一些問題:
- Quick 和 quick 以獨立的詞條出現,然而用戶可能認為它們是相同的詞。
- fox 和 foxes 非常相似, 就像 dog 和 dogs ;他們有相同的詞根。
- jumped 和 leap, 盡管沒有相同的詞根,但他們的意思很相近。他們是同義詞。
使用前面的索引搜索 +Quick +fox 不會得到任何匹配文檔。(記住,+ 前綴表明這個詞必須存在。)只有同時出現 Quick 和 fox 的文檔才滿足這個查詢條件,但是第一個文檔包含 quick fox ,第二個文檔包含 Quick foxes 。我們的用戶可以合理的期望兩個文檔與查詢匹配。我們可以做的更好。如果我們將詞條規范為標準模式,那么我們可以找到與用戶搜索的詞條不完全一致,但具有足夠相關性的文檔。例如:
- Quick 可以小寫化為 quick 。
- foxes 可以 詞干提取 --變為詞根的格式-- 為 fox 。類似的, dogs 可以為提取為 dog 。
- jumped 和 leap 是同義詞,可以索引為相同的單詞 jump 。
現在索引看上去像這樣:
Term Doc_1 Doc_2
-------------------------
brown | X | X
dog | X | X
fox | X | X
in | | X
jump | X | X
lazy | X | X
over | X | X
quick | X | X
summer | | X
the | X | X
------------------------
這還遠遠不夠。我們搜索 +Quick +fox 仍然 會失敗,因為在我們的索引中,已經沒有 Quick 了。但是,如果我們對搜索的字符串使用與 content 域相同的標準化規則,會變成查詢 +quick +fox ,這樣兩個文檔都會匹配!
5.分析和分析器
分析包含下面的過程:
- 首先,將一塊文本分成適合于倒排索引的獨立的 詞條 ,
- 之后,將這些詞條統一化為標準格式以提高它們的“可搜索性”,或者 recall
分析器執行上面的工作。分析器實際上是將三個功能封裝到了一個包里:
字符過濾器
首先,字符串按順序通過每個 字符過濾器 。他們的任務是在分詞前整理字符串。一個字符過濾器可以用來去掉HTML,或者將 & 轉化成 and
。
分詞器
其次,字符串被 分詞器 分為單個的詞條。一個簡單的分詞器遇到空格和標點的時候,可能會將文本拆分成詞條。
Token 過濾器
最后,詞條按順序通過每個 token 過濾器 。這個過程可能會改變詞條(例如,小寫化 Quick ),刪除詞條(例如, 像 a,
and,
the 等無用詞),或者增加詞條(例如,像 jump 和 leap 這種同義詞)。
Elasticsearch提供了開箱即用的字符過濾器、 分詞器和token 過濾器。 這些可以組合起來形成自定義的分析器以用于不同的目的。我們會在
自定義分析器 章節詳細討論。
5.1 內置的分析器
但是, Elasticsearch還附帶了可以直接使用的預包裝的分析器。 接下來我們會列出最重要的分析器。為了證明它們的差異,我們看看每個分析器會從下面的字符串得到哪些詞條:
"Set the shape to semi-transparent by calling set_trans(5)"
標準分析器
標準分析器是Elasticsearch默認使用的分析器。它是分析各種語言文本最常用的選擇。它根據Unicode 聯盟定義的單詞邊界劃分文本。刪除絕大部分標點。最后,將詞條小寫。它會產生
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
簡單分析器
簡單分析器在任何不是字母的地方分隔文本,將詞條小寫。它會產生
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器在空格的地方劃分文本。它會產生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
語言分析器
特定語言分析器可用于很多語言。它們可以考慮指定語言的特點。例如,英語
分析器附帶了一組英語無用詞(常用單詞,例and
或者the
,它們對相關性沒有多少影響),它們會被刪除。 由于理解英語語法的規則,這個分詞器可以提取英語單詞的詞干。英語
分詞器會產生下面的詞條:
set, shape, semi, transpar, call, set_tran, 5
注意看
transparent
、 calling
和set_trans
已經變為詞根格式。
5.2什么時候使用分析器
當我們索引一個文檔,它的全文域被分析成詞條以用來創建倒排索引。但是,當我們在全文域搜索的時候,我們需要將查詢字符串通過
相同的分析過程,以保證我們搜索的詞條格式與索引中的詞條格式一致。全文查詢,理解每個域是如何定義的,因此它們可以做正確的事:
- 當你查詢一個全文 域時, 會對查詢字符串應用相同的分析器,以產生正確的搜索詞條列表。
- 當你查詢一個精確值 域時,不會分析查詢字符串,而是搜索你指定的精確值。
現在你可以理解在開始的查詢為什么返回那樣的結果:
-
date
域包含一個精確值:單獨的詞條2014-09-15
。 -
_all
域是一個全文域,所以分詞進程將日期轉化為三個詞條:2014
,09
, 和15
。
當我們在_all
域查詢2014
,它匹配所有的12條推文,因為它們都含有 2014
,
當我們在 _all 域查詢 2014-09-15,它首先分析查詢字符串,產生匹配
2014,
09, 或
15 中 任意 詞條的查詢。這也會匹配所有12條推文,因為它們都含有 2014 ,
當我們在 date 域查詢 2014-09-15
,它尋找 精確 日期,只找到一個推文,
當我們在 date 域查詢 2014
,它找不到任何文檔,因為沒有文檔含有這個精確日志