面試官:1億數(shù)據(jù)分庫分表,分頁查詢應(yīng)如何設(shè)計?
大家好,我是秀才。在后端技術(shù)面試,尤其是針對高級工程師或架構(gòu)師的崗位,分庫分表是一個核心考察點。資深面試官往往不會直接問我們分庫分表策略是怎么設(shè)計的,而是會基于此提出一個極具挑戰(zhàn)性的場景問題:“在億級別數(shù)據(jù)量且已實施分庫分表的背景下,分頁查詢應(yīng)如何設(shè)計?常見的性能瓶頸有哪些?”
這個問題并不是單純考察分頁實現(xiàn),而是旨在評估工程師對于分布式數(shù)據(jù)處理的理解深度、多方案橫向?qū)Ρ鹊臋?quán)衡能力,以及在復(fù)雜約束下進行架構(gòu)設(shè)計的綜合能力。它能夠精確地衡量出一位工程師在分布式系統(tǒng)領(lǐng)域的知識深度與廣度。
接下來秀才就對這個問題進行系統(tǒng)性解構(gòu),從根源探究其技術(shù)挑戰(zhàn),并梳理出一套完整的架構(gòu)解決方案,希望能對大家在工作實踐和面試中有所幫助。
1. 分庫分表的常見策略
在深入探討分頁查詢方案這一問題之前,我們必須首先對分庫分表架構(gòu)下的基礎(chǔ)組件與核心概念建立一個清晰的共識。這里我們主要討論水平分表的解決方案。水平分表表結(jié)構(gòu)一般不會變更。要探索的是如何將海量數(shù)據(jù)分片存儲到不同的表中以實現(xiàn)高性能。
1.1 水平分片策略
通常,我們會采用以下幾種策略將數(shù)據(jù)打散到不同的庫表中:
- 哈希取模:這是最常見的方式,比如根據(jù)用戶ID進行哈希計算后,對分庫(或分表)的數(shù)量取模,從而決定數(shù)據(jù)落在哪個節(jié)點。例如,將訂單數(shù)據(jù)按
user_id % 16分散到16個表中。
1
- 優(yōu)點:數(shù)據(jù)分布相對均勻,不容易產(chǎn)生數(shù)據(jù)傾斜和熱點問題。
- 缺點:范圍查詢極不友好。比如,要查詢某一段時間內(nèi)創(chuàng)建的所有訂單,就必須掃描所有分片,因為無法從時間維度定位到具體的分片。
- 范圍劃分:按照某個字段的區(qū)間來切分數(shù)據(jù)。最典型的就是按時間范圍,例如,每個月的訂單數(shù)據(jù)存放在一張獨立的表中(
orders_202401,orders_202402...)。
2
- 優(yōu)點:范圍查詢非常高效,天然支持按時間等維度的冷熱數(shù)據(jù)分離。
- 缺點:容易產(chǎn)生熱點問題。例如,在訂單場景下,所有新的寫入請求都會集中在最新的那張表上,對數(shù)據(jù)庫造成瞬時高壓。
- 路由中間表:建立一個獨立的映射關(guān)系表,用于記錄某條數(shù)據(jù)(如主鍵)具體存儲在哪個物理庫表中。這種方式雖然靈活,但多了一次額外查詢,增加了架構(gòu)的復(fù)雜度。查詢時需要先訪問路由表,再根據(jù)路由信息訪問目標分片。
3
這些策略并不是互斥,實際架構(gòu)中往往是組合使用,例如先按用戶ID哈希分庫,庫內(nèi)再按時間范圍分表。
1.2 分庫分表的實現(xiàn)方式
實現(xiàn)數(shù)據(jù)分片的邏輯,通常由分庫分表中間件來完成,它們主要有三種架構(gòu)形態(tài):
- SDK 模式:以
jar包的形式被業(yè)務(wù)應(yīng)用直接引入。優(yōu)點是性能損耗最低,因為路由、聚合等操作都在業(yè)務(wù)應(yīng)用進程內(nèi)完成,沒有額外的網(wǎng)絡(luò)調(diào)用。缺點是與特定編程語言強耦合(Java的SDK無法給Go用),且版本升級時需要協(xié)調(diào)所有依賴方,維護成本較高,容易形成“胖客戶端”
4
- Proxy 模式:以一個獨立的代理服務(wù)存在,對業(yè)務(wù)應(yīng)用偽裝成一個數(shù)據(jù)庫。應(yīng)用所有的SQL請求都先發(fā)給Proxy,由Proxy解析、路由、執(zhí)行,最后合并結(jié)果返回。優(yōu)點是與語言無關(guān),對業(yè)務(wù)透明。缺點是多了一層網(wǎng)絡(luò)開銷,且Proxy自身容易成為性能瓶頸和單點故障。為了保證Proxy的高可用,還需要額外部署如LVS、Nginx等負載均衡和Keepalived等高可用組件,增加了運維的復(fù)雜度。
5
- Sidecar 模式:這是一種在服務(wù)網(wǎng)格(Service Mesh)架構(gòu)下興起的形態(tài),將分庫分表的能力下沉到與業(yè)務(wù)應(yīng)用一同部署的Sidecar中。它結(jié)合了SDK的低耦合和Proxy的語言無關(guān)性,但目前市面上尚未有非常成熟的開源產(chǎn)品。
6
在這三種形態(tài)中,SDK 模式的性能最好,但缺點是與具體編程語言綁定緊密。比如,Java 提供的 ShardingSphere jar 包無法直接被 Go 語言調(diào)用。 相比之下,Proxy 模式的性能最弱,因為所有查詢請求都會集中經(jīng)過它,極易成為系統(tǒng)瓶頸。如果只部署單節(jié)點 Proxy,還存在單點故障的風險。不過它的優(yōu)勢也很明顯:與語言無關(guān),任何技術(shù)棧的業(yè)務(wù)都可以通過同一個 Proxy 使用分庫分表能力。而且 Proxy 偽裝成普通數(shù)據(jù)庫實例后,業(yè)務(wù)只需替換數(shù)據(jù)源配置,就能在單庫單表與分庫分表之間平滑切換,幾乎無需額外改造
2. 全局查詢法
理解了這些背景,我們就能清晰地認識到,為什么一個簡單的LIMIT offset, size分頁查詢,在分庫分表環(huán)境下會面臨嚴峻的挑戰(zhàn)。其根本原因在于,數(shù)據(jù)被物理隔離在不同分片,單體數(shù)據(jù)庫的offset和size語義在分布式全局視角下已經(jīng)失效,必須引入跨節(jié)點的排序與數(shù)據(jù)聚合機制。
針對這個問題,一種直接的思路是:將查詢請求廣播至所有相關(guān)分片,獲取各分片的數(shù)據(jù)后,在中間件層進行全局排序,最后根據(jù)分頁參數(shù)截取目標數(shù)據(jù)返回。這就是全局查詢法。
假設(shè)我們有一個分頁查詢需求:
SELECT * FROM order_tab ORDER BY id LIMIT 4 OFFSET 2;假設(shè)訂單數(shù)據(jù)按照user_id % 2被分到了order_tab_0和order_tab_1兩張表中。此查詢的復(fù)雜性根源于數(shù)據(jù)在分片中的不確定性分布。例如,全局偏移量為2、數(shù)量為4的這批數(shù)據(jù),可能存在以下幾種極端情況:
- 所有目標數(shù)據(jù)(包括被
OFFSET跳過的數(shù)據(jù)和最終返回的數(shù)據(jù))全部位于order_tab_0中。
7
- 被
OFFSET跳過的2條數(shù)據(jù)位于order_tab_0,而最終需要的4條數(shù)據(jù)位于order_tab_1。
8
- 偏移量和目標數(shù)據(jù)均勻或不均勻地散落在兩個分片中。
9
為應(yīng)對這種不確定性,并確保結(jié)果的完備性,中間件必須采用一種“寧可錯撈,不可漏過”的策略。它會將原始SQL改寫為對所有分片都有效的查詢。
改寫的邏輯是:LIMIT size OFFSET offset -> LIMIT size + offset OFFSET 0。
-- 改寫后的SQL,下發(fā)到order_tab_0和order_tab_1
SELECT * FROM order_tab ORDER BY id LIMIT 6 OFFSET 0; -- (4 + 2 = 6)中間件會從兩個分片中各自取出前6條數(shù)據(jù),然后在內(nèi)存中進行歸并排序,最后再從排序后的結(jié)果集中,跳過前8條,取出4條數(shù)據(jù)作為最終結(jié)果返回給客戶端。
應(yīng)對面試知道方法是遠遠不夠的,在介紹完這種方法之后,一定要分析這種方案的缺點,為引出我們后續(xù)的優(yōu)化方案做準備。你可以這樣來分析:“這種方案雖然能保證數(shù)據(jù)的絕對準確,但其性能隱患是巨大的,尤其是在深度分頁(OFFSET值很大)的場景下主要有以下三大性能損耗?!?/span>
- 網(wǎng)絡(luò)開銷劇增:為了量化其網(wǎng)絡(luò)開銷,我們進行如下分析。假設(shè)查詢
LIMIT 10 OFFSET 10000,在單庫中,數(shù)據(jù)庫只需傳輸10條記錄。但在分庫分表場景下,假設(shè)我們有10個分片,每個分片都需要傳輸10000 + 10條記錄到中間件。如果每條記錄是1KB,那么總傳輸量將是10 * 10010 * 1KB約等于 97.7MB。而真正有用的數(shù)據(jù)只有10 * 1KB = 10KB。為了獲取10KB的有效數(shù)據(jù),產(chǎn)生了近100MB的無效網(wǎng)絡(luò)傳輸,資源利用效率極低。
- 內(nèi)存消耗巨大:中間件需要將這近100MB的數(shù)據(jù)全部加載到內(nèi)存中進行排序。隨著分頁越深,所需內(nèi)存越大,極易引發(fā)OOM(內(nèi)存溢出),尤其是在Proxy模式下,會導致整個代理服務(wù)集群響應(yīng)緩慢甚至崩潰。
- CPU 負載過高:排序本身是CPU密集型操作。當數(shù)據(jù)量巨大時,會導致中間件節(jié)點的CPU負載急劇升高,成為整個系統(tǒng)的性能瓶頸。
介紹完缺陷之后還可以補充一個優(yōu)化點以展示對問題理解的深度。
“雖然該方案存在明顯瓶頸,但在工程實踐中,可以利用歸并排序的特性進行優(yōu)化。由于從各分片獲取的數(shù)據(jù)本身已有序,無需將所有數(shù)據(jù)一次性加載到內(nèi)存。可采用多路歸并(Multiway Merge Sort)算法,維護一個大小為N(分片數(shù))的最小堆(Min-Heap),堆中存放每個分片當前待處理的記錄。每次從堆頂取出全局最小的記錄,然后將該記錄所在分片的下一條記錄補充到堆中并調(diào)整堆。這個過程可以流式進行,顯著降低內(nèi)存消耗,直到獲取到
size條目標數(shù)據(jù)為止。”
10
這個補充回答,能體現(xiàn)出對問題細節(jié)的深入思考。但這依然沒有從根本上解決深度分頁時數(shù)據(jù)傳輸量過大的問題,因此我們需要探索更高效的架構(gòu)方案。
3. 面試實戰(zhàn)指南
既然全局聚合排序方案存在性能瓶頸,這就需要通過更優(yōu)化的設(shè)計來達成目標。這也是面試的時候凸顯你個人能力的地方,這里主要有以下三種方案可以跟面試官討論
3.1 禁用跳頁查詢法
這是目前業(yè)界在移動端無限滾動(Infinite Scroll)等場景下最主流、最高效的方案。其核心思想是:在產(chǎn)品設(shè)計上約束分頁行為,放棄跳轉(zhuǎn)到任意頁的功能,只允許用戶逐頁向后加載。也就是通過犧牲一定的用戶體驗來保證高性能
在這種交互模式下,我們不再需要OFFSET。取而代之的是,每次請求下一頁數(shù)據(jù)時,客戶端都需要帶上上一頁最后一條記錄的排序鍵的值。
假設(shè)我們按ID升序分頁,每頁50條:
- 第一頁查詢:
-- 首次加載,沒有max_id
SELECT * FROM order_tab ORDER BY id LIMIT 50;客戶端拿到這50條數(shù)據(jù)后,記錄下最后一條數(shù)據(jù)的ID,比如是1050。
- 第二頁查詢:
-- 加載下一頁時,從上一頁的終點繼續(xù)
SELECT * FROM order_tab WHERE id > 1050 ORDER BY id LIMIT 50;這樣,無論翻到多少頁,SQL語句中的LIMIT部分始終是固定的50,而OFFSET永遠是0。查詢性能極其穩(wěn)定,且與分頁深度完全無關(guān)。如果排序鍵是降序的,比如按時間倒序,那么查詢條件就變?yōu)?nbsp;WHERE create_time < last_create_time。
處理復(fù)合排序鍵:如果排序規(guī)則是 ORDER BY create_time DESC, id DESC,WHERE條件就需要更嚴謹?shù)倪壿媮硖幚?,以避免排序謬誤:
WHERE (create_time < last_create_time) OR (create_time = last_create_time AND id < last_id)這個細節(jié)非常關(guān)鍵,能體現(xiàn)出對問題的深入思考和方案的完備性。還是老規(guī)矩,介紹完方案之后一定要分析其優(yōu)缺點
優(yōu)點:性能極高且穩(wěn)定,實現(xiàn)簡單,完美契合移動端的交互習慣。
缺點:犧牲了用戶自由跳轉(zhuǎn)頁碼的功能。
這個方案是解決這類場景的關(guān)鍵技術(shù)之一,雖然看似犧牲了一定的用戶體驗,但大多數(shù)情況下用戶卻還是可以接受的。這種思想在架構(gòu)設(shè)計中具有重要價值,它展現(xiàn)了如何通過優(yōu)化產(chǎn)品交互來規(guī)避復(fù)雜的技術(shù)難題,體現(xiàn)了技術(shù)方案與業(yè)務(wù)場景深度結(jié)合的架構(gòu)思維。
3.2 二次查詢法(亮點方案一)
這個時候面試官為了考察你的技術(shù)深度,可能會接著挑戰(zhàn)
“如果業(yè)務(wù)場景(例如后臺管理系統(tǒng))在產(chǎn)品設(shè)計上必須要求精確的跳頁功能,怎么辦?
這個時候就輪到我們的第一個亮點方案出場了——二次查詢法。這個方案的邏輯較為復(fù)雜,你如果能在面試中通過一些示例簡潔的講清楚,將成為一個重要的技術(shù)亮點。
我們用一個例子來拆解其步驟,假設(shè)一個DB中保存了用戶年齡數(shù)據(jù),從1歲到30歲,共有30條記錄。如果需要查詢這些數(shù)據(jù),可能會執(zhí)行以下SQL語句:
select * from T order by age limit 5 offset 10那么會返回以下紅色標識數(shù)據(jù),即[11,15],請記住此結(jié)果,下面會講解怎么分庫查詢以下結(jié)果
11
把以上所有數(shù)據(jù)分片存儲到3個分庫中,如下,注意下面數(shù)據(jù)只是用戶屬性年齡,不是分片鍵:
12
根據(jù)前面分析,在單一數(shù)據(jù)庫中執(zhí)行 LIMIT 5 OFFSET 10 查詢時,返回的結(jié)果是[11-15]。那么,如果在這三個分庫中進行全局查詢 LIMIT 5 OFFSET 10,該如何操作呢?
- 語句改寫
將以下SQL
select * from T order by age limit 5 offset 10改寫為:
select * from T order by age limit 5 offset 3在所有分庫執(zhí)行修改之后的sql,注意,這個 offset 的 3,來自于全局offset的總偏移量 10,除以水平切分數(shù)據(jù)庫個數(shù) 3。
執(zhí)行select * from T order by age limit 5 offset 3,結(jié)果如下(紅色標識數(shù)據(jù)),為了便于理解用淺綠色標識庫表前三條數(shù)據(jù):
13
- 找到返回數(shù)據(jù)的最小值
第一個庫,5 條數(shù)據(jù)的 age 最小值是10
第二個庫,5 條數(shù)據(jù)的 age 最小值是 6
第三個庫,5 條數(shù)據(jù)的 age 最小值是 12
14
三頁數(shù)據(jù)中,只需要比較各個分庫第一條數(shù)據(jù)[10,6,12],因此age最小值來自第二個庫,age_min=6,時間復(fù)雜度很低
- 查詢二次改寫
第一次改寫的SQL語句是select * from T order by age limit 5 offset 3 第二次要改寫成一個between語句,between的起點是age_min,between的終點是原來每個分庫各自返回數(shù)據(jù)的最大值:
- 第一個分庫,第一次返回數(shù)據(jù)的最大值是22 所以查詢改寫為
select * from T order by age where age between age_min and 22- 第二個分庫,第一次返回數(shù)據(jù)的最大值是20 所以查詢改寫為
select * from T order by age where age between age_min and 20- 第三個分庫,第一次返回數(shù)據(jù)的最大值是25 所以查詢改寫為
select * from T order by age where age between age_min and 25相對于第一次查詢,第二次查詢的條件放寬了,因此第二次查詢會返回比第一次查詢結(jié)果集更多的數(shù)據(jù)。假設(shè)這三個分庫返回的數(shù)據(jù)如下:

