mysql分區表

分區表是一種粗粒度,簡易的索引策略,適用於大數據的過濾場景.

最適合的場景是,沒有合適的索引時,對其中幾個分區表進行全表掃描.或者只有一個分區表和索引是熱點,而且這個分區和索引能夠全部存儲在內存中.

限制單表分區數不要超過150個,並且注意某些導致無法做分區過濾的細節,分區表對於單條記錄的查詢沒有優勢,需要注意這類查詢的性能.


水平分區(根據列屬性按行分)

舉個簡單例子:一個包含十年發票記錄的表可以被分區爲十個不同的分區,每個分區包含的是其中一年的記錄。


水平分區的幾種模式:

* Range(範圍) – 這種模式允許DBA將數據劃分不同範圍。例如DBA可以將一個表通過年份劃分成三個分區,90年代(1990's)的數據,20世紀(2000's)的數據以及任何在2010年(包括2010年)後的數據。 


* Hash(哈希) – 這中模式允許DBA通過對錶的一個或多個列的Hash Key進行計算,最後通過這個Hash碼不同數值對應的數據區域進行分區。例如DBA可以建立一個對錶主鍵進行分區的表。 


* Key(鍵值) – 上面Hash模式的一種延伸,這裏的Hash Key是MySQL系統產生的。 


* List(預定義列表) – 這種模式允許系統通過DBA定義的列表的值所對應的行數據進行分割。例如:DBA建立了一個橫跨三個分區的表,分別根據2004年2005年和2006年值所對應的數據。 


* Composite(複合模式) -以上模式的組合使用而已,就不解釋了。舉例:在初始化已經進行了Range範圍分區的表上,我們可以對其中一個分區再進行hash哈希分區。 


垂直分區(按列分)

舉個簡單例子:一個包含了大text和BLOB列的表,這些text和BLOB列又不經常被訪問,這時候就要把這些不經常使用的text和BLOB了劃分到另一個分區,在保證它們數據相關性的同時還能提高訪問速度。


水平分區表語法:


  分區表分爲RANGE,LIST,HASH,KEY以及複合模式五種類型,並且分區表的索引是可以局部針對分區表建立的。

CREATE TABLE `xaction_log_time` (

  `xaction_idx` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `pidx` int(10) unsigned DEFAULT NULL,

  `time` datetime NOT NULL,

  PRIMARY KEY (`xaction_idx`,`time`),

  KEY `pidx` (`pidx`)

) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

 PARTITION BY RANGE (YEAR(time))

(PARTITION p_2009 VALUES LESS THAN (2009) ,

 PARTITION p_2010 VALUES LESS THAN (2010) ,

 PARTITION p_2011 VALUES LESS THAN (2011) ,

 PARTITION p_2012 VALUES LESS THAN (2012) ,

 PARTITION p_2013 VALUES LESS THAN (2013) ,

 PARTITION p_2014 VALUES LESS THAN (2014) ,

 PARTITION p_2015 VALUES LESS THAN (2015) ,

 PARTITION p_catchall VALUES LESS THAN MAXVALUE ) ;


  這段語句表示將表內數據按照tiem的年份範圍進行分區,


  要注意如果這麼做,則tiem必須包含在主鍵中,且會產生一個問題,就是當年份超過閾值,到了2016年時,需要手動創建這些分區


新增加分區:

mysql> ALTER TABLE xaction_log_time ADD PARTITION (PARTITION p2016 VALUES LESS THAN (2016));


刪除分區:

當刪除了一個分區,也同時刪除了該分區中所有的數據。

mysql> ALTER TABLE xaction_log_time DROP PARTITION p2010;

TRUNCATE PARTITION,它和DROP PARTITION語句有些類似,但它保留了分區本身,也就是說分區還可以重複利用。


分區的合併


下面的SQL,將p2010 - p2012 合併爲1個分區p2012-3


mysql> ALTER TABLE xaction_log_time


REORGANIZE PARTITION p2010,p2011,p2012,

INTO (PARTITION p2010-3 VALUES LESS THAN (2012));


拆分分區

mysql> ALTER TABLE xaction_log_time REORGANIZE PARTITION p2010INTO (

PARTITION s2009 VALUES LESS THAN (201001),

PARTITION s2010 VALUES LESS THAN (201006)

);


分區並不能總是改善性能,要進行性能評測。例如可以使用expalin partitions 來查看查詢語句是否使用分區過濾了數據:


mysql> explain partitions  select count(*) from xaction_log_time where time>'2009-10-01' and time <'2015-10-02';

+----+-------------+------------------+------------------------------------------------------+-------+---------------+------+---------+------+----------+--------------------------+

| id | select_type | table            | partitions                                           | type  | possible_keys | key  | key_len | ref  | rows     | Extra                    |

