mysql分區方案的研究

 

 筆者以爲,分庫分表確實好的。可是,動不動搞分庫分表,太麻煩了。分庫分表雖然是提升數據庫性能的常規辦法,可是太麻煩了。因此,嘗試研究mysql的分區到底如何。html

 以前寫過一篇文章,http://www.cnblogs.com/wangtao_20/p/7115962.html 討論過訂單表的分庫分表,折騰起來工做量挺大的,須要多少技術去折騰。作過的人才知道有多麻煩mysql

 

   要按照什麼字段切分,切分數據後,要遷移數據;分庫分表後,會涉及到跨庫、跨表查詢,爲了解決查詢問題,又得用其餘方案來彌補(好比爲了應對查詢得作用戶訂單關係索引表)。工做量確實不小。算法

 

  從網上也能夠看到,大部分實施過的人(成功的)的經驗總結:水平分表,不是必須的,能不作,儘可能不作。sql

 

  像阿里這些系統,數據庫單表數量十多億,達到瓶頸了,不得不作分庫分表,擴容也方便。沒有選擇。
 
  那麼,針對起步階段的業務,技術人員不夠,產品還處在試錯階段。是否是能夠考慮一下分區方案。
 
   筆者幾年前,也犯了思惟錯誤,在小公司作系統,產品還在開發,有待推向市場驗證。那個時候,筆者就去考慮什麼評論表數據量大的狀況下要怎麼作,其實傷腦,又費時間,業務沒有作起來,其實沒多少用處。
 
  架構是演變出來的,不是設計出來的。企圖一開始就設計大炮,結果只有蚊子。筆者作試驗看看mysql的分區究竟是什麼個原理。研究發現,其實跟分表差很少,好比按hash打散數據、按值範圍分散數據。

   

 一、探討分區的原理數據庫

 

瞭解分區到底在作什麼,存儲的數據文件有什麼變化,這樣知道分區是怎麼提升性能的。服務器

 

實際上:每一個分區都有本身獨立的數據、索引文件的存放目錄。本質上,一個分區,實際上對應的是一個磁盤文件。因此分區越多,文件數越多。架構

 

如今使用innodb存儲較多,mysql默認的存儲引擎從mysiam變爲了innodb了。併發

 

以innodb來討論:函數

innodb存儲引擎一張表,對應兩個文件:表名.ibd、表名.frm。性能

若是分區後,一個分區就單獨一個ibd文件,以下圖:

將fs_punch_in_log表拆分紅4個分區,上圖中看到,每一個分區就變成一個單獨的數據文件了。mysql會使用"#p#p1"來命名數據文件,1是分區的編號。總共4個分區,最大值是4。

分表的原理,實際上相似,一個表對應一個數據文件。分表後,數據分散到多個文件去了。性能就提升了。

 

分區後的查詢語句

 

語句仍是按照原來的使用。但爲了提升性能。仍是儘可能避免跨越多個分區匹配數據。

 

以下圖,因爲表是按照id字段分區的。數據分散在多個分區。如今使用user_id做爲條件去查詢。mysql不知道到底分配在哪一個分區。因此要去所有分區掃描,若是每一個分區的數據量大,這樣就耗時很長了。

 

 

 

 

 

分區思路和分區語句

 

 

id字段的值範圍來分區:在1-2千萬分到p0分區,4千萬到-6千萬p1分區。6千萬到8千萬p2分區。依此推算下去。這樣能夠分紅不少的分區了。

爲了保持線性擴容方便。那麼只能使用range範圍來算了。

 

sql以下

CREATE TABLE `fs_punch_in_log` (
`id`  bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵自增' ,
`user_id`  varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '簽到的用戶id' ,
`punch_in_time`  int(10) UNSIGNED NULL DEFAULT NULL COMMENT '打卡簽到時間戳' ,
PRIMARY KEY (`id`)
)  

partition BY RANGE (id) (
    PARTITION p1 VALUES LESS THAN (40000000),
PARTITION p2  VALUES LESS THAN (80000000), 
PARTITION p3  VALUES LESS THAN (120000000),
PARTITION p4  VALUES LESS THAN MAXVALUE
 
 
);

  以上語句通過筆者測驗,注意點:

  •        按照hash均勻分散。傳遞給分區的hash()函數的值,必須是一個整數(hash計算整數計算,實現均勻分佈)。上面的id字段就是表的主鍵,知足整數要求。
  •        partition BY RANGE 中的partition BY表示按什麼方式分區。RANGE告訴mysql,我使用範圍分區。

 

 

 

