1. 程式人生 > 其它 >從分頁功能看Elasticsearch和MySQL的區別

從分頁功能看Elasticsearch和MySQL的區別

技術標籤:資料庫javaelasticsearchesmysql


作者:elsef

elsef.com/2020/05/28/從分頁功能看Elasticsearch和MySQL的區別

MySQL分頁

熟悉MySQL的limit語法的同學都知道limit x, y的含義,即x為開始位置,y為所需返回的資料條數,這個語法天然適合用於做分頁查詢。

但是有一個性能問題需要考慮一下,比如10個數據一分頁,如果有1000頁,那麼如果使用limit 10000, 10這種方式查詢10001頁資料的話, MySQL會先去查到10000條記錄,並在後面繼續查詢10條返回,對於速度來說非常慢,並且浪費了很多記憶體空間保留臨時資料。

可以用explain

命令檢視具體的掃描的行數:

mysql>explainSELECT*FROMmessageORDERBYidDESCLIMIT10000,20
*****************1.row**************
id:1
select_type:SIMPLE
table:message
type:index
possible_keys:NULL
key:PRIMARY
key_len:4
ref:NULL
rows:10020
Extra:
1rowinset(0.00sec)

所以一般都需要在查詢條件上提前做好優化,比如知道1000頁的最大的id是z,那麼直接使用select * from tb where id > z LIMIT 0, 10

作為查詢下一頁的語句即可,這樣不但減少了查詢的資料條數降低了對記憶體的佔用,還大大降低了查詢速度,因為一般都是在索引欄位上過濾。

ES分頁

MySQL的特點是查詢的表的資料都在一臺機器的同一個庫的資料夾中,搞不好還在一個檔案中(如果表不夠大的話),而對於ES來說,由於天生是分散式的, 所以排序需要統籌全域性的資料進行排序。

比如,假如你每頁是 10 條資料,你現在要查詢第 100 頁,實際上是會把每個 shard 上儲存的前 1000 條資料都查到一個_協調節點_上(因為無法確認哪個shard上的資料是真正符合本次條件的), 如果你有個 5 個 shard,那麼就有 5000 條資料,接著協調節點對這 5000 條資料進行一些合併、處理,再獲取到最終第 100 頁的 10 條資料。

你翻頁的時候,翻的越深,每個 shard 返回的資料就越多,而且協調節點處理的時間越長。同時對記憶體的使用也非常大,比如同時有1000個併發請求, 每個請求查詢5000條記錄,每條記錄1k大小,整體大小將近5G,如果資料量更大後果是伺服器會直接報錯。

{
"error":{
"root_cause":[{
"type":"query_phase_execution_exception",
"reason":"Resultwindowistoolarge,from+sizemustbelessthanorequalto:[10000]butwas[10002].Seethescrollapiforamoreefficientwaytorequestlargedatasets.Thislimitcanbesetbychangingthe[index.max_result_window]indexlevelparameter."
}],
"type":"search_phase_execution_exception",
"reason":"allshardsfailed",
"phase":"query",
"grouped":true,
"failed_shards":[{
"shard":0,
"index":"_audit_0102",
"node":"f_CQitYESZedx8ZbyZ6bHA",
"reason":{
"type":"query_phase_execution_exception",
"reason":"Resultwindowistoolarge,from+sizemustbelessthanorequalto:[10000]butwas[10002].Seethescrollapiforamoreefficientwaytorequestlargedatasets.Thislimitcanbesetbychangingthe[index.max_result_window]indexlevelparameter."
}
}]
},
"status":500
}

雖然ES提供了max_result_window引數來調整可以一次性查詢的視窗大小,預設是10000,但是這個只能緩解, 不能從根本上解決深度查詢的問題。隨著頁碼的增加,系統資源佔用成指數級上升,很容易就會出現OOM。

所以,ES應該避免使用深度搜索場景。

scroll

scroll查詢原理是在第一次查詢的時候一次性生成一個快照,根據上一次的查詢的id來進行下一次的查詢,這個就類似於關係型資料庫的遊標cursor, 然後每次滑動都是根據產生的遊標id進行下一次查詢,這種效能比上面說的分頁效能要高出很多,基本都是毫秒級的。

