es查詢-統計總數以及深度分頁
一、查詢總數
1. ES 查詢 hits 統計總數不准?
當我們使用 ES 的時候,有時會比較關心匹配到的文檔總數是多少,所以在查詢得到結果后會使用 hits.total.value 這個值作為匹配的總數,如下
圖一
說明:這是因為,es官方默認限制索引查詢最多只能查詢10000條數據。
2. track_total_hits
平常數據量不大的情況下,這個數值沒問題。但是當超出 10000 個數據量的時候,這個 value 將不會增長了,固定為 10000。這個時候很顯然數量統計就不准了。
ES 為我們准備了這樣一個參數來開啟精確匹配 track_total_hits
這個時候返回值將是精確的:
圖二
3. track_total_hits的使用場景
如果你的業務需求在超過 10000 這個閾值之后就不需要精准的計算的話,就不需要設置該值,畢竟匹配大量的文檔是一個成本較高的操作,同樣的如果你並不需要統計數量,那么將該值設置為 false "track_total_hits": false 也是一種優化檢索效率的操作。
4. hits.total.relation
通過圖一和圖二,可以發現設置 track_total_hits 為 true 的時候返回 relation 的值是不一樣的,gte 表示 hits.total.value 是查詢匹配總命中數的上限。 eq 則表示hits.total.value 是准確計數。
因此,我們可以通過設置 track_total_hits 為整數,來自定義上限如:
二、查詢數據
1. 查詢超過10000條數據,開始報錯
上面解決了需要統計超過10000條數據總數的問題,但是在查詢具體數據的時候依然存在類似的問題。es官方默認限制索引查詢最多只能查詢10000條數據,查詢第10001條數據開始就會報錯:
result window is too large, from + size must be less than or equal to
解決方案:
1)第一種辦法:在kibana中執行,解除索引最大查詢數的限制
put _all/_settings { "index.max_result_window":200000 }
_all表示所有索引,針對單個索引的話修改成索引名稱即可。
2)第二種辦法:在創建索引的時候加上
1 "settings":{ 2 "index":{ 4 "max_result_window": 500000 5 } 7 }
三、深度分頁
做分頁查詢的時候,我們需要考慮兩個問題
- 性能要求,在硬件和查詢效率中作出權衡
- 對大部分請求來說,都不會超過10000條,即不需要深度分頁,當需要請求超過10000條時,就需要考慮我們的查詢語句是否恰當
下面有幾種分頁方式:
1、方式一: from+size淺分頁
"淺"分頁可以理解為簡單意義上的分頁。它的原理很簡單,就是查詢前20條數據,然后截斷前10條,只返回10-20的數據。這樣其實白白浪費了前10條的查詢。
1 GET test_alias/_search 2 { 3 "query": { 4 "match_all": { 5 } 6 }, 7 "sort": [ 8 { 9 "add_time": { 10 "order": "desc" 11 } 12 } 13 ], 14 "from":0, 15 "size": 2 16 }
其中,from定義了目標數據的偏移值,size定義當前返回的數目。默認from為0,size為10,即所有的查詢默認僅僅返回前10條數據。
from/size的原理:
因為es是基於分片的,假設有5個分片,from=100,size=10。則會根據排序規則從5個分片中各取回100條數據數據,然后匯總成500條數據后,選擇最后面的10條數據。
做過測試,越往后的分頁,執行的效率越低。總體上會隨着from的增加,消耗時間也會增加。而且數據量越大,就越明顯。
說明:from+size查詢在10000-50000條數據(1000到5000頁)以內的時候還是可以的,但是如果數據過多的話,就會出現深分頁問題。
2、方式二: scroll深分頁
為了解決上面的問題,elasticsearch提出了一個scroll滾動的方式。
scroll 類似於sql中的cursor,使用scroll,每次只能獲取一頁的內容,然后會返回一個scroll_id。根據返回的這個scroll_id可以不斷地獲取下一頁的內容,所以scroll並不適用於有跳頁的情景。
2.1 參數說明:
- scroll=5m表示設置scroll_id保留5分鍾可用。
- size決定后面每次調用_search搜索返回的數量
2.2 scroll刪除
根據官方文檔的說法,scroll的搜索上下文會在scroll的保留時間截止后自動清除,但是我們知道scroll是非常消耗資源的,所以一個建議就是當不需要了scroll數據的時候,盡可能快的把scroll_id顯式刪除掉。
1)清除指定的scroll_id:
DELETE _search/scroll/DnF1ZX...
2)清除所有的scroll:
DELETE _search/scroll/_all
然后我們可以通過數據返回的_scroll_id讀取下一頁內容,每次請求將會讀取下10條數據,直到數據讀取完畢或者scroll_id保留時間截止。
說明:scroll 的方式,官方的建議不用於實時的請求(一般用於數據導出),因為每一個 scroll_id 不僅會占用大量的資源,而且會生成歷史快照,對於數據的變更不會反映到快照上。
3、方式三:search_after 深分頁
search_after是ES5.0 及之后版本提供的新特性,search_after有點類似scroll,但是和scroll又不一樣,它提供一個活動的游標,通過上一次查詢最后一條數據來進行下一次查詢。
比如第一次查詢如下:
注意到返回結果中有一個sort字段,所以下一次查詢的時候,只需要將本次查詢最后一條數據中的排序字段加入查詢即可:
實現原理:
search_after 分頁的方式是根據上一頁的最后一條數據來確定下一頁的位置,同時在分頁請求的過程中,如果有索引數據的增刪改查,這些變更也會實時的反映到游標上。
說明:
- 為了找到每一頁最后一條數據,每個文檔必須有一個全局唯一值,官方推薦使用 _uid 作為全局唯一值,其實使用業務層的 id 也可以。
- 說明:使用search_after查詢需要將from設置為0或-1,當然也可以不寫。
需要注意的是:
1)sort字段的選擇
如果search_after中的關鍵字為***,那么***123的文檔也會被搜索到,所以在選擇search_after的排序字段時需要謹慎,可以使用比如文檔的id或者時間戳等。另外,search_after並不是隨機的查詢某一頁數據,而是並行的滾屏查詢;search_after的查詢順序會在更新和刪除時發生變化,也就是說支持實時的數據查詢。
2)無法跳頁請求
因為每一頁的數據依賴於上一頁最后一條數據,所以無法跳頁請求。
4、方式四: 由前端傳入上次查詢的最小ID
由於業務需要,我這邊實現的分頁列表由前端配合一起完成,這樣也解決了跳頁的問題,供參考:
1 <?php 2 3 class Test 4 { 5 6 public function queryBaiduJobList(array $fileds, int $minId, int $limit) 7 { 8 // 為了解決es查詢默認1w條的上限,由前端傳入上一次查詢返回列表中最小id:minId 9 $query = JobParams::build() 10 ->term('is_check', 1) 11 ->select($fileds) 12 ->trackHits(true) 13 ->sort('id') 14 ->limit($limit); 15 16 if ($minId) { 17 $query = $query->range('id', ['<', $minId]); 18 } 19 20 return $this->all($query); 21 } 22 }
參考鏈接:https://blog.csdn.net/andybegin/article/details/83864171