狀況:若是表結構已經定義好了,裏面有數據了,怎麼進行分區呢?使用alter語句修改便可,通過筆者測驗了。

 

ALTER TABLE `fs_punch_in_log`
PARTITION BY RANGE (id)
(

PARTITION p1 VALUES LESS THAN (40000000),
PARTITION p2  VALUES LESS THAN (80000000), 
PARTITION p3  VALUES LESS THAN (120000000),
PARTITION p4  VALUES LESS THAN MAXVALUE

)

  

注:因爲表裏面已經存在數據了,進行從新分區,mysql會把數據按照分區規則從新移動一次,生成新的文件。若是數據量比較大,耗時間比較長。

 

 

 

2、四種分區類型

 

mysql分區包括四種分區方式:hash分區、按range分區、按key分區、list分區。

四種有點多,實際上,爲了好記,把類再縮小點,就兩大類方式進行分區:一種是計算hash值、一種是按照範圍值。

其實分庫分表的時候,也會用到兩大類,hash運算分、按值範圍分。

 

 一、HASH分區

 

有常規hash和線性hash兩種方式。

 

 

  • 常規hash是基於分區個數取模(%)運算。根據餘數插入到指定的分區。打算分4個分區,根據id字段來分區。

             怎麼算出新插入一行數據,須要放到分區1,仍是分區4呢?  id的值除以4,餘下1,這一行數據就分到1分區。

 

            常規hash,可讓數據很是平均的分佈每個分區。好比分爲4個取,取餘數,餘數老是0-3之間的值(總到這幾個分區去)。分配打散比較均勻。

 

            可是也是有缺點的:因爲分區的規則在建立表的時候已經固定了,數據就已經打散到各個分區。如今若是須要新增分區、減小分區,運算規則變化了,原來已經入庫的數據,就須要適應新的運算規則來作遷移。

            實際上在分庫分表的時候,使用hash方式,也是數據量遷移的問題。不過還好。

            針對這個狀況,增長了線性hash的方式。

 

  • 線性HASH(LINEAR HASH)稍微不一樣點。

         實際上線性hash算法,就是咱們memcache接觸到的那種一致性hash算法。使用虛擬節點的方式,解決了上面hash方式分區時,當新增長分區後,涉及到的數據須要大量遷移的問題。也不是不須要遷移,而是須要遷移的數據量小。

 

         在技術實現上:線性哈希功能使用的一個線性的2的冪(powers-of-two)運算法則,而常規哈希使用的是求哈希函數值的模數。

 

           線性哈希分區和常規哈希分區在語法上的惟一區別在於,在「PARTITION BY」子句中添加「LINEAR」關鍵字。

 

 

二者也有有相同的地方:

 

  •    都是均勻分佈的,預先指定n個分區,而後均勻網幾個分區上面分佈數據。根據一個字段值取hash值,這樣獲得的結果是一個均勻分佈的值。後面添加新的分區多少須要考慮數據遷移。 

 

  •    常規HASH和線性HASH,由於都是計算整數取餘的方式,那麼增長和收縮分區後,原來的數據會根據現有的分區數量從新分佈。

 

  •     HASH分區不能刪除分區,因此不能使用DROP PARTITION操做進行分區刪除操做;

 

考慮之後遷移數據量少,使用線性hash。

 

 

 

二、按照range範圍分區

 

範圍分區,能夠自由指定範圍。好比指定1-2000是一個分區,2000到5000千又是一個分區。範圍徹底能夠本身定。後面我要添加新的分區,很容易嗎?

 

 

 

三、按key分區

 

   相似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供其自身的哈希函數。必須有一列或多列包含整數值。

 

四、按list方式分區

 

能夠把list當作是在range方式的基礎上的改進版。list和range本質都是基於範圍,本身控制範圍。

range是列出範圍,好比1-2000範圍算一個分區,這樣是一個連續的值。

而list分區方式是枚舉方式。能夠指定在1,5,8,9,20這些值都分在第一個分區。從list單詞的字面意思命名暗示就是列表,指定列表中出現的值都分配在第幾個分區。

 

 

3、如何根據業務選擇分區類型

 

一、什麼時候選擇分區,什麼時候選擇分表

 

