:分片鍵選型與遷移避坑指南)
做 MySQL 核心存儲的團隊只要數(shù)據(jù)量和并發(fā)一起來幾乎都會遇到同一個靈魂拷問到底要不要上分庫分表如果要上是垂直分庫還是水平分表分片鍵選誰才不會踩雷這些坑我都在生產(chǎn)環(huán)境里踩過。今天這篇就按自己的復(fù)盤經(jīng)驗把分庫分表路上最容易翻車的地方攤開講透什么時候該拆、三種拆分方式怎么選、分片鍵怎么定、遷移分幾步走以及上線之后最常見的幾個深坑怎么排查。適合正在評估拆分方案、或者已經(jīng)被線上大表壓得喘不過氣的開發(fā)同學(xué)也適合準備做數(shù)據(jù)庫架構(gòu)升級的團隊當(dāng)參考。1. 先別急著拆分庫分表是一筆“高成本負債”1.1 什么信號出現(xiàn)才該動手分庫分表和加索引、調(diào)SQL不一樣它不是一項“性能優(yōu)化手段”而是一次存儲架構(gòu)的重新設(shè)計。一旦上車SQL寫法要改、事務(wù)邊界要改、查詢習(xí)慣要改連運維方式都要跟著變。所以我的第一個建議是先用門檻擋住絕大多數(shù)不該拆的場景。什么時候才真正需要考慮拆分我從實際運維中總結(jié)了四個硬信號至少命中兩三個再動手單表行數(shù)超過千萬級別且持續(xù)增長索引層級變深寫入和查詢都明顯退化數(shù)據(jù)庫連接數(shù)長期打滿應(yīng)用側(cè)不斷報Too many connections加連接數(shù)上限也沒用高峰期 QPS 或 TPS 已經(jīng)逼近單實例硬件上限CPU 常年 70% 以上SQL 優(yōu)化和參數(shù)調(diào)優(yōu)都試過了寫入容量成為瓶頸比如訂單、日志、流水這類只增不改的數(shù)據(jù)單庫磁盤和寫入速度都跟不上業(yè)務(wù)增速。這里有個容易誤判的地方單表 500 萬行、偶爾慢查詢不一定需要分表。很多慢查詢純粹是索引沒建對或者 SQL 寫法本身有問題。我見過一個項目一張表兩千萬數(shù)據(jù)業(yè)務(wù)方嚷嚷著要拆表結(jié)果我一看日志大部分慢查詢都是因為LIKE %xxx%這種無法走索引的寫法。把查詢改寫加聯(lián)合索引之后性能直接翻了十倍分表需求自然消失了。1.2 常見的兩種誤判緩存能解決的事別用分庫分表扛除非數(shù)據(jù)庫已經(jīng)到了物理極限否則優(yōu)先考慮其他手段。這里說兩個我反復(fù)跟團隊強調(diào)的“不要把分庫分表當(dāng)萬金油”的場景。第一是讀多寫少的場景。如果 90% 流量是查詢先上緩存。Redis 或本地緩存可以扛掉絕大部分讀壓力數(shù)據(jù)庫壓力會大幅下降。分庫分表解決的是“存儲容量”和“寫入吞吐”的問題不是“傻讀”的問題。你用分庫分表去頂讀流量相當(dāng)于雇了一整支裝修隊來換一個燈泡。第二是歷史數(shù)據(jù)歸檔沒做。很多業(yè)務(wù)表 90% 的數(shù)據(jù)是三年以上的冷數(shù)據(jù)平時根本不會被查詢命中。這種場景正確的做法是先做冷熱分離——把半年或一年前的數(shù)據(jù)遷到歸檔表、歸檔庫甚至直接放到數(shù)倉或?qū)ο蟠鎯?。我拆過一個用戶操作日志表拆完發(fā)現(xiàn)其中 80% 是兩年前的過期日志早該歸檔了白白為這些冷數(shù)據(jù)花了大量分庫分表的成本。1.3 從單庫到分布式存儲的整體演進路線分庫分表不是一步到位的業(yè)界踩了這么多年坑基本沉淀出一條比較穩(wěn)妥的演進路徑先做 SQL 優(yōu)化和索引優(yōu)化把能省的資源都省下來引入緩存層解決熱點讀引入讀寫分離主庫寫、從庫讀分攤讀壓力做垂直分庫按業(yè)務(wù)域把不同模塊拆到不同庫做垂直分表把單個大表的熱點字段和冷字段拆開最后才做水平分表解決單表數(shù)據(jù)量過大和單庫寫入瓶頸的問題。這個順序本質(zhì)上是按照“成本從低到高、收益從明顯到隱蔽”來排的。前面幾步做完很多系統(tǒng)已經(jīng)能扛住相當(dāng)高的并發(fā)根本走不到最后一步。反過來如果跳過前幾步直接上水平分表你會發(fā)現(xiàn)拆完之后 SQL 不能隨便 join 了、事務(wù)要改分布式事務(wù)了而數(shù)據(jù)庫本身的問題比如一條爛 SQL 沒優(yōu)化依然存在只是被你拆碎了而已。2. 垂直分庫、垂直分表、水平分表三種拆分一次講清2.1 垂直分庫按業(yè)務(wù)域把不同的表拆到不同的庫垂直分庫的核心思路是“按業(yè)務(wù)模塊切分數(shù)據(jù)庫”。比如一個電商系統(tǒng)用戶、訂單、商品原來在同一個庫mall_db里現(xiàn)在拆成user_db、order_db、product_db三個庫分別歸屬用戶中心、訂單中心、商品中心。這種拆分通常伴隨微服務(wù)化進行訂單服務(wù)的庫只允許訂單服務(wù)訪問其他服務(wù)要想拿數(shù)據(jù)必須通過接口調(diào)取而不是直接連庫。所以垂直分庫解決的不僅是單庫容量和連接數(shù)壓力更重要的是劃清了數(shù)據(jù)邊界避免一個庫塞滿所有業(yè)務(wù)表、互相爭搶連接和 IO。垂直分庫的難點不在技術(shù)而在組織架構(gòu)。如果你們的服務(wù)還是單體應(yīng)用拆了庫之后代碼還是全部寫在一起那么每次查詢都要跨庫、每個事務(wù)都要跨庫復(fù)雜度會陡增。我的建議是垂直分庫必須跟在服務(wù)拆分后面走業(yè)務(wù)邊界沒理清楚之前不要動庫。2.2 垂直分表把一張大表的字段拆成兩張或多張表垂直分表是把一張列很多的表按照字段的訪問頻率和關(guān)聯(lián)度拆成多張表。最常見的是“主表 擴展表”模式。舉個例子用戶表user有 30 個字段其中id、name、mobile、status這類字段每次查詢都要用到是熱數(shù)據(jù)而avatar、signature、last_login_ip、register_source這類字段很少用但是占存儲空間是冷數(shù)據(jù)。那就拆成user_main和user_extend兩張表通過user_id關(guān)聯(lián)。這里有個細節(jié)容易被忽略垂直分表拆的是“行寬度”它不能解決單表行數(shù)過多的問題。想象一張表是 1000 萬行、每行 2KB另一張表同樣是 1000 萬行、每行 100 字節(jié)前者的掃描成本遠高于后者。垂直分表就是通過“瘦身”讓單頁能容納更多行從而降低 IO 和緩存淘汰率。但對于行數(shù)爆炸導(dǎo)致的寫入瓶頸它無能為力。2.3 水平分表按行拆分才是解決“大表”的根本手段水平分表是把同一張表的行數(shù)據(jù)按照某個字段的取值分散到多張結(jié)構(gòu)完全相同的子表中。比如訂單表按user_id模 16拆成order_0到order_15共 16 張表。水平分表的意義在于單表行數(shù)被控制在可接受范圍內(nèi)索引體積減小B樹層級變淺寫入和查詢都能保持穩(wěn)定。同時如果把不同子表放到不同庫的不同實例上還能同時攤薄存儲和計算壓力。這也是標題里“水平分表”和“垂直分庫”并列出現(xiàn)的原因——它們一個解決“表太大”一個解決“庫太雜”是不同維度的問題實踐中最優(yōu)方案往往是兩者組合。2.4 三種拆分方式對比速查表對比維度垂直分庫垂直分表水平分表拆分維度按業(yè)務(wù)模塊按字段冷熱按行數(shù)據(jù)范圍/哈希解決的核心問題連接數(shù)、單庫容量、業(yè)務(wù)邊界單表行寬、IO浪費單表行數(shù)多、寫入瓶頸拆分對象庫與表單張寬表單張超大數(shù)據(jù)量表對應(yīng)用透明性需改造訪問方式需改造SQL需引入路由層典型代價分布式事務(wù)、接口調(diào)用需要額外一次查詢跨分片查詢、擴容麻煩適合場景微服務(wù)架構(gòu)下的多業(yè)務(wù)域字段多、有冷熱之分的大寬表日志、訂單、流水等持續(xù)增長的表一句話總結(jié)垂直是“分家”把不同類型的東西分開水平是“分灶”把同一類的東西均勻分配到多個鍋里。兩者不沖突真正的生產(chǎn)架構(gòu)里經(jīng)常先用垂直分庫把服務(wù)邊界劃開再對核心大表做水平分表最后配合垂直分表把寬表瘦身。3. 分片鍵選型決定成敗的關(guān)鍵決策3.1 為什么說分片鍵是“第一重要決策”分片鍵Sharding Key是水平分表時用來決定某一行數(shù)據(jù)應(yīng)該落在哪張子表的字段。它決定了路由的效率和查詢的方式選錯后面所有環(huán)節(jié)都會跟著變形。我見過最典型的翻車案例某團隊訂單表按order_id做哈希分片結(jié)果業(yè)務(wù)上大量查詢是“查某個用戶最近的訂單”。查詢條件里沒有order_id只有user_id于是每次都要把 16 張子表全掃一遍再合并。分表之后查詢反而變慢了團隊一度想要回滾。這里本質(zhì)問題在于路由規(guī)則只認識order_id但業(yè)務(wù)高頻查詢不認識order_id。數(shù)據(jù)被均勻分散到了各個分片卻無法從查詢條件定位到具體分片分布式架構(gòu)最核心的“數(shù)據(jù)本地性”優(yōu)勢直接作廢。3.2 分片鍵的五條選擇標準根據(jù)實際經(jīng)驗一個合格的分片鍵應(yīng)該同時滿足以下條件查詢覆蓋率高高頻查詢條件里必須帶上這個字段最好能直接定位到一個分片區(qū)分度足夠高字段取值足夠分散不能像status只有幾個可選值否則某個分片會堆積大量數(shù)據(jù)穩(wěn)定不可變字段值一旦生成就不會修改。否則用戶更換手機號后原來按手機號分片的數(shù)據(jù)定位全部失效產(chǎn)生方式有規(guī)律ID 類字段要有全局唯一性且最好由應(yīng)用層或者發(fā)號器生成而不是依賴數(shù)據(jù)庫自增主鍵訪問均勻、無熱點字段的取值分布要均勻盡量避免少數(shù)值貢獻了絕大多數(shù)流量。實際項目里不會有一個字段完美命中所有標準但當(dāng)業(yè)務(wù)不止一個高頻查詢維度時就要根據(jù)業(yè)務(wù)權(quán)重做取舍。電商的訂單表就是最典型的例子買家會高頻查“我的訂單”賣家會高頻查“店鋪訂單”運營還會按訂單號精查。三個維度互相沖突不可能同時作為分片鍵。這時候要回到業(yè)務(wù)本質(zhì)看哪個維度流量最大、SLA要求最高舍棄部分查詢的可路由性用“基因法”或者數(shù)據(jù)冗余來補償。3.3 三種主流路由方案哈希取模、范圍分片、一致性哈希分片鍵定好之后還要選路由算法。路由算法決定了“按分片鍵的值計算出數(shù)據(jù)落在哪張子表”的具體規(guī)則。哈希取模是最簡單直接、也是最常用的方案。分片鍵的值經(jīng)過哈希函數(shù)映射后對分片總數(shù)取模。比如總分成 16 片hash(user_id) % 16結(jié)果落在 0 到 15 之間對應(yīng)table_0到table_15。# 偽代碼應(yīng)用層路由邏輯 def route(user_id: int, db_num: int, table_num: int): total db_num * table_num idx hash(user_id) % total # 邏輯分片號 db idx // table_num # 物理庫編號 table idx % table_num # 物理表編號 return fdb_{db}.table_{table}哈希取模的優(yōu)點是人話能聽懂的簡單數(shù)據(jù)分布也均勻。最大缺點是分片數(shù)一旦確定后續(xù)擴容極其痛苦。從 16 片擴成 32 片絕大多數(shù)數(shù)據(jù)的路由結(jié)果都會變化意味著存量數(shù)據(jù)要大面積遷移。如果沒有一套完整遷移方案擴容就是一次小規(guī)?!皳Q庫手術(shù)”。范圍分片是按分片鍵的值區(qū)間劃分比如訂單號 1 到 1000 萬放在table_01000 萬到 2000 萬放在table_1。這種方案天然便于范圍查詢擴容時只需要新增一片不用動舊數(shù)據(jù)。缺點是數(shù)據(jù)分布可能嚴重傾斜月初訂單少月底訂單多熱點全壓在一張表上。所以范圍分片比較適合時間序列數(shù)據(jù)比如日志、流水不適合高并發(fā)訂單。一致性哈希是為了解決“增加/減少節(jié)點時遷移量過大”而設(shè)計的。它把哈希值空間組織成一個環(huán)形結(jié)構(gòu)每個分片負責(zé)環(huán)上一段區(qū)間數(shù)據(jù)落到哪個分片由它在環(huán)上的位置決定。節(jié)點增減時只有相鄰分片的數(shù)據(jù)需要遷移。為了均衡分布一致性哈希還引入“虛擬節(jié)點”概念讓每個物理節(jié)點在環(huán)上擁有多個位置。但一致性哈希的實現(xiàn)復(fù)雜度高、排查路由相對困難MySQL 分庫分表里用好的人遠遠少于用哈希取模的。三種方案我用一張表收一下路由方案數(shù)據(jù)分布特點擴容代價實現(xiàn)復(fù)雜度適用場景哈希取模均勻高需遷移大量數(shù)據(jù)低大多數(shù)在線業(yè)務(wù)范圍分片易傾斜低只需新增分片低日志、時序流水一致性哈希較均勻中只遷移相鄰數(shù)據(jù)高節(jié)點頻繁變化的分布式系統(tǒng)3.4 分片鍵選錯后怎么補救如果上線后發(fā)現(xiàn)分片鍵選錯了不要慌有幾個止損手段。小規(guī)模業(yè)務(wù)可以用“索引表”兜底單獨建一張映射表保存分片鍵和行 id 的對應(yīng)關(guān)系。比如訂單表按user_id分片但運營經(jīng)常用order_id查單那就維護一張order_user_index表通過order_id查出user_id再定位到具體分片。代價是每次精查多一次索引表查詢但至少能保證功能可用。另一種思路是“基因法”。設(shè)計主鍵時把分片鍵的某些位種進主鍵里。比如order_id的末尾 4 位取自user_id的末尾 4 位這樣拿到order_id就能直接算出user_id對應(yīng)的分片號不需要額外查詢。這個方案很棒但是要求生成 ID 的規(guī)則從一開始就設(shè)計好中途很難改。最實在的提醒是分片鍵的選型必須在拆分立項階段就拉上所有業(yè)務(wù)方把高頻查詢 SQL 全部列出來逐一確認哪些查詢必須路由到單分片、哪些可以接受跨分片聚合。這一步花一兩天時間能省掉后面半年的返工。4. 從單庫到分庫分表的落地實操四步遷移法4.1 第一步容量預(yù)估與分片數(shù)規(guī)劃動手遷移前第一件事是計算分片數(shù)量。分片數(shù)既不能太少又不宜過壕我的經(jīng)驗公式是按未來三年的業(yè)務(wù)增長預(yù)估數(shù)據(jù)總量再除以單表行數(shù)目標上限。假設(shè)當(dāng)前訂單表 8000 萬行年增速約 50%三年后總量大約 2.7 億行。單表行數(shù)控制在 500 萬到 800 萬行以內(nèi)是相對舒適的狀態(tài)取 800 萬作為閾值那么分片總數(shù) 2.7 億 / 800 萬 ≈ 34 片。為了避免將來擴容我會直接取 2 的冪次方物理上分成 64 片。為什么取 2 的冪次因為分片數(shù)從 64 擴到 128 時只需要把原分片編號的最高位從 0 變 1遷移量恰好是一半數(shù)據(jù)可以用二進制位運算輕松做平滑擴容。這是老 DBA 都懂的“二次擴容”技巧。分片鍵如果選擇user_id那么路由邏輯就是hash(user_id) % 64。為了分攤寫壓力64 張表不要全放在一個實例里建議按 4 庫 × 16 表 或者 8 庫 × 8 表 布局。庫和表的組合方式會影響后面的連接數(shù)分配和運維復(fù)雜度建議單獨畫一張拓撲圖標注清楚每個物理庫上有哪些表、通過哪些分片范圍路由。4.2 第二步全局唯一 ID 生成方案先行分庫分表之后數(shù)據(jù)庫自增主鍵不能再用了——因為每個分片都有自己的自增起點多張表生成的 ID 會重復(fù)全局唯一性無法保證。所以分片鍵如果是主鍵必須在遷移前上線全局 ID 生成器。業(yè)界用得最多的是雪花算法Snowflake ID。一個 64 位的長整型 ID由時間戳、機器編號、序列號組成。它生成的 ID 全局唯一、趨勢遞增、且不依賴數(shù)據(jù)庫。我建議組合使用應(yīng)用內(nèi)嵌雪花算法生成器避免每次生成 ID 都請求外部服務(wù)減少網(wǎng)絡(luò)開銷對于需要嚴格順序的業(yè)務(wù)可以借助數(shù)據(jù)庫發(fā)號器用一個獨立的id_generator表維護多段步長來避免時鐘回撥問題。這里補一個我踩過的坑雪花算法強依賴機器時鐘如果服務(wù)器做了 NTP 時間同步偶爾會出現(xiàn)時鐘回撥導(dǎo)致生成的 ID 可能重復(fù)。解決方法是發(fā)號器內(nèi)部保留上一次生成 ID 時的毫秒時間戳一旦發(fā)現(xiàn)當(dāng)前時間小于上次時間就在內(nèi)存里阻塞等待時鐘追上或者直接為本次生成微調(diào)偏移量。這段邏輯不復(fù)雜但不能省。4.3 第三步雙寫遷移 歷史數(shù)據(jù)全量同步生產(chǎn)環(huán)境做分庫分表遷移絕對不能“停服一刀切”。業(yè)界最穩(wěn)的套路是“雙寫 全量 對賬 切讀”。雙寫的意思是應(yīng)用側(cè)在寫入老庫的同時把同一份數(shù)據(jù)也寫入分庫分表后的新庫。代碼層面可以用 MQ 異步雙寫也可以用 AOP 加一個雙寫切面走一個開關(guān)控制。雙寫階段新庫的寫入路徑就是不間斷接受流量驗證發(fā)現(xiàn)路由 bug 或者寫入失敗馬上關(guān)掉雙寫開關(guān)業(yè)務(wù)無感。全量遷移是指把存量數(shù)據(jù)從老庫導(dǎo)入新庫。這里推薦用工具批量做比如mysqldump按主鍵范圍分批導(dǎo)出再導(dǎo)入到分布式表或者用DataX、Canel等數(shù)據(jù)同步工具。必須按主鍵或者時間游標分批處理避免一次性加載幾千萬行把數(shù)據(jù)庫 IO 打滿。整個過程我建議用流水賬記錄每批遷移多少行、耗時多久、是否有報錯、對賬結(jié)果如何。全量遷完之后老庫可能又新增了一批增量數(shù)據(jù)所以還要有一個增量追平的過程。最簡單的方式是在遷移期間持續(xù)消費 binlog把老庫的增量變更同步到新庫直到兩邊數(shù)據(jù)完全追平。4.4 第四步灰度切流與快速回滾數(shù)據(jù)追平之后開始灰度切流。不要一次把全部流量切到新庫按用戶灰度比如先切 1% 的用戶觀察指標再逐步放大到 10%、50%、100%。切流階段最需要注意兩個指標一是寫入新庫的失敗率二是分布式查詢的耗時和報錯率。我見過團隊把流量切到 50% 之后發(fā)現(xiàn)某些跨分片查詢把數(shù)據(jù)庫連接池打爆了因為舊庫的查詢是一條 SQL 搞定新庫要并發(fā)查 16 張表再合并結(jié)果單位查詢的資源消耗翻了不止一倍。這時候要回頭優(yōu)化查詢邏輯把“并發(fā)查 16 張子表”改成“按業(yè)務(wù)維度路由到 1 張表”或者增加結(jié)果合并層的聚合能力才能繼續(xù)放量?;叶拳h(huán)境里一定要準備一鍵回滾開關(guān)。萬一新庫出了問題把開關(guān)撥回去流量回落到老庫比在代碼里緊急改 bug 快得多。這個開關(guān)最好在架構(gòu)設(shè)計階段就預(yù)留好而不等出問題當(dāng)天再開發(fā)。4.5 實操過程中的數(shù)據(jù)校驗與對賬技巧遷移過程中我強烈建議做三層對賬行數(shù)對賬老庫和新庫每個分片的行數(shù)總和要一致抽樣字段對賬按主鍵抽樣比對關(guān)鍵字段的值是否完全一致指標對賬統(tǒng)計最近時間段的寫入量、查詢量看兩邊是否一致。對賬不是一次性的雙寫期間要持續(xù)跑。我習(xí)慣把對賬結(jié)果輸出到一張監(jiān)控表設(shè)置閾值告警任何不一致立刻告警、立刻凍結(jié)雙寫防止臟數(shù)據(jù)繼續(xù)蔓延。等穩(wěn)態(tài)運行一兩周之后再考慮把老庫降級為只讀備份庫最終歸檔下線。5. 拆分之后躲不開的四大深坑與排查實錄5.1 跨分片 Join不能再用一條 SQL 解決分庫分表之后跨分片 Join 基本不可行。兩個表如果分片鍵不一致數(shù)據(jù)分散在不同的物理庫上SQL 層面無法做 Join應(yīng)用層做內(nèi)存 Join 的成本又高得離譜。我的處理原則是能下沉到數(shù)據(jù)冗余的絕不在應(yīng)用層拼。比如訂單表需要展示商品名稱那就把商品名稱冗余到訂單表里作為冗余字段需要展示買家昵稱就冗余買家昵稱。用空間換 Join這是最直接有效的手段。實在需要實時聚合的可以上異構(gòu)同步方案用 Canal 訂閱 MySQL binlog把數(shù)據(jù)同步到 Elasticsearch 或者 ClickHouse由這些引擎承擔(dān)復(fù)雜的聚合查詢。這個方案我測下來非常穩(wěn)但要注意同步鏈路存在秒級延遲不適合強一致數(shù)據(jù)場景。5.2 跨分片分布式事務(wù)別再迷信強一致性跨分片的事務(wù)想要保持 ACID必須引入分布式事務(wù)方案。常見的有兩階段提交XA、TCC、本地消息表、消息隊列最終一致性。我的實際經(jīng)驗是業(yè)務(wù)側(cè)盡可能避免跨分片事務(wù)把事務(wù)邊界收斂到一個分片內(nèi)。具體怎么收斂在設(shè)計分片鍵階段就要盡量讓一個事務(wù)涉及的數(shù)據(jù)落在同一個分片。比如“下單減庫存”如果訂單表和庫存表分片鍵不一樣那一次事務(wù)就跨了兩個分片如果都按商家 ID 分片同一商家的訂單和庫存天然落在同一個分片事務(wù)就還是本地事務(wù)。如果實在無法避免推薦用“本地消息表 MQ”的方案落地最終一致性不要強行上 XA。XA 在低并發(fā)下看起來很美一旦分片數(shù)增加、事務(wù)參與方變多性能損耗會非常明顯線上很容易出現(xiàn)“鎖等待超時”和“連接吃緊”的問題。5.3 數(shù)據(jù)傾斜哈希取模也不代表絕對均勻很多人以為用了哈希取模就萬事大吉其實哈希只是一種概率均勻。當(dāng)分片鍵的取值空間本身就傾斜時比如某個大客戶的訂單量占據(jù)全站 30%那它的所有數(shù)據(jù)都會被路由到固定的幾個分片形成“熱點分片”。這個熱點分片的磁盤 IO 和查詢壓力遠超其他分片數(shù)據(jù)庫整體就變成了“木桶效應(yīng)”。我處理過的最典型的案例一個 toB 系統(tǒng)按merchant_id分片某頭部商戶的訂單量是普通商戶的上百倍負責(zé)它的那張表被拖垮直接影響全站。后來采用的方案是“分片鍵 業(yè)務(wù)后綴”對超大商戶在分片鍵后面拼接一個 0 到 15 的隨機后綴讓同一個商戶的數(shù)據(jù)也能打散到多張表。查詢時帶后綴批量并行查再合并結(jié)果。代價是代碼復(fù)雜度上去了但能有效救活熱點分片。5.4 擴容遷移數(shù)字上的“擴容”遠比想象中麻煩最后聊一個所有分庫分表系統(tǒng)遲早要面對的擴容。哈希取模分 16 片用了三年數(shù)據(jù)量漲了要擴到 32 片。這時候的問題不是“加幾張表”這么簡單而是存量數(shù)據(jù)的大量路由結(jié)果會變必須在擴容方案里做數(shù)據(jù)遷移。平滑擴容最基礎(chǔ)的方法是“停機擴容”把服務(wù)暫停跑數(shù)據(jù)遷移驗證后再啟動。業(yè)務(wù)允許停機的話這是最簡單、最可控的方式。不可停機的系統(tǒng)可以用“新舊雙路由”方案應(yīng)用層同時維護新舊兩套路由規(guī)則寫數(shù)據(jù)時雙寫查到舊分片的數(shù)據(jù)時從新分片重建并遷移。等數(shù)據(jù)追平切到新路由下線舊路由。整個過程非??简瀳F隊的運維能力所以再次強調(diào)分片數(shù)規(guī)劃一定要留足余量別一開始就規(guī)劃得剛剛好。寫在最后的一些真實體會分庫分表這個方案真的是“拆之前百般猶豫拆之后百般謹慎”。我自己的感覺是技術(shù)上最難的往往不是選型或者寫路由代碼而是業(yè)務(wù)查詢維度的梳理和遷移過程中的耐心。很多團隊倒在了遷移的半路上不是因為技術(shù)不行而是因為沒有做好充分的灰度驗證和數(shù)據(jù)對賬就急于放量。如果這篇文章只能給你留下一個記憶點我希望是這句話分片鍵選型是分庫分表里唯一一個“一開始錯后面全盤錯”的決策點規(guī)劃時間至少要占到整個項目的一半。再實際的建議是團隊在真正動手之前先用模擬數(shù)據(jù)把路由規(guī)則、擴容方案、遷移演練都跑一遍把可能踩的坑盡可能提前暴露在測試環(huán)境。最后補一句分庫分表從來不是數(shù)據(jù)庫問題的終點。真正穩(wěn)定的大規(guī)模系統(tǒng)往往是分庫分表、緩存、歸檔、異構(gòu)查詢、消息隊列共同協(xié)作的結(jié)果單一手段解決不了所有問題。你們團隊現(xiàn)在遇到的大表難題屬于哪一類歡迎帶具體場景來評論區(qū)聊我會挑典型的出來拆解分析。