首頁  >  文章  >  後端開發  >  如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

Guanhui
Guanhui轉載
2020-07-27 17:24:393375瀏覽

如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

「訪談問題& 真實經驗

面試題:在資料量很大的情況下,怎麼實現深度分頁? 大家在面試時,或是準備面試中可能會遇到上述的問題,大多的回答基本上是分庫分錶建索引,這是一種很標準的正確回答,但現實總是很骨感,所以面試官通常會追問你一句,現在工期不足,人員不足,該怎麼實現深度分頁?

這時候沒有實​​際經驗的同學基本麻爪,So,請聽我娓娓道來。

慘痛的教訓如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

首先必須明確一點:深度分頁可以做,但深度隨機跳頁絕對需要禁止。

上一張圖:

你猜,我點第142360頁,服務會不會爆炸?

像MySQL,MongoDB資料庫還好,本身就是專業的資料庫,處理的不好,最多就是慢,但如果涉及到ES,性質就不一樣了,我們只好利用SearchAfter Api,去循環取得數據,這就牽扯到記憶體佔用的問題,如果當時程式碼寫的不優雅,直接就可能導致記憶體溢出。

為什麼不能允許隨機深度跳頁

從技術的角度淺顯的聊一聊為什麼不能允許隨機深度跳頁,或者說為什麼不建議深度分頁

MySQL

分頁的基本原理:

SELECT * FROM test ORDER BY id DESC LIMIT 10000, 20;

LIMIT 10000 , 20的意思掃描滿足條件的10020行,丟掉前面的10000行,返回最後的20行。如果是LIMIT 1000000 , 100,需要掃描1000100 行,在一個高並發的應用程式裡,每次查詢需要掃描超過100W行,不炸才怪。

MongoDB

分頁的基本原理:

db.t_data.find().limit(5).skip(5);

同樣的,隨著頁碼的增大,skip 跳過的項目也會隨之變大,而這個操作是透過cursor 的迭代器來實現的,對於cpu的消耗會非常明顯,當頁碼非常大時且頻繁時,必然爆炸。

  • ElasticSearch

  • 從業務的角度來說,ElasticSearch不是典型的資料庫,它是搜尋引擎,如果在篩選條件下沒有搜尋想要的數據,繼續深度分頁也不會找到想要的數據,退一步講,假如我們把ES作為數據庫來使用進行查詢,在進行分頁的時候一定會遇到max_result_window 的限制,看到沒,官方都告訴你最大偏移量限制是一萬。
  • 查詢流程:

  • 如查詢第501頁,每頁10條,客戶端傳送請求到某節點

  • 此節點將數據廣播到各個分片,各分片各自查詢前5010 條數據

#查詢結果返回至該節點,然後對數據進行整合,取出前5010 條數據

回傳給客戶端

由此可以看出為什麼要限制偏移量,另外,如果使用Search After 這種捲動式API進行深度跳頁查詢,也是一樣需要每次滾動幾千條,可能一共需要滾動上百萬,千萬條數據,就為了最後的20條數據,效率可想而知。

再一次和產品對線

俗話說的好,技術解決不了的問題,就由業務來解決!

在實習的時候信了產品的邪,必須實現深度分頁跳頁,如今必須撥亂反正,業務上必須有如下更改:如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

盡可能的增加默認的篩選條件,如:時間週期,目的是為了減少資料量的展示

修改跳頁的展現方式,改為滾動顯示,或小範圍跳頁

如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?滾動顯示參考圖:

小規模跳頁參考圖:

  • 通用解決方案

  • #短時間內快速解決的方案主要是以下幾點:

  • 必備:對排序字段,篩選條件務必設定好索引

#核心:利用小範圍頁碼的已知數據,或捲動載入的已知數據,減少偏移量

額外:如果遇到不好處理的情況,也可以取得多餘的數據,進行一定的截取,效能影響並不大

#######MySQL#########原分頁SQL:###
# 第一页
SELECT * FROM `year_score` where `year` = 2017 ORDER BY id limit 0, 20;
# 第N页
SELECT * FROM `year_score` where `year` = 2017 ORDER BY id limit (N - 1) * 20, 20;
###透過上下文關係,改寫為:###
# XXXX 代表已知的数据
SELECT * FROM `year_score` where `year` = 2017 and id > XXXX ORDER BY id limit 20;

在 没内鬼,来点干货!SQL优化和诊断 一文中提到过,LIMIT会在满足条件下停止查询,因此该方案的扫描总量会急剧减少,效率提升Max!

ES

方案和MySQL相同,此时我们就可以随用所欲的使用 FROM-TO Api,而且不用考虑最大限制的问题。

MongoDB

方案基本类似,基本代码如下:

如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

相关性能测试:

如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?

如果非要深度随机跳页

如果你没有杠过产品经理,又该怎么办呢,没关系,还有一丝丝的机会。

在 SQL优化 一文中还提到过MySQL深度分页的处理技巧,代码如下:

# 反例(耗时129.570s)
select * from task_result LIMIT 20000000, 10;
# 正例(耗时5.114s)
SELECT a.* FROM task_result a, (select id from task_result LIMIT 20000000, 10) b where a.id = b.id;
# 说明
# task_result表为生产环境的一个表,总数据量为3400万,id为主键,偏移量达到2000万

该方案的核心逻辑即基于聚簇索引,在不通过回表的情况下,快速拿到指定偏移量数据的主键ID,然后利用聚簇索引进行回表查询,此时总量仅为10条,效率很高。

因此我们在处理MySQL,ES,MongoDB时,也可以采用一样的办法:

  • 限制获取的字段,只通过筛选条件,深度分页获取主键ID

  • 通过主键ID定向查询需要的数据

瑕疵:当偏移量非常大时,耗时较长,如文中的 5s

推荐教程:《MySQL教程

文章来源:https://juejin.im/post/5f0de4d06fb9a07e8a19a641

以上是如何相容於 MySQL + ES + MongoDB 實現上億資料的深度分頁?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除