1. 程式人生 > >資料庫分庫分表-水平分表筆記

資料庫分庫分表-水平分表筆記

分表筆記    作者Q:359559774  一起探討

場景,主表與從表進行關聯,主表資料較小,百萬內,從表較大

分表方式:userId%從表分表總數 求餘得到所在分表,主表不切分,從表切分 (例如從表按照主表userId進行切分)

結合業務邏輯和表間關係,將當前shard劃分成多個更小的shard,通常情況下,這些更小的shard每一個都只包含一個主表(將以該表ID進行雜湊的表)和多個與其關聯或間接關聯的次表。這種一個shard一張主表多張次表的狀況是水平切分的必然結果。

這樣切分下來,shard數量就會迅速增多。如果每一個shard代表一個獨立的資料庫,那麼管理和維護資料庫將會非常麻煩,而且這些小shard往往只有兩三張表,為此而建立一個新庫,利用率並不高,因此,在水平切分完成後可再進行一次“反向的Merge”,即:將業務上相近,並且具有相近資料增長速率(主表資料量在同一數量級上)的兩個或多個shard放到同一個資料庫上,在邏輯上它們依然是獨立的shard,有各自的主表,並依據各自主表的ID進行雜湊,不同的只是它們的雜湊取模(即節點數量)必需是一致的。這樣,每個資料庫結點上的表格數量就相對平均了。

所有表格均劃分到合適的shard之後,所有跨越shard的表間關聯都必須打斷,在書寫sql時,跨shard的join、group by、order by都將被禁止,需要在應用程式層面協調解決這些問題。

特別想提一點:經水平切分後,shard的粒度往往要比只做垂直切割的粒度要小,原單一垂直shard會被細分為一到多個以一個主表為中心關聯或間接關聯多個次表的shard,此時的shard粒度與領域驅動設計中的“聚合”概念不謀而合,甚至可以說是完全一致,每個shard的主表正是一個聚合中的聚合根!

http://blog.csdn.net/bluishglc/article/details/7766508

實際上按照分庫分表考慮因素,一般是先考慮垂直切分,首先可以考慮業務層面優化,即垂直分表。垂直分表就是把一個數據量很大的表

,可以按某個欄位的屬性或使用頻繁程度分類,拆分為多個表。

如有多種業務型別,每種業務型別入不同的表,table1,table2,table3.

如果日常業務不需要使用所有資料,可以按時間分表,比如說月表。每個表只存一個月記錄。

2.架構上的優化,即水平分表。

水平分表就是根據一列或多列資料的值把資料行放到多個獨立的表裡,這裡不具備業務意義。

如按照id分表,末尾是0-9的資料分別插入到10個表裡面。

可能你要問,這樣看起來和剛才說的垂直分表沒什麼區別。只不過是否具備業務意義的差異,都是按欄位的值來分表。

相比起來,代價最低的是按時間分表或分割槽,這兩種辦法對應用來說都是透明的。

分割槽只需要一次本地資料遷移的操作。

而通過分表把現網資料和歷史資料分離,唯一的代價是定期的資料維護。

1.   分庫分表維度的問題 

假如使用者購買了商品,需要將交易記錄儲存取來,如果按照使用者的緯度分表,則每個使用者的交易記錄都儲存在同一表中,所以很快很方便的查詢到某使用者的購買情況,但是某商品被購買的情況則很有可能分佈在多張表中,查詢起來比較麻煩。反之,按照商品維度分表,可以很方便的查詢到此商品的購買情況,但要查詢到買人的交易記錄比較麻煩。 

所以常見的解決方式有: 

     a.通過掃表的方式解決,此方法基本不可能,效率太低了。 

     b.記錄兩份資料,一份按照使用者緯度分表,一份按照商品維度分表。 

     c.通過搜尋引擎解決,但如果實時性要求很高,又得關係到實時搜尋。 

2.   聯合查詢的問題 

聯合查詢基本不可能,因為關聯的表有可能不在同一資料庫中。 

3.   避免跨庫事務 

避免在一個事務中修改db0中的表的時候同時修改db1中的表,一個是操作起來更復雜,效率也會有一定影響。 

4.   儘量把同一組資料放到同一DB伺服器上 

例如將賣家a的商品和交易資訊都放到db0中,當db1掛了的時候,賣家a相關的東西可以正常使用。也就是說避免資料庫中的資料依賴另一資料庫中的資料。 

