MySQL 分表和表分區

爲何要分表和分區?html

平常開發中咱們常常會遇到大表的狀況,所謂的大表是指存儲了百萬級乃至千萬級條記錄的表。這樣的表過於龐大,致使數據庫在查詢和插入的時候耗時太長,性能低下,若是涉及聯合查詢的狀況,性能會更加糟糕。分表和表分區的目的就是減小數據庫的負擔,提升數據庫的效率,一般點來說就是提升表的增刪改查效率。node

什麼是分表?mysql

分表是將一個大表按照必定的規則分解成多張具備獨立存儲空間的實體表,咱們能夠稱爲子表,每一個表都對應三個文件,MYD數據文件,.MYI索引文件,.frm表結構文件。這些子表能夠分佈在同一塊磁盤上,也能夠在不一樣的機器上。app讀寫的時候根據事先定義好的規則獲得對應的子表名,而後去操做它。算法

什麼是分區?sql

分區和分表類似,都是按照規則分解表。不一樣在於分表將大表分解爲若干個獨立的實體表,而分區是將數據分段劃分在多個位置存放,能夠是同一塊磁盤也能夠在不一樣的機器。分區後,表面上仍是一張表,但數據散列到多個位置了。app讀寫的時候操做的仍是大表名字,db自動去組織分區的數據。數據庫

mysql分表和分區有什麼聯繫呢?
1.都能提升mysql的性高,在高併發狀態下都有一個良好的表現。
2.分表和分區不矛盾,能夠相互配合的,對於那些大訪問量,而且表數據比較多的表,咱們能夠採起分表和分區結合的方式(若是merge這種分表方式,不能和分區配合的話,能夠用其餘的分表試),訪問量不大,可是表數據不少的表,咱們能夠採起分區的方式等。
3.分表技術是比較麻煩的,須要手動去建立子表,app服務端讀寫時候須要計算子表名。採用merge好一些,但也要建立子表和配置子表間的union關係。
4.表分區相對於分表,操做方便,不須要建立子表。express

 

今天統計數據的時候發現一張表使用了表分區,藉此機會記錄一下。bash

1. 什麼是表分區?

表分區,是指根據必定規則,將數據庫中的一張表分解成多個更小的,容易管理的部分。從邏輯上看,只有一張表,可是底層倒是由多個物理分區組成。併發

2. 表分區與分表的區別

分表:指的是經過必定規則,將一張表分解成多張不一樣的表。好比將用戶訂單記錄根據時間成多個表。 分表與分區的區別在於:分區從邏輯上來說只有一張表,而分表則是將一張表分解成多張表。app

3. 表分區有什麼好處?

1)分區表的數據能夠分佈在不一樣的物理設備上,從而高效地利用多個硬件設備。 2)和單個磁盤或者文件系統相比,能夠存儲更多數據 3)優化查詢。在where語句中包含分區條件時,能夠只掃描一個或多個分區表來提升查詢效率;涉及sum和count語句時,也能夠在多個分區上並行處理,最後彙總結果。 4)分區表更容易維護。例如:想批量刪除大量數據能夠清除整個分區。 5)可使用分區表來避免某些特殊的瓶頸,例如InnoDB的單個索引的互斥訪問,ext3問價你係統的inode鎖競爭等。

4. 分區表的限制因素

1)一個表最多隻能有1024個分區 2) MySQL5.1中,分區表達式必須是整數,或者返回整數的表達式。在MySQL5.5中提供了非整數表達式分區的支持。 3)若是分區字段中有主鍵或者惟一索引的列,那麼多有主鍵列和惟一索引列都必須包含進來。即:分區字段要麼不包含主鍵或者索引列,要麼包含所有主鍵和索引列。 4)分區表中沒法使用外鍵約束 5)MySQL的分區適用於一個表的全部數據和索引,不能只對表數據分區而不對索引分區,也不能只對索引分區而不對錶分區,也不能只對表的一部分數據分區。

5. 如何判斷當前MySQL是否支持分區?

命令:show variables like '%partition%' 運行結果:

mysql> show variables like '%partition%'; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ 1 row in set (0.00 sec)

have_partintioning 的值爲YES,表示支持分區。

6. MySQL支持的分區類型有哪些?

1)RANGE分區:按照數據的區間範圍分區

2)LIST分區:按照List中的值分區,與RANGE的區別是,range分區的區間範圍值是連續的。

3)HASH分區

4)KEY分區 

