分庫分表經典15連問

碼農談IT發表於2022-12-13

前言

大家好,我是田螺。我們去面試的時候,幾乎都會被問到分庫分表。田螺哥整理了分庫分表的15道經典面試題,大家看完肯定會有幫助的。

分庫分表經典15連問

1. 我們為什麼需要分庫分表

在分庫分表之前,就需要考慮為什麼需要拆分。我們做一件事,肯定是有充分理由的。所以得想好分庫分表的理由是什麼。我們現在就從兩個維度去思考它,為什麼要分庫?為什麼要分表?

1.1 為什麼要分庫

如果業務量劇增,資料庫可能會出現效能瓶頸,這時候我們就需要考慮拆分資料庫。從這兩方面來看:

  • 磁碟儲存

業務量劇增,MySQL單機磁碟容量會撐爆,拆成多個資料庫,磁碟使用率大大降低。

  • 併發連線支撐

我們知道資料庫連線數是有限的。在高併發的場景下,大量請求訪問資料庫,MySQL單機是扛不住的!高併發場景下,會出現too many connections報錯。

當前非常火的微服務架構出現,就是為了應對高併發。它把訂單、使用者、商品等不同模組,拆分成多個應用,並且把單個資料庫也拆分成多個不同功能模組的資料庫(訂單庫、使用者庫、商品庫),以分擔讀寫壓力。

1.2 為什麼要分表

假如你的單表資料量非常大,儲存和查詢的效能就會遇到瓶頸了,如果你做了很多最佳化之後還是無法提升效率的時候,就需要考慮做分表了。一般千萬級別資料量,就需要分表。

這是因為即使SQL命中了索引,如果表的資料量超過一千萬的話,查詢也是會明顯變慢的。這是因為索引一般是B+樹結構,資料千萬級別的話,B+樹的高度會增高,查詢就變慢啦。MySQL的B+樹的高度怎麼計算的呢?跟大家複習一下:

InnoDB儲存引擎最小儲存單元是頁,一頁大小就是16k。B+樹葉子存的是資料,內部節點存的是鍵值+指標。索引組織表透過非葉子節點的二分查詢法以及指標確定資料在哪個頁中,進而再去資料頁中找到需要的資料,B+樹結構圖如下:

分庫分表經典15連問

假設B+樹的高度為2的話,即有一個根結點和若干個葉子結點。這棵B+樹的存放總記錄數為=根結點指標數*單個葉子節點記錄行數。

如果一行記錄的資料大小為1k,那麼單個葉子節點可以存的記錄數  =16k/1k =16. 非葉子節點記憶體放多少指標呢?我們假設主鍵ID為bigint型別,長度為8位元組(面試官問你int型別,一個int就是32位,4位元組),而指標大小在InnoDB原始碼中設定為6位元組,所以就是 8+6=14 位元組,16k/14B =16*1024B/14B = 1170

因此,一棵高度為2的B+樹,能存放1170 * 16=18720條這樣的資料記錄。同理一棵高度為3的B+樹,能存放1170 *1170 *16 =21902400,大概可以存放兩千萬左右的記錄。B+樹高度一般為1-3層,如果B+到了4層,查詢的時候會多查磁碟的次數,SQL就會變慢。

因此單表資料量太大,SQL查詢會變慢,所以就需要考慮分表啦。

2. 什麼時候考慮分庫分表?

對於MySQLInnoDB儲存引擎的話,單表最多可以儲存10億級資料。但是的話,如果真的儲存這麼多,效能就會非常差。一般資料量千萬級別,B+樹索引高度就會到3層以上了,查詢的時候會多查磁碟的次數,SQL就會變慢。

阿里巴巴的《Java開發手冊》提出:

單錶行數超過500萬行或者單表容量超過2GB,才推薦進行分庫分表。

那我們是不是等到資料量到達五百萬,才開始分庫分表呢?

不是這樣的,我們應該提前規劃分庫分表,如果估算3年後,你的表都不會到達這個五百萬,則不需要分庫分表。

MySQL伺服器如果配置更好,是不是可以超過這個500萬這個量級,才考慮分庫分表?

雖然配置更好,可能資料量大之後,效能還是不錯,但是如果持續發展的話,還是要考慮分庫分表

一般什麼型別業務表需要才分庫分表?

通用是一些流水錶、使用者表等才考慮分庫分表,如果是一些配置類的表,則完全不用考慮,因為不太可能到達這個量級。

3. 如何選擇分表鍵

分表鍵,即用來分庫/分表的欄位,換種說法就是,你以哪個維度來分庫分表的。比如你按使用者ID分表、按時間分表、按地區分表,這些使用者ID、時間、地區就是分表鍵。

一般資料庫表拆分的原則,需要先找到業務的主題。比如你的資料庫表是一張企業客戶資訊表,就可以考慮用了客戶號做為分表鍵

為什麼考慮用客戶號做分表鍵呢?