那麼分庫分表多少合適呢? 
經測試在單表1000萬條記錄一下,寫入讀取效能是比較好的. 這樣在留點buffer,那麼單表全是資料字型的保持在 
800萬條記錄以下, 有字元型的單表保持在500萬以下. 

如果按 100庫100表來規劃,如使用者業務: 
500萬*100*100 = 50000000萬 = 5000億記錄. 

具體例項

1,使用者表50萬,3年的帖子表上千萬,3年的評論回覆表幾千萬

帖子的釋出時間來分

那評論回覆表就按 發帖的時間分

也就是說 帖子對應 評論回覆

比如 tiezi_1對應 plunhuifu_1 ,這個都是因為tiezi_1的發帖時間是2014年1月

如果帖子對應評論 回覆就可以這麼分

一個帖子拿到發帖時間 然後根據邏輯算出是插入哪個表

這樣的話  2張表相當於變成了接近100張表

假如當前表是帖子表 ,然後路由規則(例如2015-1在表25中) 算出是tiezi_25和h25,最後將表名帶入資料庫sql.

保證一張回覆評論表 只對應一張發帖表, 這樣操作就方便了。

分表以後要確保表不要上千萬 不要進行跨表操作就行了。

那我現在也可以join,沒問題,程式碼端  我加一個路由 ,然後將sql改成動態入表 ,  在程式碼端加個路由 。

2,使用者表 50萬,3年的訂單表上千萬,3年的訂單項表幾千萬,都是1對多關係

訂單表和訂單項表都按照使用者hash進行切分

這樣就不用去取訂單表的資料了。其他也是一個邏輯規則吧

每個小分都需要路由。使用者1的全部在order_1和order_item_1中

當然一個訂單項對應1個產品,產品也是分表的。則根據訂單項中的產品ID找到商品第幾個表

3,產品表 500萬,3年的訂單表上千萬,3年的訂單項表幾千萬,都是1對多關係

3個都需要進行分表,則可以根據產品表進行hash,產品表product_1中,則訂單項也需要在訂單1中,因為先有產品,再有訂單項。但是訂單表是不可控的,只能根據訂單使用者查詢該訂單所在第幾個表。

綜合是:訂單按照使用者hash進行切分,產品和訂單項是按照產品進行hash切分。

4,融資產品表,投資記錄表,使用者表(融資使用者+投資使用者),使用者與投資記錄1對多,投資記錄與還款記錄1對多,投資記錄也和債券1對1 ,融資產品和投資記錄是1對多 。

 按照融資使用者將融資產品分為多個表,按照融資使用者將投資記錄分成多個表,按照融資使用者將還款記錄分為多個表。這樣的話,相當於和融資產品相關的都是同一層級中,product_1,tender_1,repaymeng_1。當然要要查詢某個人的投資記錄就會比較困難,需要進行擴表在聚合。這個主要看需求。

比如融資產品按照融資使用者來分。投資產品按照投資使用者來分。2個關係不大 。我要獲取該產品的所有投資記錄 。和獲取該使用者的所有投資記錄 。是2中不同的分表方式 。可是我生產上 這2個都很重要 。不然我就有一個肯定需要從所有分表中 union了 。

主鍵如何生成的方案:

1. 使用資料庫自增主鍵很方便,但不推薦使用,移植性和靈活性大大降底,比如只能在插入記錄後獲取主鍵值,

相對而言,很多人更推薦自管理主鍵的方式,下面就忽略自增主鍵的討論吧

第一種,自增長的,要在資料庫完全自控的情況下,是可以使用的。像一些企業內部應用。

2. 使用 UUID 作為主鍵非常方便,JAVA簡單的 API 呼叫就可以生成 UUID(也可以考慮使用第 3 方類庫生成 UUID),並且全球唯一,在叢集環境中或其它複雜環境中使用都很方便,但經過查閱相關資料,發現一些劣勢,一是生成 UUID 的效率不高,併發量大的情況下很明顯,二是用 UUID 作為表的主鍵,等資料量大了之後,會嚴重影響 SELECT 和 INSERT 的效率。

雖然沒嘗試過用 UUID 作主鍵,但看了些文章後就被嚇到了(我基本還是用關係型資料庫的)

第二種,UUID,在一些操作頻率不高,但又在不同分公司,最後還要集中到總公司的情況下,

還是有些用的。

3. 一個常用的主鍵管理方案是,用一張表來維護所有的主鍵/值,在應用層生成下個 ID 值,經常見到這種程式碼:getNextSequence(key)。為了避免與資料庫的頻繁互動,一般加個快取,一次取多個 sequence,偶然情況下會有少許的浪費。