分表仍是比分區更加靈活。在代碼中能夠本身控制。通常分表會與分庫結合起來使用的。在進行分表的時候,順帶連分庫方案也一塊兒搞定了。

分表分庫,性能和併發能力要比分區要強。分表後,有個麻煩點:本身須要修改代碼去不一樣的表操做數據。

好比用戶表分表後,計劃分4個表,每一個表4千萬用戶。按照用戶編號取模爲4。

代碼不少處要作專門的匹配以下:

     每次操做用戶資料,先要根據uid算出是哪一個表名稱。而後再去寫sql查詢。
 
    固然,是可使用數據庫中間件來作完成分庫、分表。應用代碼不用修改。大部分中間件是根據他們本身的業務特色定製的,拿來使用,不見得適合本身的業務。因此目前缺乏通用的。

     若是使用分區的方式。代碼不用修改。sql仍是按照原來的方式寫。mysql內部自動作了匹配了。

     很是適合業務剛剛起步的時候,能不能作起來,存活期是多久不知。不用把太多精力花費在分庫分表的適應上。
 
   
    考慮到如今業務才起步,使用分區不失爲一種既省事又能提升數據庫併發能力的辦法。等之後業務發展起來了,數據量過億了,那個時候經濟實力已加強,再作改進方案不遲。
    架構是演變出來的,不是設計出來的。適應當前業務的方案,就是好的方案。
 
    過分設計也是一種負擔:不少技術,企圖一開始就設計出一個多大量的系統,實際上沒有那種量,爲了顯示本身技術牛逼。
 
   
    總結:訪問量不大,可是數據錶行數不少。可使用分區的方式。訪問量大,數據量大,能夠重構成分表的方式。

    這是由於雖然數據量多,可是訪問量不大,若是使用分表的話,要修改代碼不少地方,弄起來太麻煩了。投入多,產出少就不必了。

    
 
二、如何選擇適合本身的分區類型
 
 
使用分區和分表同樣的思想:儘可能讓數據均勻分散,這樣達到分流、壓力減少的效果。若是不能均勻分佈,某個分區的操做量特別大,出現單點瓶頸。
 
雖然4種類型的分區方式。其實總共兩大類,按範圍分區和按hash運算分區。
 
range範圍分區,適合按照範圍來切分數據。好比按時間範圍分區。
hash,適合均勻分散數據。使用hash分區,麻煩點是後續增長分區,數據要遷移。有了線性hash分區法,這個遷移量減低了不少。
 
 
以用戶表爲例子,若是要使用分區方案。改使用哪一種分區類型呢?

    考慮到user_id通常不會設計成自增數字。有人會奇怪,怎麼不是自增的,我見過好多用戶編號都是自增的!
 
    的確,有自增數字作uid的,不過通常是開源系統爲了省事,好比discuz、ecshop等。人家沒那麼多工做量給你設計用戶編號方案。
 
    自增的用戶編號,因爲是每次加1進行遞增的。這規律太明顯了,很容易被別有用途的人猜想user_id。再說,別人一看就知道你有多少用戶! 
 
    有個原則,設計編號的時候,儘可能不要讓外部知道你的生成規律。好比訂單號,若是是逐個加1的訂單號,外界能夠猜想出你的業務訂單總數出來。
 
    說一個自增用戶編號的例子。筆者曾經在一家上市互聯網公司,有幾千萬的用戶,uid過去是discuz那一套自增的方式。後來不得不改掉user_id的生成方式。筆者當時負責了這個改造方案。
    不是自增的數字,會是這種:註冊一個是1897996,再註冊一個是9689898,外界徹底找不到數字的規律。
 
   
    不是自增的編號,若是使用範圍來分區,各個分區的數據作不到均勻分佈的。緣由以下:
 
    好比說用戶編號爲1-200000000的用戶分配到p1分區,20000000-40000000分配到p2分區,40000000-60000000分配到p3區,這樣類推下去。
 
    因爲用戶編號不是自增,註冊分配到的用戶編號,多是1到2千萬之間的數字,也多是4千萬到6千萬之間的一個數字。若是被分配到4千萬到6千萬的數字會更多,那麼各個分區給到的數據是不均勻的。
 
    故很差使用範圍來分區。
 
    比較好的辦法是,使用hash取模,user_id%分區數。數據就能夠分散均勻到4個分區去了。
相關文章
相關標籤/搜索