說明 在MySQL5.1版本中,RANGE,LIST,HASH分區要求分區鍵必須是INT類型,或者經過表達式返回INT類型。但KEY分區的時候,可使用其餘類型的列(BLOB,TEXT類型除外)做爲分區鍵。

7. Range分區

利用取值範圍進行分區,區間要連續而且不能互相重疊。 語法:

partition by range(exp)( //exp能夠爲列名或者表達式,好比to_date(created_date) partition p0 values less than(num) )

例如:

mysql> create table emp(
    -> id INT NOT null,  -> store_id int not null  -> )  -> partition by range(store_id)(  -> partition p0 values less than(10),  -> partition p1 values less than(20)  -> );

上面的語句建立了emp表,並根據store_id字段進行分區,小於10的值存在分區p0中,大於等於10,小於20的值存在分區p1中。 注意 每一個分區都是按順序定義的,從最低到最高。上面的語句,若是將less than(10) 和less than (20)的順序顛倒過來,那麼將報錯,以下:

ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition

RANGE分區存在的問題

  1. range範圍覆蓋問題:當插入的記錄中對應的分區鍵的值不在分區定義的範圍中的時候,插入語句會失敗。 上面的例子,若是我插入一條store_id = 30的記錄會怎麼樣呢? 咱們上面分區的時候,最大值是20,若是插入一條超過20的記錄,會報錯:
    mysql> insert into emp(id,store_id) values(2,30);
    ERROR 1526 (HY000): Table has no partition for value 30
    提示30這個值沒有對應的分區。 解決辦法 A. 預估分區鍵的值,及時新增分區。 B. 設置分區的時候,使用values less than maxvalue 子句,MAXVALUE表示最大的可能的整數值。 C. 儘可能選擇可以所有覆蓋的字段做爲分區鍵,好比一年的十二個月等。
  2. Range分區中,分區鍵的值若是是NULL,將被做爲一個最小值來處理。

8. LIST分區

List分區是創建離散的值列表告訴數據庫特定的值屬於哪一個分區。 語法:

partition by list(exp)( //exp爲列名或者表達式 partition p0 values in (3,5) //值爲3和5的在p0分區 )

與Range不一樣的是,list分區沒必要生命任何特定的順序。例如:

mysql> create table emp1(
    -> id int not null,  -> store_id int not null  -> )  -> partition by list(store_id)(  -> partition p0 values in (3,5),  -> partition p1 values in (2,6,7,9)  -> );

注意 若是插入的記錄對應的分區鍵的值不在list分區指定的值中,將會插入失敗。而且,list不能像range分區那樣提供maxvalue。

9. Columns分區

MySQL5.5中引入的分區類型,解決了5.5版本以前range分區和list分區只支持整數分區的問題。 Columns分區能夠細分爲 range columns分區和 list columns分區,他們都支持整數,日期時間,字符串三大數據類型。(不支持text和blob類型做爲分區鍵) columns分區還支持多列分區(這裏不詳細展開)。

10. Hash分區

Hash分區主要用來分散熱點讀,確保數據在預先肯定個數的分區中儘量平均分佈。 MySQL支持兩種Hash分區:常規Hash分區和線性Hash分區。 A. 常規Hash分區:使用取模算法 語法:

partition by hash(store_id) partitions 4;

上面的語句,根據store_id對4取模,決定記錄存儲位置。 好比store_id = 234的記錄,MOD(234,4)=2,因此會被存儲在第二個分區。

常規Hash分區的優勢和不足 優勢:可以使數據儘量的均勻分佈。 缺點:不適合分區常常變更的需求。假如我要新增長兩個分區,如今有6個分區,那麼MOD(234,6)的結果與以前MOD(234,4)的結果就會出現不一致,這樣大部分數據就須要從新計算分區。爲解決此問題,MySQL提供了線性Hash分區。

B. 線性Hash分區:分區函數是一個線性的2的冪的運算法則。 語法:

partition by LINER hash(store_id) partitions 4;

與常規Hash的不一樣在於,「Liner」關鍵字。 算法介紹: 假設要保存記錄的分區編號爲N,num爲一個非負整數,表示分割成的分區的數量,那麼N能夠經過如下步驟獲得:
Step 1. 找到一個大於等於num的2的冪,這個值爲V,V能夠經過下面公式獲得:
V = Power(2,Ceiling(Log(2,num)))
例如:剛纔設置了4個分區,num=4,Log(2,4)=2,Ceiling(2)=2,power(2,2)=4,即V=4
Step 2. 設置N=F(column_list)&(V-1)
例如:剛纔V=4,store_id=234對應的N值,N = 234&(4-1) =2
Step 3. 當N>=num,設置V=Ceiling(V/2),N=N&(V-1)
例如:store_id=234,N=2<4,因此N就取值2,便可。
假設上面算出來的N=5,那麼V=Ceiling(4/2)=2,N=5&(2-1)=1,即在第一個分區。

線性Hash的優勢和不足 優勢:在分區維護(增長,刪除,合併,拆分分區)時,MySQL可以處理得更加迅速。 缺點:與常規Hash分區相比,線性Hash各個分區之間的數據分佈不太均衡。

11. Key分區

相似Hash分區,Hash分區容許使用用戶自定義的表達式,但Key分區不容許使用用戶自定義的表達式。Hash僅支持整數分區,而Key分區支持除了Blob和text的其餘類型的列做爲分區鍵。 語法:

partition by key(exp) partitions 4;//exp是零個或多個字段名的列表

key分區的時候,exp能夠爲空,若是爲空,則默認使用主鍵做爲分區鍵,沒有主鍵的時候,會選擇非空唯一鍵做爲分區鍵。

12. 子分區

分區表中對每一個分區再次分割,又成爲複合分區。

13. 分區對於NULL值的處理

MySQ容許分區鍵值爲NULL,分區鍵多是一個字段或者一個用戶定義的表達式。通常狀況下,MySQL在分區的時候會把NULL值看成零值或者一個最小值進行處理。
注意
Range分區中:NULL值被看成最小值來處理
List分區中:NULL值必須出如今列表中,不然不被接受
Hash/Key分區中:NULL值會被看成零值來處理

14. 分區管理

分區管理包括對於分區的增長,刪除,以及查詢。

  1. 增長分區:
    對於Range分區和LIst分區來講:
    alter table table_name add partition (partition p0 values ...(exp))
    values後面的內容根據分區的類型不一樣而不一樣。
    對於Hash分區和Key分區來講:
    alter table table_name add partition partitions 8;
    上面的語句,指的是新增8個分區 。
  2. 刪除分區
    對於Range分區和List分區:
    alter table table_name drop partition p0; //p0爲要刪除的分區名稱
    刪除了分區,同時也將刪除該分區中的全部數據。同時,若是刪除了分區致使分區不能覆蓋全部值,那麼插入數據的時候會報錯。
    對於Hash和Key分區:
    alter table table_name coalesce partition 2; //將分區縮減到2個
    coalesce [ˌkəʊəˈles] vi. 聯合,合併
  3. 分區查詢 1)查詢某張表一共有多少個分區
    mysql> select 
     -> partition_name,  -> partition_expression,  -> partition_description,  -> table_rows  -> from  -> INFORMATION_SCHEMA.partitions  -> where  -> table_schema='test'  -> and table_name = 'emp'; +----------------+----------------------+-----------------------+------------+
    | partition_name | partition_expression | partition_description | table_rows |
    +----------------+----------------------+-----------------------+------------+
    | p0             | store_id             | 10                    |          0 |
    | p1             | store_id             | 20                    |          1 |
    +----------------+----------------------+-----------------------+------------+
    即,能夠從information_schema.partitions表中查詢。
    2)查看執行計劃,判斷查詢數據是否進行了分區過濾
    mysql> explain partitions select * from emp where store_id=10 \G; *************************** 1. row *************************** id: 1
    select_type: SIMPLE
         table: emp
    partitions: p1
          type: system
    possible_keys: NULL
           key: NULL
       key_len: NULL
           ref: NULL
          rows: 1
         Extra: 
    1 row in set (0.00 sec)
    上面的結果:partitions:p1 表示數據在p1分區進行檢索。