注意:scroll不支援跳頁查詢。

使用場景:對實時性要求不高的查詢,例如微博或者頭條滾動查詢。

注意:從 scroll 請求返回的結果反映了 search 發生時刻的索引狀態,就像一個快照。後續的對文件的改動(索引、更新或者刪除)都只會影響後面的搜尋請求。

為了使用 scroll,初始搜尋請求應該在查詢中指定 scroll 引數,這可以告訴 Elasticsearch 需要保持搜尋的上下文環境多久,如1m即一分鐘:

curl-XGET'localhost:9200/twitter/tweet/_search?scroll=1m'-d'
{
"query":{
"match":{
"title":"elasticsearch"
}
}
}

如果把查詢型別設定成SCAN,那麼不能獲取結果並且不支援排序,只能獲得scrollId,如果使用預設設定或者不設定,那麼第一次在獲取id的同時也可以獲取到查詢結果。

使用上面的請求返回的結果中包含一個 scroll_id,這個 ID 可以被傳遞給 scroll API 來檢索下一個批次的結果。

curl-XGET'localhost:9200/_search/scroll'-d'
{
"scroll":"1m",
"scroll_id":"c2Nhbjs2OzM0NDg1ODpzRlBLc0FXNlNyNm5JWUc1"
}
  • GET 或者 POST 可以使用

  • URL不應該包含 index 或者 type 名字——這些都指定在了原始的 search 請求中。

  • scroll 引數告訴 Elasticsearch 保持搜尋的上下文等待另一個 1m

注意:初始搜尋請求和每個後續滾動請求返回一個新的 _scroll_id——只有最近的 _scroll_id 才能被使用。

比如下面的Java程式碼演示瞭如何持續的從初始結果中獲取所有資料:

SearchResponseresponse1=client.prepareSearchScroll(scrollId)//初始查詢返回的scrollId
.setScroll(TimeValue.timeValueMinutes(5))
.execute()
.actionGet();

while(response1.getHits().hits().length>0){
for(SearchHitsearchHit:response1.getHits().hits()){
System.out.println(searchHit.getSource().toString());
}
response1=client.prepareSearchScroll(response1.getScrollId())//新的scrollId
.setScroll(TimeValue.timeValueMinutes(5))
.execute().actionGet();
}

scroll-scan

scroll API 保持了哪些結果已經返回的記錄,所以能更加高效地返回排序的結果。但是,按照預設設定排序結果仍然需要代價。

可以指定使用scan為搜尋型別,它適合不關心順序,只需要掃描資料的場景,需要指定search_type=scan

curl-XGET'localhost:9200/twitter/tweet/_search?scroll=1m&search_type=scan'-d'
{
"query":{
"match":{
"title":"elasticsearch"
}
}
}

scroll-scan和scroll的區別:

  • scan不算分,關閉排序,效能好,結果會按照在索引中出現的順序返回。

  • 不支援聚合

  • 初始 search 請求的響應不會在 hits 陣列中包含任何結果。第一批結果就會按照第一個 scroll 請求返回。

  • 引數 size 控制了_每個分片_上而_非每個請求_的結果數目,所以 size 為 10 的情況下,如果命中了 5 個分片,那麼每個 scroll 請求最多會返回 50 個結果。

保活

一般來說,背後的合併過程通過合併更小的分段建立更大的分段來優化索引,同時會刪除更小的分段。這個過程在滾動時進行,但是一個開啟狀態的搜尋上下文阻止了舊分段在使用的時候不會被刪除。這就是 Elasticsearch 能夠不管後續的文件的變化,返回初始搜尋請求的結果的原因。

Reference

  • https://my.oschina.net/u/1787735/blog/3024051

  • www.elastic.co/guide/en/elasticsearch/reference/2.0/search-request-scroll.html

推薦好文

強大,10k+點讚的 SpringBoot 後臺管理系統竟然出了詳細教程!

分享一套基於SpringBoot和Vue的企業級中後臺開源專案,程式碼很規範!
能掙錢的,開源 SpringBoot 商城系統,功能超全,超漂亮!