Elasticsearch深入理解(九)——三种分页方式选取
一、from + size
ElasticSearch默認采用的分頁方式是from + size的形式,在深度分頁的情況下,這種使用方式的效率是非常低的,比如我們執行如下查詢:
| 1 2 3 4 5 6 7 8 | GET /student/student/_search { ??"query":{ ????"match_all": {} ??}, ??"from":5000, ??"size":10 } |
以上DSL語句執行后,ElasticSearch需要在各個分片上匹配排序并得到5010條數據,協調節點拿到這些數據再進行排序處理,然后結果集中取最后10條數據返回。
這樣的深度分頁的效率非常低,因為我們只需要查詢10條數據,而ElasticSearch則需要對from + size條數據進行排序處理后返回。
其次:ElasticSearch為了性能,限制了分頁的深度,目前ElasticSearch支持的最大的查詢長度是max_result_window = 10000;也就是說我們不能分頁到10000條數據以上。
例如:9990+10 = 10000,此時可以執行分頁查詢。
但是,當9991+10 > 10000,此時執行查詢的返回為:
二、scroll(游標)
? ? ? ? 相對于from和size的分頁來說,使用scroll可以模擬一個傳統數據的游標,記錄當前讀取的文檔信息位置。這個分頁的用法,不是為了實時查詢數據,而是為了一次性查詢大量的數據(甚至是全部的數據)。
傳統數據庫游標:
? ? ??游標(cursor)是系統為用戶開設的一個數據緩沖區,存放SQL語句的執行結果。每個游標區都有一個名字,用戶可以用SQL語句逐一從游標中獲取記錄,并賦給主變量,交由主語言進一步處理。就本質而言,游標實際上是一種能從包括多條數據記錄的結果集中每次提取一條記錄的機制。游標是一段私有的SQL工作區,也就是一段內存區域,用于暫時存放受SQL語句影響到的數據。通俗理解就是將受影響的數據暫時放到了一個內存區域的虛表中,而這個虛表就是游標。
ElasticSearch scroll:
? ? ? 使用scroll滾動搜索,可以先搜索一批數據,然后下次再搜索一批數據,以此類推,直到搜索出全部的數據。scroll搜索會在第一次搜索的時候,保存一個當時的視圖快照,之后只會基于該舊的視圖快照提供數據搜索,如果這個期間數據變更,是不會讓用戶看到的。每次發送scroll請求,我們還需要指定一個scroll參數,指定一個時間窗口,每次搜索請求只要在這個時間窗口內能完成就可以了。
? ? ??一個滾屏搜索允許我們做一個初始階段搜索并且持續批量從Elasticsearch里拉取結果直到沒有結果剩下。
? ? ? 滾屏搜索會及時制作快照。這個快照不會包含任何在初始階段搜索請求后對index做的修改。它通過將舊的數據文件保存在手邊,所以可以保護index的樣子看起來像搜索開始時的樣子。這樣將使得我們無法得到用戶最近的更新行為。
scroll的使用:
執行如下curl,每次請求兩條。可以定制 scroll = 1m意味著該窗口過期時間為1分鐘。
| 1 2 3 4 5 6 7 | GET /student/student/_search?scroll=1m { ??"query":{ ????"match_all": {} ??}, ??"size":2 } |
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | { ??"_scroll_id"?:?"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAR-lFlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEfphZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABH6cWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==", ??"took"?:?15, ??"timed_out"?:?false, ??"_shards"?: { ????"total"?:?3, ????"successful"?:?3, ????"skipped"?:?0, ????"failed"?:?0 ??}, ??"hits"?: { ????"total"?:?4, ????"max_score"?:?1.0, ????"hits"?: [ ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"2", ????????"_score"?:?1.0, ????????"_source"?: { ??????????"id"?:?2, ??????????"name"?:?"仙道彰", ??????????"age"?:?17, ??????????"gender"?:?"男", ??????????"address"?:?"神奈川縣陵南高中" ????????} ??????}, ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"4", ????????"_score"?:?1.0, ????????"_source"?: { ??????????"id"?:?4, ??????????"name"?:?"赤木剛憲", ??????????"age"?:?20, ??????????"address"?:?"東京大學", ??????????"gender"?:?"男" ????????} ??????} ????] ??} } |
可以看到在返回結果中,存在一個很重要的_scroll_id,在后面的請求中,都需要在帶著這個_scroll_id去請求。如下:
| 1 2 3 4 5 | GET /_search/scroll { ??"scroll":"1m", ??"scroll_id":"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAR-lFlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEfphZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABH6cWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==" } |
執行一次,得到結果:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | { ??"_scroll_id"?:?"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAASI_FlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEiQRZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABIkAWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==", ??"took"?:?3, ??"timed_out"?:?false, ??"_shards"?: { ????"total"?:?3, ????"successful"?:?3, ????"skipped"?:?0, ????"failed"?:?0 ??}, ??"hits"?: { ????"total"?:?4, ????"max_score"?:?1.0, ????"hits"?: [ ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"3", ????????"_score"?:?1.0, ????????"_source"?: { ??????????"id"?:?3, ??????????"name"?:?"赤木晴子", ??????????"age"?:?17, ??????????"gender"?:?"女", ??????????"address"?:?"神奈川縣湘北高中" ????????} ??????}, ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"1", ????????"_score"?:?1.0, ????????"_source"?: { ??????????"id"?:?1, ??????????"name"?:?"櫻木花道", ??????????"age"?:?18, ??????????"address"?:?"神奈川縣湘北高中", ??????????"gender"?:?"男" ????????} ??????} ????] ??} } |
再執行一次,得到結果:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { ??"_scroll_id"?:?"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAASI_FlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEiQRZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABIkAWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==", ??"took"?:?1, ??"timed_out"?:?false, ??"_shards"?: { ????"total"?:?3, ????"successful"?:?3, ????"skipped"?:?0, ????"failed"?:?0 ??}, ??"hits"?: { ????"total"?:?4, ????"max_score"?:?1.0, ????"hits"?: [ ] ??} } |
現在student這個索引中共有4條數據,id分別為 1, 2, 3, 4。當我們使用 scroll 查詢第3次的時候,返回結果為kong。這時我們就知道已經結果集已經匹配完了。
三、search_after
? ? ? from + size的分頁方式雖然是最靈活的分頁方式,但是當分頁深度達到一定程度將會產生深度分頁的問題。scroll能夠解決深度分頁的問題,但是其無法實現實時查詢,即當scroll_id生成后無法查詢到之后數據的變更,因為其底層原理是生成數據的快照。這時 search_after應運而生。其是在es-5.X之后才提供的。
? ? ??search_after 是一種假分頁方式,根據上一頁的最后一條數據來確定下一頁的位置,同時在分頁請求的過程中,如果有索引數據的增刪改查,這些變更也會實時的反映到游標上。為了找到每一頁最后一條數據,每個文檔必須有一個全局唯一值,官方推薦使用 _id 作為全局唯一值,但是只要能表示其唯一性就可以。
執行如下查詢:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | GET /student/student/_search { ??"query":{ ????"match_all": {} ??}, ??"size":2, ??"sort": [ ????{ ??????"id": { ????????"order":?"desc" ??????} ????} ??] } |
結果為:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | { ??"took"?:?1, ??"timed_out"?:?false, ??"_shards"?: { ????"total"?:?3, ????"successful"?:?3, ????"skipped"?:?0, ????"failed"?:?0 ??}, ??"hits"?: { ????"total"?:?4, ????"max_score"?:?null, ????"hits"?: [ ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"4", ????????"_score"?:?null, ????????"_source"?: { ??????????"id"?:?4, ??????????"name"?:?"赤木剛憲", ??????????"age"?:?20, ??????????"address"?:?"東京大學", ??????????"gender"?:?"男" ????????}, ????????"sort"?: [ ??????????4 ????????] ??????}, ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"3", ????????"_score"?:?null, ????????"_source"?: { ??????????"id"?:?3, ??????????"name"?:?"赤木晴子", ??????????"age"?:?17, ??????????"gender"?:?"女", ??????????"address"?:?"神奈川縣湘北高中" ????????}, ????????"sort"?: [ ??????????3 ????????] ??????} ????] ??} } |
可以看到結果集中的??"sort" : [3] ,將當前結果集中sort的值,作為下一次查詢search_after的參數:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | GET /student/student/_search { ??"query":{ ????"match_all": {} ??}, ??"size":2, ??"sort": [ ????{ ??????"_id": { ????????"order":?"desc" ??????} ????} ??], ??"search_after":[3] } |
得到結果為:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | { ??"took"?:?1, ??"timed_out"?:?false, ??"_shards"?: { ????"total"?:?3, ????"successful"?:?3, ????"skipped"?:?0, ????"failed"?:?0 ??}, ??"hits"?: { ????"total"?:?4, ????"max_score"?:?null, ????"hits"?: [ ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"4", ????????"_score"?:?null, ????????"_source"?: { ??????????"id"?:?4, ??????????"name"?:?"赤木剛憲", ??????????"age"?:?20, ??????????"address"?:?"東京大學", ??????????"gender"?:?"男" ????????}, ????????"sort"?: [ ??????????4 ????????] ??????}, ??????{ ????????"_index"?:?"student", ????????"_type"?:?"student", ????????"_id"?:?"3", ????????"_score"?:?null, ????????"_source"?: { ??????????"id"?:?3, ??????????"name"?:?"赤木晴子", ??????????"age"?:?17, ??????????"gender"?:?"女", ??????????"address"?:?"神奈川縣湘北高中" ????????}, ????????"sort"?: [ ??????????3 ????????] ??????} ????] ??} } |
這樣我們就使用search_after方式實現了分頁查詢。
四、三種分頁方式的比較
| 分頁方式 | 性能 | 優點 | 缺點 | 場景 |
| from + size | 低 | 靈活性好,實現簡單 | 深度分頁問題 | 數據量比較小,能容忍深度分頁問題 |
| scroll | 中 | 解決了深度分頁問題 | 無法反應數據的實時性(快照版本) 維護成本高,需要維護一個 scroll_id | 海量數據的導出,需要查詢海量結果集的數據 |
| search_after | 高 | 性能最好 不存在深度分頁問題 能夠反映數據的實時變更 | 實現復雜,需要有一個全局唯一的字段 連續分頁的實現會比較復雜,因為每一次查詢都需要上次查詢的結果 | 海量數據的分頁 |
總結
以上是生活随笔為你收集整理的Elasticsearch深入理解(九)——三种分页方式选取的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 桌面美化:透明居中任务栏 Taskbar
- 下一篇: pycharm调试bug Process