[參考資料] 《深刻MySQL數據庫開發、優化與管理維護(第2版)》
《高性能MySQL》

 

============================================

分表的幾種方式:

一、mysql集羣

它並非分表,但起到了和分表相同的做用。集羣可分擔數據庫的操做次數,將任務分擔到多臺數據庫上。集羣能夠讀寫分離,減小讀寫壓力。從而提高數據庫性能。

二、自定義規則分表

大表能夠按照業務的規則來分解爲多個子表。一般爲如下幾種類型,也可本身定義規則。

Range(範圍)–這種模式容許將數據劃分不一樣範圍。例如能夠將一個表經過年份劃分紅若干個分區。
Hash(哈希)–這中模式容許經過對錶的一個或多個列的Hash Key進行計算,最後經過這個Hash碼不一樣數值對應的數據區域進行分區。例如能夠創建一個對錶主鍵進行分區的表。
Key(鍵值)-上面Hash模式的一種延伸,這裏的Hash Key是MySQL系統產生的。
List(預約義列表)–這種模式容許系統經過預約義的列表的值來對數據進行分割。
Composite(複合模式) –以上模式的組合使用 

分表規則與分區規則同樣,在分區模塊詳細介紹。

下面以Range簡單介紹下如何分表(按照年份表)。

假設表結構有4個字段:自增id,姓名,存款金額,存款日期

