關係型數據庫以MySQL爲例,單機的存儲能力、鏈接數是有限的,它自身就很容易會成爲系統的瓶頸。當單表數據量在百萬以裏時,咱們還能夠經過添加從庫、優化索引提高性能。一旦數據量朝着千萬以上趨勢增加,再怎麼優化數據庫,不少操做性能仍降低嚴重。數據庫
移動互聯網時代,海量的用戶天天產生海量的數量,好比:微信
以支付寶用戶爲例,8億;微信用戶更是10億。訂單表更誇張,好比美團外賣,天天都是幾千萬的訂單。淘寶的歷史訂單總量應該百億,甚至千億級別,這些海量數據遠不是一張表能Hold住的。事實上MySQL單表能夠存儲10億級數據,只是這時候性能比較差,業界公認MySQL單表容量在1KW量級是最佳狀態,由於這時它的BTREE索引樹高在3~5之間。markdown
既然一張表沒法搞定,那麼就想辦法將數據放到多個地方,目前比較廣泛的方案有3個:架構
說明:只分庫,或者只分表,或者分庫分表融合方案都統一認爲是分庫分表方案,由於分庫,或者分表只是一種特殊的分庫分表而已。NoSQL比較具備表明性的是MongoDB,es。NewSQL比較具備表明性的是TiDB。併發
最後要介紹的就是目前互聯網行業處理海量數據的通用方法:分庫分表。app
本身開發分庫分表工具的工做量是巨大的,好在業界已經有了不少比較成熟的分庫分表中間件,咱們可運維
以將更多的時間放在業務實現上elasticsearch
可是這麼多的分庫分表中間件所有能夠歸結爲兩大類型:工具
CLIENT模式表明有阿里的TDDL,開源社區的sharding-jdbc(sharding-jdbc的3.x版本即sharding-sphere已經支持了proxy模式)。架構以下:oop
PROXY模式表明有阿里的cobar,民間組織的MyCAT。架構以下:
可是,不管是CLIENT模式,仍是PROXY模式。幾個核心的步驟是同樣的:SQL解析,重寫,路由,執行,結果歸併。
筆者比較傾向於CLIENT模式,架構簡單,性能損耗較小,運維成本低。
接下來,以幾個常見的大表爲案例,說明分庫分表如何落地!
主鍵選擇:前面咱們已經對比分析過業務主鍵和自增主鍵的優缺點,結論是業務主鍵更符合業務的查詢需求,而互聯網業務大多都符合讀多寫少的特性,因此全部線上業務都使用業務主鍵;
索引個數:因爲過多的索引會形成索引文件過大,因此要求索引數很少於5個;
列類型選擇:一般越小、越簡單越好,例如:BOOL字段統一使用TINYINT,枚舉字段統一使用TINYINT,交易金額統一使用LONG。由於BOOL和枚舉類型使用TINYINT能夠很方便的擴展,針對金額數據,雖然InnoDB提供了支持精確計算的DECIMAL類型,但DECIMAL是存儲類型不是數據類型,不支持CPU原聲計算,效率會低一些,因此咱們簡單處理將小數轉換爲整數用LONG存儲。
分表策略:首先要明確數據庫出現性能問題通常在數據量到達必定程度後!因此要求咱們提早作好預估,不要等須要拆分時再拆,通常把表的數據量控制在千萬級別;經常使用分表策略有兩種:按key取模,讀寫均勻;按時間分,冷熱數據明確;
分庫分表第一步也是最重要的一步,即sharding column的選取,sharding column選擇的好壞將直接決定整個分庫分表方案最終是否成功。而sharding column的選取跟業務強相關,筆者認爲選擇sharding column的方法最主要分析你的API流量,優先考慮流量大的API,將流量比較大的API對應的SQL提取出來,將這些SQL共同的條件做爲sharding column。例如通常的OLTP系統都是對用戶提供服務,這些API對應的SQL都有條件用戶ID,那麼,用戶ID就是很是好的sharding column。
再以幾張實際表爲例,說明如何分庫分表。
用戶表幾個核心字段通常以下:
uid爲主鍵,業務上有按uid和mobile兩種查詢需求,因此要在moblie上建立索引。switch列比較特殊,類型爲BIGINT,用來保存用戶的BOOL類型的屬性,每一位能夠保存用戶的一個屬性,例如咱們用第一位保存是否接收推送,第二位保存是否保存離線消息等等。
這種設計有很高的擴展性(由於BIGINT有64位,能夠保存64個狀態,通常狀況很難用滿),可是同時也帶來一些問題,switch有很高的查詢頻率。因爲InnoDB是行存儲,要找查詢switch須要把正行數據取出來。
這對上述場景,咱們在表設計上能夠作哪些優化呢?經常使用的方案是把表垂直查分,這種很常見咱們不作過多討論。
還有一種方案咱們能夠利用InnoDB覆蓋索引的特性,在uid和switch兩列上建立聯合索引,這樣在二級索引上包含uid和switch兩列的值,這樣用uid查詢switch時,只經過二級因此就能找到switch,不須要訪問記錄,甚至不須要到二級索引的葉子節點就能夠找到要查詢的switch值,查詢效率很是高。
另外有一點須要考慮,能夠想象switch的變動也是至關頻繁的,switch值得改變會致使聯合索引的變動嗎(這裏的變動指索引節點分裂或順序調整)?
答案是不會!由於聯合索引的第一列uid是惟一且不會變的,因此uid就已經決定了索引的順序,switch列的改變只會改變索引節點上第二個key的值,不會改變索引結構。
通常用戶登陸場景既能夠經過mobile_no,又能夠經過email,還能夠經過username進行登陸。可是一些用戶相關的API,又都包含user_id,那麼可能須要根據這4個column都進行分庫分表,即4個列都是sharding-column。
帳戶表幾個核心字段通常以下:
與帳戶表相關的API,通常條件都有acc_no,因此以acc_no做爲sharding-column便可。
上面提到的都是條件中有sharding column的SQL執行。可是,總有一些查詢條件是不包含sharding column的,同時,咱們也不可能爲了這些請求量並不高的查詢,無限制的冗餘分庫分表。那麼這些條件中沒有sharding column的SQL怎麼處理?以sharding-jdbc爲例,有多少個分庫分表,就要併發路由到多少個分庫分表中執行,而後對結果進行合併。具體如何合併,能夠看筆者sharding-jdbc系列文章,有分析源碼講解合併原理。
這種條件查詢相對於有sharding column的條件查詢性能很明顯會降低不少。若是有幾十個,甚至上百個分庫分表,只要某個表的執行因爲某些因素變慢,就會致使整個SQL的執行響應變慢,這很是符合木桶理論。
更有甚者,那些運營系統中的模糊條件查詢,或者上十個條件篩選。這種狀況下,即便單表都很差建立索引,更不要說分庫分表的狀況下。那麼怎麼辦呢?這個時候大名鼎鼎的elasticsearch,即es就派上用場了。將分庫分表全部數據全量冗餘到es中,將那些複雜的查詢交給es處理。
淘寶個人全部訂單頁面以下,篩選條件有多個,且商品標題能夠模糊匹配,這即便是單表都解決不了的問題(索引知足不了這種場景),更不要說分庫分表了:
具體狀況具體分析:多sharding column不到萬不得已的狀況下最好不要使用,成本較大。由於用戶表有一個很大的特色就是它的上限是確定的,即便全球70億人全是你的用戶,這點數據量也不大,因此筆者更建議採用單sharding column + es的模式簡化架構。
最後,對幾種方案總結以下(sharding column簡稱爲sc):
- | 單個sc | 多個sc | sc+es |
---|---|---|---|
適用場景 | 單一 | 通常 | 比較普遍 |
查詢及時性 | 及時 | 及時 | 比較及時 |
存儲能力 | 通常 | 通常 | 較大 |
代碼成本 | 很小 | 較大 | 通常 |
架構複雜度 | 簡單 | 通常 | 較難 |
總之,對於海量數據,且有必定的併發量的分庫分表,毫不是引入某一個分庫分表中間件就能解決問題,而是一項系統的工程。須要分析整個表相關的業務,讓合適的中間件作它最擅長的事情。例若有sharding column的查詢走分庫分表,一些模糊查詢,或者多個不固定條件篩選則走es,海量存儲則交給HBase。
作了這麼多事情後,後面還會有不少的工做要作,好比數據同步的一致性問題,還有運行一段時間後,某些表的數據量慢慢達到單表瓶頸,這時候還須要作冷數據遷移。總之,分庫分表是一項很是複雜的系統工程。任何海量數據的處理,都不是簡單的事情,作好戰鬥的準備吧!