可以看到:
- 分庫一的結(jié)果集比第一次查詢多返回了一條數(shù)據(jù),即藍色記錄7。
- 由于
age_min是從原來的分庫二獲取的,因此分庫二的返回結(jié)果集與第一次查詢相同,實際上這次查詢可以省略。 - 分庫三的結(jié)果集比第一次查詢多返回了三條數(shù)據(jù),即深藍色記錄8、9、11。
- 找到age_min在全局的offset
注:offset表示所查詢的結(jié)果集中前面有多少條數(shù)據(jù)沒有被查詢。
在每個結(jié)果集中虛擬一個age_min記錄,找到age_min在各個分庫的offset,也就是找到age_main在各個分庫中前面有多少條數(shù)據(jù)。
16
在分庫1中age_min的offset為2,分庫2中age_min的offset為3,分庫3中age_min的offset為0。
所以age_min的全局offset為:2+3+0=5。
- 查找最終數(shù)據(jù)
既然已經(jīng)確定了全局的 age_min 為偏移量5,因此可以獲得全局的視角。根據(jù)第二次查詢的結(jié)果集。
各分庫二次查詢結(jié)果如下:
分庫1:7、10、14、16、21、22
分庫2:6、13、17、19、20
分庫3:8、9、11、12、15、18、23、25
統(tǒng)一放到list排序后:[6、7、8、9、10、11、12、13、14、15、16、17、18、19、20、21、22、23、25],得知最小值age_main當前的全局offset為5,即6前面有5條數(shù)據(jù)了,最終結(jié)果要取offset 10 limit 5,即要保證前面有10條數(shù)據(jù)不會被取到,所以偏移量offset要往后再移動5個單位到11,這樣前面就有10條數(shù)據(jù)不會被取到了。查詢的limit為5,然后再向后取5位,那就是[11、12、13、14、15],圖中黃色標識數(shù)據(jù)就是我們要找的結(jié)果集。
17
3.2.1 二次查詢法的限制條件
雖然二次查詢法在一定程度上能提高跨庫分頁的性能,但是卻有著嚴格的限制條件,這個前提條件就是某1頁的數(shù)據(jù)要均攤到各分表,換句話說就是二次查詢法不太適用于數(shù)據(jù)分布不均勻,比如數(shù)據(jù)大量集中在某一張表,而其他的分表數(shù)據(jù)量少,分段法的分表策略同樣不適用,下面就用實際例子來看一下:
- 場景1(分表策略:取模法)
原序列:(1,2,3,4,5,6,7,8),需要取出limit 2,2 ,即:(3,4)
第1次查詢
(1,3,5,7) -> limit 2,2 -> 改寫成 limit 1,2 -> (3,5)
(2,4,6,8) -> limit 2,2 -> 改寫成 limit 1,2 -> (4,6)
最小值min為3
第2次查詢
(1,3,5,7) -> between 3 and 5 -> (3,5)
(2,4,6,8) -> between 3 and 6 -> (4,6)
將第2次查詢的結(jié)果合并:
(3,4,5,6) ->根據(jù)規(guī)則,推算出最小值3的全局的offset為2,不需要向后移動,然后取limit即2個元素 -> (3,4) ,結(jié)果正確。
- 場景2(分表策略:取模法)
原序列:(1,2,3,4,5,6,7,8),需要取出limit 1,2 ,即:(2,3)
第1次查詢
(1,3,5,7) -> limit 1,2 -> 改寫成 limit 0,2 -> (1,3) --注:因為1/2除不盡,這里向下取整
(2,4,6,8) -> limit 1,2 -> 改寫成 limit 0,2 -> (2,4)
最小值min為1
第2次查詢
(1,3,5,7) -> between 1 and 3 -> (1,3)
(2,4,6,8) -> between 1 and 4 -> (2,4)
將第2次查詢的結(jié)果合并:
(1,2,3,4) -> 根據(jù)規(guī)則,推算出最小值1的全局的offset為0,向后移2位,然后取limit即2個元素 -> (3,4) ,結(jié)果正確。
- 場景3(分表策略:分段法)
原序列(1,2,3,4,5,6,7,8)->(1,2,3,4),(5,6,7,8)
原序列:(1,2,3,4,5,6,7,8),需要取出limit 2,2 ,即:(3,4)
第1次查詢
(1,2,3,4) -> limit 2,2 -> limit 1,2 -> {2,3} --注:這里就已經(jīng)把正確的數(shù)據(jù)給丟掉了。
(5,6,7,8) -> limit 2,2 -> limit 1,2 -> {6,7} --注:這一段里根本就沒有這一頁的數(shù)據(jù)。
最小值min為2
第2次查詢
(1,2,3,4) -> between 2 and 3 -> {2,3}
(5,6,7,8) -> between 2 and 7 -> {5,6,7}
(2,3,5,6,7) -> (3,5) 根據(jù)規(guī)則,推算出最小值2的全局的offset為1,向后移1為,然后取limit即2個元素 -> (3,5) ,結(jié)果正確這個跟預(yù)期結(jié)果就對不上了。
整體來看,二次查詢法還是相對復(fù)雜的,所以在跟面試官介紹的時候,能用示例來說明很重要,如果有屏幕或者有白紙可以演示的話更好,這樣更容易講清楚。在介紹完方案之后,你可以再總結(jié)一下二次查詢的優(yōu)缺點。
總的來說,二次查詢法通過兩次查詢,犧牲了一定的響應(yīng)時間,換來了分頁的絕對精確性。但是其缺點也很明顯,需要進行兩次數(shù)據(jù)庫查詢,并且有一定的限制,要求數(shù)據(jù)均勻分布,即某一頁的數(shù)據(jù)要均攤到各分表,否則查詢結(jié)果就不精確。它的優(yōu)點就是不會對業(yè)務(wù)造成局限性,每次返回的數(shù)據(jù)量都很有限,不會隨著翻頁增加數(shù)據(jù)的返回量。
3.3 引入中間表(亮點方案二)
中間表方案的核心是“空間換時間”思想。我們創(chuàng)建一個獨立的、未分片的“索引表”或“物化視圖”,該表僅存儲用于排序的字段和主鍵ID。
image
例如,要按更新時間排序,我們的索引表結(jié)構(gòu)可以是 (主鍵, 更新時間, 目標庫)。
當需要分頁查詢時:
- 先在這個單體的索引表上執(zhí)行分頁查詢,由于是單表,
LIMIT OFFSET性能很高。
-- 在索引表上輕松定位
SELECT primary_key, target_db_shard FROM index_table ORDER BY update_time DESC LIMIT 10 OFFSET 100;- 拿到目標數(shù)據(jù)的
primary_key和它們所在的分片信息后,再通過IN查詢回到各個分片庫中,撈取完整的數(shù)據(jù)詳情。
這個方案有兩個顯著的挑戰(zhàn):數(shù)據(jù)一致性維護和查詢能力的限制。
- 數(shù)據(jù)一致性:這個方案最大的挑戰(zhàn)在于如何維護索引表與主數(shù)據(jù)表之間的數(shù)據(jù)一致性。
同步雙寫:在業(yè)務(wù)代碼中同時寫入主表和索引表。這會增加業(yè)務(wù)邏輯的復(fù)雜度和響應(yīng)時間,且存在分布式事務(wù)問題。
異步復(fù)制:更優(yōu)化的方式是,通過訂閱數(shù)據(jù)庫的binlog(如使用Canal等工具),異步地將數(shù)據(jù)變更同步到索引表中。這能與業(yè)務(wù)邏輯解耦,但會存在一定的延遲,即數(shù)據(jù)一致性是最終一致性。

