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