針對叢集環境,瞭解一點,有一種辦法是每個結點生成 sequence 時使步進(step)錯位,這樣可以避免衝突。

第三種,也不很好,壓力全集中到了這張表了。得需要至少兩個伺服器保證不會一起掛了。

4. 我自己尋思的一種方法,不使用資料庫的自增主鍵特性,在應用啟動的時刻,初始化/收集所有需要主鍵管理的表的最大主鍵值(maxId)

並儲存到記憶體中(針對 int/long 型別),然後使用 AtomicInteger 或 AtomicLong 實現遞增,除了初始化階段外,不需要為了主鍵管理而去和資料庫打交道,

而且 AtomicInteger 的併發效率更高吧?這種方式實戰不多,有沒有朋友提點建議?

第四種,一臺主機,有點壓力。可不可以把第三種和它結合起來呢,你可以看下推特的,好像

就是這兩種結合在一起的。我猜的,不要相信我。

5. 我發現有些應用,生成 ID 的時機非常早。比如新增一條記錄,在進入到新增介面時,ID 就生成了,如果你未提交併且關閉瀏覽器視窗,

會提示什麼“資料會丟失”、“是否確認關閉視窗”等資訊。這裡我有幾個疑問,希望瞭解的朋友給講講。

a). ID 這麼早生成有什麼特殊的應用場景嗎?為要這麼急?

b). 進入新建頁面->不提交->關閉視窗,這樣操作就要浪費一個 ID,這個影響不嚴重嗎?惡意使用者頻率過高的浪費ID呢?

或者乾脆寫個 HTTP 互動的程式暴ID呢?是我想得太多了,還是想歪了?請高手講解如何正確使用這種方法

第五種,生成的id就類似uuid,不會浪費的。

至於為什麼 那麼急,可能為了便於頁面管理吧。給每個頁面生成個id,正好順便就做該記錄的id

1,本方法是方法都是MYISAM的,至於INNODB如何做分表並且保留事務和外來鍵,我還不是很瞭解。

首先,我們需要想好到底分多少個表,前提當然是滿足應用。這裡我使用了一個比較簡單的分表方法,

就是根據自增id的尾數來分,也就是說分0-9一共10個表,其取值也很好做,就是對10進行取模。另外,

還可以根據某一欄位的md5值取其中幾位進 行分表,這樣的話,可以分的表就很多了。

article_0........... article_9  型別為MYISAM

好了10個表建立完畢了,需要注意的是,這裡的id不能設為自增,而且所有的表結構必須一致,包括結構,型別,長度,

欄位的順序都必須一致那麼對於這個id如何取得呢?後面我會詳細說明。現在,我們需要一個合併表,用於查詢,建立合併表的程式碼如下:article

其中 ENGINE=MRG_MyISAM DEFAULT CHARSET=utf8 INSERT_METHOD=0UNION=(`article_0`,`article_1`,`article_2`,`article_3`

,`article_4`,`article_5`,`article_6`,`article_7`,`article_8`,`article_9`);

注意,合併表也必須和前面的表有相同的結構,型別,長度,包括欄位的順序都必須一致這裡的 INSERT_METHOD=0表示不允許對本表進行insert操作。

好了,當需要查詢的時候,我們可以只對article這個表進行操作就可以了,也就是說這個表僅僅只能進行select操作,

那麼對於插入也就是insert操作應該如何來搞呢,首先就是獲取唯一的id了,這裡就還需要一個表來專門創 建id,程式碼如下:

CREATE TABLE`test`.`create_id` (

`id` BIGINT( 20) NOT NULL AUTO_INCREMENT PRIMARY KEY

) ENGINE =MYISAM

當我們需要插入資料的時候,必須由這個表來產生id值

根據經驗,Mysql表資料一般達到百萬級別,查詢效率會很低,容易造成表鎖,甚至堆積很多連線,直接掛掉;水平分表能夠很大程度較少這些壓力。

1.按時間分表

這種分表方式有一定的侷限性,當資料有較強的實效性,如微博傳送記錄、微信訊息記錄等,這種資料很少有使用者會查詢幾個月前的資料,如就可以按月分表。

2.按區間範圍分表

一般在有嚴格的自增id需求上,如按照user_id水平分表:

table_1  user_id從1~100w

table_2  user_id從101~200w

table_3  user_id從201~300w

...

可以先採用分片機制(如地區,時間,型別)等做強業務獨立性的分節點片,然後再同節點中做同表的同庫水平切分,這樣對同業務維度的關聯查詢有好處。