這里也是面試官最容易問的地方,有經(jīng)驗的面試官很可能會追問:“異步更新失敗了怎么辦?”,還是萬變不離其宗,只要失敗,就可以補償。
你可以回答:“我們會引入具備重試與死信隊列(DLQ)的機制。如果多次重試后仍然失敗,則消息進入死信隊列,并觸發(fā)告警,由人工介入進行數(shù)據(jù)校準?!?/span>
- 查詢能力限制:另一個重要的限制就是查詢能力有限。一個重要的操作限制是,任何過濾條件(
WHERE子句)都必須基于索引表中已存在的列。如果用戶需要根據(jù)一個未被物化的字段進行篩選,此方案將失效,除非向索引表添加更多列,但這會增加存儲和維護的成本。
3.4 引入外部存儲
當排序和篩選條件非常復(fù)雜時,以上所有基于關(guān)系型數(shù)據(jù)庫的方案可能都難以應(yīng)對。此時,一個更通用的架構(gòu)選擇是引入外部系統(tǒng)。
- 引入搜索引擎:將需要查詢的數(shù)據(jù)(全量或部分字段)通過雙寫或
binlog同步的方式,寫入到Elasticsearch這樣的專業(yè)搜索引擎中。分頁、排序、復(fù)雜篩選等查詢請求,全部交由Elasticsearch處理。Elasticsearch基于Lucene構(gòu)建,其倒排索引和分布式聚合能力使其在處理這類需求時具備天然優(yōu)勢。從ES獲取ID后,再回源數(shù)據(jù)庫查詢詳情。這是一種非常成熟的異構(gòu)索引方案。 - 引入分布式關(guān)系型數(shù)據(jù)庫:另一種思路是采用NewSQL數(shù)據(jù)庫,如TiDB。TiDB的底層存儲引擎TiKV是一個全局有序的分布式鍵值存儲,這使得它能夠在分布式環(huán)境下高效地處理
ORDER BY和LIMIT操作,將分頁的復(fù)雜性下沉到了數(shù)據(jù)庫內(nèi)核層面,對應(yīng)用層透明。
4. 小結(jié)
從最初的全局查詢,到禁用跳頁、二次查詢、引入中間表,再到借助外部存儲,可以看到分頁查詢在分庫分表架構(gòu)下并不是一個簡單的 SQL 問題,而是牽涉到性能、數(shù)據(jù)一致性、交互設(shè)計等多維度的系統(tǒng)性挑戰(zhàn)。沒有放之四海而皆準的最佳方案,關(guān)鍵在于理解業(yè)務(wù)場景的需求邊界,然后在可接受的性能與復(fù)雜度之間做出取舍。面試中,能否系統(tǒng)性地剖析問題、提出不同層次的解決思路,并清晰地權(quán)衡優(yōu)缺點,往往比直接給出某個答案更能打動面試官。


