把存款日期做爲規則分表,分別建立幾個表

2011年:account_2011

2012年:account_2012

……

2015年:account_2015

app在讀寫的時候根據日期來查找對應的表名,須要手動來斷定。

var getTableName = function() {
    var data = {
        name: 'tom',
        money: 2800.00,
        date: '201410013059'
    };
    var tablename = 'account_';
    var year = parseInt(data.date.substring(0, 4));
    if (year < 2012) {
        tablename += 2011; // account_2011
    } else if (year < 2013) {
        tablename += 2012; // account_2012
    } else if (year < 2014) {
        tablename += 2013; // account_2013
    } else if (year < 2015) {
        tablename += 2014; // account_2014
    } else {
        tablename += 2015; // account_2015
    }
    return tablename;
}

merge分表,分爲主表和子表,主表相似於一個殼子,邏輯上封裝了子表,實際上數據都是存儲在子表中的。三、利用merge存儲引擎來實現分表

咱們能夠經過主表插入和查詢數據,若是清楚分表規律,也能夠直接操做子表。

子表2011年

CREATE TABLE `account_2011` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`money`  float NOT NULL ,
`tradeDate`  datetime NOT NULL
PRIMARY KEY (`id`)
)
ENGINE=MyISAM
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=2
CHECKSUM=0
ROW_FORMAT=DYNAMIC
DELAY_KEY_WRITE=0;

子表2012年

CREATE TABLE `account_2012` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`money`  float NOT NULL ,
`tradeDate`  datetime NOT NULL
PRIMARY KEY (`id`)
)
ENGINE=MyISAM
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
AUTO_INCREMENT=2
CHECKSUM=0
ROW_FORMAT=DYNAMIC
DELAY_KEY_WRITE=0;

主表,全部年

CREATE TABLE `account_all` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`name`  varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
`money`  float NOT NULL ,
`tradeDate`  datetime NOT NULL
PRIMARY KEY (`id`)
)
ENGINE=MRG_MYISAM
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
UNION=(`account_2011`,`account_2012`)
INSERT_METHOD=LAST
ROW_FORMAT=DYNAMIC;

建立主表的時候有個INSERT_METHOD,指明插入方式,取值能夠是:0 不容許插入;FIRST 插入到UNION中的第一個表; LAST 插入到UNION中的最後一個表。

經過主表查詢的時候,至關於將全部子表合在一塊兒查詢。這樣並不能體現分表的優點,建議仍是查詢子表。

分區的幾種方式

Range:

create table range( 
  id int(11), 
  money int(11) unsigned not null, 
  date datetime 
  )partition by range(year(date))( 
  partition p2007 values less than (2008), 
  partition p2008 values less than (2009), 
  partition p2009 values less than (2010) 
  partition p2010 values less than maxvalue 
);

List:

create table list( 
  a int(11), 
  b int(11) 
  )(partition by list (b) 
  partition p0 values in (1,3,5,7,9), 
  partition p1 values in (2,4,6,8,0) 
 );

Hash:

create table hash( 
  a int(11), 
  b datetime 
  )partition by hash (YEAR(b) 
  partitions 4;

Key:

create table t_key( 
  a int(11), 
  b datetime) 
  partition by key (b) 
  partitions 4;

 

分區管理

新增分區

ALTER TABLE sale_data
ADD PARTITION (PARTITION p201010 VALUES LESS THAN (201011));

刪除分區
--當刪除了一個分區,也同時刪除了該分區中全部的數據。
ALTER TABLE sale_data DROP PARTITION p201010;

分區的合併
下面的SQL,將p201001 - p201009 合併爲3個分區p2010Q1 - p2010Q3

ALTER TABLE sale_data
REORGANIZE PARTITION p201001,p201002,p201003,
p201004,p201005,p201006,
p201007,p201008,p201009 INTO
(
PARTITION p2010Q1 VALUES LESS THAN (201004),
PARTITION p2010Q2 VALUES LESS THAN (201007),
PARTITION p2010Q3 VALUES LESS THAN (201010)
);

http://www.2cto.com/database/201503/380348.html

相關文章
相關標籤/搜索