+----+-------------+------------------+------------------------------------------------------+-------+---------------+------+---------+------+----------+--------------------------+

|  1 | SIMPLE      | xaction_log_time | p_2010,p_2011,p_2012,p_2013,p_2014,p_2015,p_catchall | index | NULL          | pidx | 5       | NULL | 21582698 | Using where; Using index |

+----+-------------+------------------+------------------------------------------------------+-------+---------------+------+---------+------+----------+--------------------------+

1 row in set (0.00 sec)


mysql> explain partitions  select count(*) from xaction_log_time where time>'2009-10-01' and time <'2009-10-02';

+----+-------------+------------------+------------+-------+---------------+------+---------+------+---------+--------------------------+

| id | select_type | table            | partitions | type  | possible_keys | key  | key_len | ref  | rows    | Extra                    |

+----+-------------+------------------+------------+-------+---------------+------+---------+------+---------+--------------------------+

|  1 | SIMPLE      | xaction_log_time | p_2010     | index | NULL          | pidx | 5       | NULL | 1473670 | Using where; Using index |

+----+-------------+------------------+------------+-------+---------------+------+---------+------+---------+--------------------------+

  

* 如果查詢語句中有未建立索引字段,分區時間遠遠優於未分區時間

* 如果查詢語句中字段建立了索引,分區和未分區的差別縮小,分區略優於未分區。


HASH模式可以不用再手動添加新的分區:


CREATE TABLE `xaction_log_time` (

  `xaction_idx` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `pidx` int(10) unsigned DEFAULT NULL,

  `time` datetime NOT NULL,

  PRIMARY KEY (`xaction_idx`,`time`),

  KEY `pidx` (`pidx`)

) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 PARTITION BY HASH(xaction_idx DIV 1000000);


這種分區表示每100W條數據建立一個分區,且沒有閾值範圍的影響


list類型:

CREATE TABLE category (  

     cid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  

     name VARCHAR(30) NOT NULL DEFAULT ''  

)  

PARTITION BY LIST (cid) (  

     PARTITION p0 VALUES IN (0,4,8,12)  

     DATA DIRECTORY = '/data0/data'   

     INDEX DIRECTORY = '/data1/idx',  

       

     PARTITION p1 VALUES IN (1,5,9,13)  

     DATA DIRECTORY = '/data2/data'  

     INDEX DIRECTORY = '/data3/idx',  

       

     PARTITION p2 VALUES IN (2,6,10,14)  

     DATA DIRECTORY = '/data4/data'  

     INDEX DIRECTORY = '/data5/idx',  

       

     PARTITION p3 VALUES IN (3,7,11,15)  

     DATA DIRECTORY = '/data6/data'  

     INDEX DIRECTORY = '/data7/idx'  

);    

分成4個區,數據文件和索引文件單獨存放。


KEY 類型

CREATE TABLE users (  

     uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  

     name VARCHAR(30) NOT NULL DEFAULT '',  

     email VARCHAR(30) NOT NULL DEFAULT ''  

)  

PARTITION BY KEY (uid) PARTITIONS 4 (  

     PARTITION p0  

     DATA DIRECTORY = '/data0/data'  

     INDEX DIRECTORY = '/data1/idx',  

       

     PARTITION p1  

     DATA DIRECTORY = '/data2/data'   

     INDEX DIRECTORY = '/data3/idx',  

       

     PARTITION p2   

     DATA DIRECTORY = '/data4/data'  

     INDEX DIRECTORY = '/data5/idx',  

       

     PARTITION p3   

     DATA DIRECTORY = '/data6/data'  

     INDEX DIRECTORY = '/data7/idx'  

);   

分成4個區,數據文件和索引文件單獨存放。


複合類型

CREATE TABLE employees (  

  emp_no int(11) NOT NULL,  

  birth_date date NOT NULL,  

  first_name varchar(14) NOT NULL,  

  last_name varchar(16) NOT NULL,  

  gender char(1) DEFAULT NULL,  

  hire_date date NOT NULL 

) ENGINE=MyISAM  

PARTITION BY RANGE  COLUMNS(gender,hire_date)  

(PARTITION p01 VALUES LESS THAN ('F','1990-01-01') ,  

 PARTITION p02 VALUES LESS THAN ('F','2000-01-01') ,  

 PARTITION p03 VALUES LESS THAN ('F',MAXVALUE) ,  

 PARTITION p04 VALUES LESS THAN ('M','1990-01-01') ,  

 PARTITION p05 VALUES LESS THAN ('M','2000-01-01') ,  

 PARTITION p06 VALUES LESS THAN ('M',MAXVALUE) ,  

 PARTITION p07 VALUES LESS THAN (MAXVALUE,MAXVALUE) );