這是因為表是基於客戶資訊的,所以,需要將同一個客戶資訊的資料,落到一個表中,避免觸發全表路由

4.非分表鍵如何查詢

分庫分表後,有時候無法避免一些業務場景,需要透過非分表鍵來查詢

假設一張使用者表,根據userId做分表鍵,來分庫分表。但是使用者登入時,需要根據使用者手機號來登陸。這時候,就需要透過手機號查詢使用者資訊。而手機號是非分表鍵

非分表鍵查詢,一般有這幾種方案:

  • 遍歷:最粗暴的方法,就是遍歷所有的表,找出符合條件的手機號記錄(不建議
  • 將使用者資訊冗餘同步到ES,同步傳送到ES,然後透過ES來查詢(推薦

其實還有基因法:比如非分表鍵可以解析出分表鍵出來,比如常見的,訂單號生成時,可以包含客戶號進去,透過訂單號查詢,就可以解析出客戶號。但是這個場景除外,手機號似乎不適合冗餘userId

5. 分表策略如何選擇

5.1 range範圍

range,即範圍策略劃分表。比如我們可以將表的主鍵order_id,按照從0~300萬的劃分為一個表,300萬~600萬劃分到另外一個表。如下圖:

分庫分表經典15連問

有時候我們也可以按時間範圍來劃分,如不同年月的訂單放到不同的表,它也是一種range的劃分策略。

  • 優點: range範圍分表,有利於擴容。
  • 缺點:可能會有熱點問題。因為訂單id是一直在增大的,也就是說最近一段時間都是匯聚在一張表裡面的。比如最近一個月的訂單都在300萬~600萬之間,平時使用者一般都查最近一個月的訂單比較多,請求都打到order_1表啦。

5.2 hash取模

hash取模策略:

指定的路由key(一般是user_id、order_id、customer_no作為key)對分表總數進行取模,把資料分散到各個表中。

比如原始訂單表資訊,我們把它分成4張分表:

分庫分表經典15連問
  • 比如id=1,對4取模,就會得到1,就把它放到t_order_1;
  • id=3,對4取模,就會得到3,就把它放到t_order_3;

一般,我們會取雜湊值,再做取餘

Math.abs(orderId.hashCode()) % table_number
  • 優點:hash取模的方式,不會存在明顯的熱點問題
  • 缺點:如果未來某個時候,表資料量又到瓶頸了,需要擴容,就比較麻煩。所以一般建議提前規劃好,一次性分夠。(可以考慮一致性雜湊

5.3 一致性Hash

如果用hash方式分表,前期規劃不好,需要擴容二次分表,表的數量需要增加,所以hash值需要重新計算,這時候需要遷移資料了。

比如我們開始分了10張表,之後業務擴充套件需要,增加到20張表。那問題就來了,之前根據orderId取模10後的資料分散在了各個表中,現在需要重新對所有資料重新取模20來分配資料

為了解決這個擴容遷移問題,可以使用一致性hash思想來解決。

一致性雜湊:在移除或者新增一個伺服器時,能夠儘可能小地改變已存在的服務請求與處理請求伺服器之間的對映關係。一致性雜湊解決了簡單雜湊演算法在分散式雜湊表存在的動態伸縮等問題

6. 如何避免熱點問題資料傾斜(熱點資料)

如果我們根據時間範圍分片,某電商公司11月搞營銷活動,那麼大部分的資料都落在11月份的表裡面了,其他分片表可能很少被查詢,即資料傾斜了,有熱點資料問題了。

我們可以使用range範圍+ hash雜湊取模結合的分表策略,簡單的做法就是:

在拆分庫的時候,我們可以先用range範圍方案,比如訂單id在0~4000萬的區間,劃分為訂單庫1;id在4000萬~8000萬的資料,劃分到訂單庫2,將來要擴容時,id在8000萬~1.2億的資料,劃分到訂單庫3。然後訂單庫內,再用hash取模的策略,把不同訂單劃分到不同的表。

分庫分表經典15連問

7.分庫後,事務問題如何解決

分庫分表後,假設兩個表在不同的資料庫,那麼本地事務已經無效啦,需要使用分散式事務了。

常用的分散式事務解決方案有:

  • 兩階段提交
  • 三階段提交
  • TCC
  • 本地訊息表
  • 最大努力通知
  • saga

大家可以看下這幾篇文章:

  • 後端程式設計師必備:分散式事務基礎篇
  • 看一遍就理解:分散式事務詳解
  • 框架篇:分散式一致性解決方案

8. 跨節點Join關聯問題

在單庫未拆分表之前,我們如果要使用join關聯多張表操作的話,簡直so easy啦。但是分庫分表之後,兩張表可能都不在同一個資料庫中了,那麼如何跨庫join操作呢?

跨庫Join的幾種解決思路:

  • 欄位冗餘:把需要關聯的欄位放入主表中,避免關聯操作;比如訂單表儲存了賣家ID(sellerId),你把賣家名字sellerName也儲存到訂單表,這就不用去關聯賣家表了。這是一種空間換時間的思想。
  • 全域性表:比如系統中所有模組都可能會依賴到的一些基礎表(即全域性表),在每個資料庫中均儲存一份。
  • 資料抽象同步:比如A庫中的a表和B庫中的b表有關聯,可以定時將指定的表做同步,將資料匯合聚集,生成新的表。一般可以藉助ETL工具。
  • 應用層程式碼組裝:分開多次查詢,呼叫不同模組服務,獲取到資料後,程式碼層進行欄位計算拼裝。

9. order by,group by等聚合函式問題

跨節點的count,order by,group by以及聚合函式等問題,都是一類的問題,它們一般都需要基於全部資料集合進行計算。可以分別在各個節點上得到結果後,再在應用程式端進行合併。

10. 分庫分表後的分頁問題

  • 方案1(全域性視野法):在各個資料庫節點查到對應結果後,在程式碼端匯聚再分頁。這樣優點是業務無損,精準返回所需資料;缺點則是會返回過多資料,增大網路傳輸

比如分庫分表前,你是根據建立時間排序,然後獲取第2頁資料。如果你是分了兩個庫,那你就可以每個庫都根據時間排序,然後都返回2頁資料,然後把兩個資料庫查詢回來的資料彙總,再根據建立時間進行記憶體排序,最後再取第2頁的資料。

  • 方案2(業務折衷法-禁止跳頁查詢):這種方案需要業務妥協一下,只有上一頁和下一頁,不允許跳頁查詢了。

這種方案,查詢第一頁時,是跟全域性視野法一樣的。但是下一頁時,需要把當前最大的建立時間傳過來,然後每個節點,都查詢大於建立時間的一頁資料,接著彙總,記憶體排序返回。

11. 分散式ID

資料庫被切分後,不能再依賴資料庫自身的主鍵生成機制啦,最簡單可以考慮UUID,或者使用雪花演算法生成分散式ID

雪花演算法是一種生成分散式全域性唯一ID的演算法,生成的ID稱為Snowflake IDs。這種演算法由Twitter建立,並用於推文的ID。

一個Snowflake ID64位。

  • 1位:Java中long的最高位是符號位代表正負,正數是0,負數是1,一般生成ID都為正數,所以預設為0。
  • 接下來前41位是時間戳,表示了自選定的時期以來的毫秒數。
  • 接下來的10位代表計算機ID,防止衝突。
  • 其餘12位代表每臺機器上生成ID的序列號,這允許在同一毫秒內建立多個Snowflake ID。
分庫分表經典15連問

12. 分庫分表選擇哪種中介軟體

目前流行的分庫分表中介軟體比較多:

  • Sharding-JDBC
  • cobar
  • Mycat
  • Atlas
  • TDDL(淘寶)
  • vitess
分庫分表經典15連問

我們專案當前就是使用Sharding-JDBC實現的分庫分表。

13.如何評估分庫數量

  • 對於MySQL來說的話,一般單庫超過5千萬記錄,DB的壓力就非常大了。所以分庫數量多少,需要看單庫處理記錄能力。
  • 如果分庫數量少,達不到分散儲存和減輕DB效能壓力的目的;如果分庫的數量多,對於跨多個庫的訪問,應用程式需要訪問多個庫。
  • 一般是建議分4~10個庫,我們公司的企業客戶資訊,就分了10個庫。

14.垂直分庫、水平分庫、垂直分表、水平分表的區別

  • 水平分庫:以欄位為依據,按照一定策略(hash、range等),將一個庫中的資料拆分到多個庫中。
  • 水平分表:以欄位為依據,按照一定策略(hash、range等),將一個表中的資料拆分到多個表中。
  • 垂直分庫:以表為依據,按照業務歸屬不同,將不同的表拆分到不同的庫中。
  • 垂直分表:以欄位為依據,按照欄位的活躍性,將表中欄位拆到不同的表(主表和擴充套件表)中。

15.分表要停服嘛?不停服怎麼做?

不用停服。不停服的時候,應該怎麼做呢,主要分五個步驟:

  1. 編寫代理層,加個開關(控制訪問新的DAO還是老的DAO,或者是都訪問),灰度期間,還是訪問老的DAO
  2. 發版全量後,開啟雙寫,既在舊錶新增和修改,也在新表新增和修改。日誌或者臨時表記下新表ID起始值,舊錶中小於這個值的資料就是存量資料,這批資料就是要遷移的。
  3. 透過指令碼把舊錶的存量資料寫入新表。
  4. 停讀舊錶改讀新表,此時新表已經承載了所有讀寫業務,但是這時候不要立刻停寫舊錶,需要保持雙寫一段時間。
  5. 當讀寫新表一段時間之後,如果沒有業務問題,就可以停寫舊錶啦

       

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2927958/,如需轉載,請註明出處,否則將追究法律責任。

相關文章