explain partitions select count(*) from employees where gender='F' and hire_date < '1990-01-01'\G  

*************************** 1. row ***************************  

           id: 1  

  select_type: SIMPLE  

        table: employees  

   partitions: p01  

         type: ALL 

possible_keys: NULL 

          key: NULL 

      key_len: NULL 

          ref: NULL 

         rows: 300024  

        Extra: Using where 

explain partitions select count(*) from employees where gender='F'\G  

*************************** 1. row ***************************  

           id: 1  

  select_type: SIMPLE  

        table: employees  

   partitions: p01,p02,p03,p04  

         type: ALL 

possible_keys: NULL 

          key: NULL 

      key_len: NULL 

          ref: NULL 

         rows: 300024  

        Extra: Using where 

explain partitions select count(*) from employees where hire_date < '1990-01-01'\G  

*************************** 1. row ***************************  

           id: 1  

  select_type: SIMPLE  

        table: employees  

   partitions: p01,p02,p03,p04,p05,p06,p07  

         type: ALL 

possible_keys: NULL 

          key: NULL 

      key_len: NULL 

          ref: NULL 

         rows: 300024  

        Extra: Using where 

複合分區如果不用分區定義的第一部分,使用分區定義的第二部分,那麼將會發生全表掃描。


子分區

子分區是針對 RANGE/LIST 類型的分區表中每個分區的再次分割。再次分割可以是 HASH/KEY 等類型。例如:

CREATE TABLE users (  

     uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  

     name VARCHAR(30) NOT NULL DEFAULT '',  

     email VARCHAR(30) NOT NULL DEFAULT ''  

)  

PARTITION BY RANGE (uid) SUBPARTITION BY HASH (uid % 4) SUBPARTITIONS 2(  

     PARTITION p0 VALUES LESS THAN (3000000)  

     DATA DIRECTORY = '/data0/data'  

     INDEX DIRECTORY = '/data1/idx',  

  

     PARTITION p1 VALUES LESS THAN (6000000)  

     DATA DIRECTORY = '/data2/data'  

     INDEX DIRECTORY = '/data3/idx'  

);  

對RANGE 分區再次進行子分區劃分,子分區採用 HASH 類型。


CREATE TABLE users (  

     uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,  

     name VARCHAR(30) NOT NULL DEFAULT '',  

     email VARCHAR(30) NOT NULL DEFAULT ''  

)  

PARTITION BY RANGE (uid) SUBPARTITION BY KEY(uid) SUBPARTITIONS 2(  

     PARTITION p0 VALUES LESS THAN (3000000)  

     DATA DIRECTORY = '/data0/data'  

     INDEX DIRECTORY = '/data1/idx',  

  

     PARTITION p1 VALUES LESS THAN (6000000)  

     DATA DIRECTORY = '/data2/data'  

     INDEX DIRECTORY = '/data3/idx'  

);  

對 RANGE 分區再次進行子分區劃分,子分區採用 KEY 類型。


對於大數據而言


  對於大數據(如10TB)而言,索引起到的作用相對小,因爲索引的空間與維護成本很高,另外如果不是索引覆蓋查詢,將導致回表,造成大量磁盤IO.那麼對於這種情況的解決策略是:


  1.全量掃描數據,不要任何索引


  通過分區表表達式將數據定位在少量的分區表,然後正常訪問這些分區表的數據


  2.分離熱點,索引數據


  將熱點數據分離出來在一個小的分區,並對分區建立索引,對熱點數據的查詢提高效率.


分區表的問題


  1.NULL值使分區過濾無效


  假設按照RANGE YEAR(time)分區,那麼如果這個表達式計算出來的時NULL值,記錄就會被存放到第一個分區.所以在查詢時加入查詢條件有可能出現NULL值,那麼就會去檢查第一個分區.

    解決的方法可以是將第一個分區建立爲NULL分區 PARTITION p_nulls VALUES LESS THAN (0),或者在MySQL5.5以後,直接使用COLUMN建立分區 PARTITION BY RANGE COLUMNS(time)


  2. 選擇分區的成本


  每插入一行數據都需要按照表達式篩選插入的分區地址


  3. 分區列和索引列不匹配


  如果索引列和分區列不匹配,且查詢中沒有包含過濾分區的條件,會導致無法進行分區過濾,那麼將會導致查詢所有分區.


  4. 打開並鎖住所有底層表


  分區表的的查詢策略是在分區過濾之前,打開並鎖住所有底層表,這會造成額外的開銷,解決問題的方法是儘量使用批量操作,例如LOAD DATA INFILE,或者一次刪除多行數據.


過濾分區表的要點


  過濾分區表的WHERE條件必須是切分分區表的列,而不能帶有函數,例如只能是time,而不能是YEAR(time)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章