MySQL學習點滴 --分區表

  寫在前面:筆者以前也有一些MySQL方面的筆記,其中部份內容來自極客時間中丁奇老師的課程。後經園友提醒,這個作法確實不太好。以後我仍會繼續更新一下MySQL方面的學習記錄,在本身理解以後用本身的方式記錄下來。學習與記錄,也是我寫博客的初衷。sql

 

概述:數據庫

  分區功能並非在存儲引擎層完成的,所以不少存儲引擎包括InnoDB, MyISAM, NDB等都支持分區功能。但也並非全部的存儲引擎都支持分區。在使用分區前,首先要了解一下存儲引擎對分區的支持狀況。若是不做特殊說明,默認是在InnoDB下進行說明。 less

  所謂分區,指的是將一個表或索引分解爲更小的部分。從物理層面來講,多是分紅了N個物理分區,每一個分區都是獨立的。從邏輯上來講,這N個物理分區還是一個表或一個索引。函數

  分區能夠分爲兩大類:性能

  1. 水平分區,指的是將同一表中不一樣行的記錄分配到不一樣的物理文件中。
  2. 垂直分區,指的是將同一表中不一樣列的記錄分配到不一樣的物理文件中。

  MySQL在5.1版本中加入了對水平分區的支持,其最新版本是否支持垂直分區筆者暫未考證。網上有一些關於MySQL垂直分區的內容,大都也是在業務層面將表進行拆分,「手動的」垂直分區。學習

  能夠經過命令 SHOW PLUGINS; 來查看是否開啓了分區功能。(partition的status值爲ACTIVE)。優化

  MySQL支持下面幾種類型的分區:3d

  1. RANGE分區:行數據基於一個給定連續區間的列值被放入分區。從5.5版本開始支持RANGE COLUMNS的分區。
  2. LIST分區:和RANGE分區相似,只是LIST分區面向的是離散的值。從5.5版本開始支持LIST COLUMNS的分區。
  3. HASH分區:根據用戶自定義的表達式的返回值進行分區,返回值不能爲負數。
  4. KEY分區:根據MySQL數據庫提供的哈希函數來進行分區。

  不論建立哪一種類型的分區,若是表中存在主鍵或惟一索引,分區列必須是惟一索引的一個組成部分。(這裏筆者初次接觸時還鬧了個笑話。上面提到了MySQL支持的是水平分區,也就是說把不一樣的行記錄分配到不一樣的物理文件中,那爲啥還有個分區列?分區列還必須是惟一索引的一個組成部分?實際上是這樣的,在分區表中若是要插入一條記錄,確定要先肯定應該插入到哪一個分區裏去。而肯定它屬於哪一個分區就是依靠了分區列的值,根據這個值再按照不一樣分區自身的規則,就能夠肯定這條記錄應該被分配到哪一個分區了)。惟一索引能夠是容許NULL值的,而且分區列只要是惟一索引的一個組成部分,不須要整個惟一索引分區列都是分區列。如惟一索引是 UNIQUE KEY(a,b),分區列能夠只指定a列,PARTITION BY HASH(a);blog

  若是建立表時沒有指定主鍵,惟一索引,那麼能夠指定任何一個列爲分區列。索引

分區類型:

RANGE分區:

  這是最經常使用的一種分區,咱們來看一個簡單的例子:

CREATE TABLE range_t(
id INT
)ENGINE = INNODB
PARTITION BY RANGE (id)(
PARTITION p0 VALUES LESS THAN (10),
PARTITION p1 VALUES LESS THAN (20)
);

  由建表語句很容易看出咱們把range_t分紅了p0,p1兩部分。因爲Range分區是給連續區間分區,所以p1的區間範圍其實[10,20)。此處須要注意的是,若是僅僅按照上面咱們的分區來的話,是不能向表range_t中插入id大於20的記錄的。

  

  這種狀況下咱們能夠添加一個MAXVALUE值的分區,MAXVALUE能夠理解爲正無窮,所以區間能夠改變爲[20,MAXVALUE).

ALTER TABLE range_t ADD PARTITION(
partition p2 values less than maxvalue);

  range分區的一個典型的應用場景是記錄與日期相關的記錄。例如要記錄某種交易記錄,能夠按年份時間進行分區。如

...
PARTITION BY RANGE (YEAR(date))(
PARTITION p2017 VALUES LESS THAN (2018), PARTITION p2018 VALUES LESS THAN (2019), PARTITION p2019 VALUES LESS THAN (2020) );

  根據date所屬的年份進行分區,year函數取得的值若是小於2019就歸檔到p2018分區。這樣作有這麼一些好處。首先是方便管理,若是咱們要刪除18年的數據,能夠直接對分區p2018進行刪除。 alter table t drop partition p2018; 另外一方面,這樣分區也能夠加快某些查詢的速度。一樣以p2018分區進行舉例,若是你肯定要查詢的範圍只在2018這個區間內,能夠直接對這個分區進行查詢:select * from t partition(p2018);

  有一點須要注意,優化器能夠對range分區中的部分函數(如YEAR(),TO_DAYS()...)進行優化選擇,而對形如YEAR(date)*100 + MONTH(date)這樣的分區條件是無能爲力的。

 

LIST分區:

  list分區和range分區很類似,區別在於list分區的值是離散的。例如建表語句:

CREATE TABLE list_t(
a INT,
b INT
)ENGINE = INNODB
PARTITION BY LIST(b)(
    PARTITION p0 values IN(1,3,5,7),
    PARTITION p1 values IN(2,4,6,8)   
);

  和range分區同樣,若是你插入的記錄的分區列的值不在list分區的範圍內,MySQL數據庫會拋出異常。另外,若是一次插入多個行的記錄,而這些記錄當中存在分區未定義的值時,MyISAM和InnoDB存儲引擎的處理方式不一樣。MyISAM會將以前的行數據庫都插入,但以後的不會插入。而InnoDB會將其視爲一個事務,所以沒有任何事務插入。

  MyISAM:

  

  Innodb:

  

 

HASH分區:

  Hash分區的目的是將數據均勻地分部到預先定義的各個分區中,儘可能保證各分區的數據量相等。在RANGE和LIST分區中,必須明確指定一個給定的列值活列值所在的集合範圍,而HASH分區中,MySQL自動完成這些工做,用戶所要作的只是基於將要進行哈希分區的列指定一個列值或表達式,以及指定被分區的表將要被分隔成幾部分。一個Hash分區的建表語句例子以下:

CREATE TABLE hash_t(
a INT,
b INT
)ENGINE = InnoDB
PARTITION BY HASH(a+b)
PARTITIONS 4;

  如上所述,用戶所要作的只是基於將要進行哈希分區的列指定一個列值或表達式。這裏咱們使用a+b的值來做爲進行hash的值,固然你也能夠直接使用字段a或b,或是別的表達式。另外,後面的PARTITIONS 4;表明了要分隔成幾個區,這裏要求是一個非負整數,默認值是1.

 

KEY分區:

  Key分區和Hash分區很相似,區別在於hash分區使用用戶定義的函數進行分區,key分區使用MySQL數據庫提供的函數進行分區。對於NDB Cluster引擎,MySQL使用MD5函數來進行分區,對於其餘引擎,MySQL數據庫使用其內部哈希函數,這些函數基於與Password()同樣的運算規則。

 

COLUMNS分區:

  前面介紹的這幾種分區有一個共同條件,即數據必須是整型(interger),若是不是整形則須要經過函數將其轉化爲整型,如YEAR()等。從5.5版本開始,MySQL支持COLUMNS分區,能夠理解成是Range分區和list分區的一種優化,它容許直接使用非整形的數據進行分區,分區根據類型直接比較而得,不須要額外的轉型處理。此外,Range COLUMNS容許對多個列的值進行分區。COLUMNS分區所支持的類型:

  • 全部的整型類型,如INT,SMALLINT,TINYINT,BIGINT。注意,FLOAT和DECIMAL不支持。
  • 日期類型,僅支持DATE和DATETIME。
  • 字符串類型,如CHAR,VARCHAR,BINARYHE VARBINARY。注意,BLOB和TEXT不支持。

  Range Columns對多個列的值進行分區的例子以下:

CREATE TABLE range_column_t(
a INT,
b INT,
c char(3)
)engine = InnoDB
PARTITION BY RANGE COLUMNS(a,b,c)(
PARTITION p0 VALUES LESS THAN (5,10,'c'),
PARTITION p1 VALUES LESS THAN (10,20,'m'),
PARTITION p2 VALUES LESS THAN (30,50,'z')
);

  到這裏,你應該和我同樣有一個疑問,若是我三個值分別屬於不一樣的區間則會被插入到哪一個分區呢。好比插入這麼一條記錄:insert into range_column_t values(4,9,'n'); a,b字段的值都在p0分區範圍內,c的值p2分區範圍內,實際上也插入成功了。咱們來看看結果吧:

  若是咱們再插入一條記錄:insert into range_column_t values(25,15,'a');查看結果以下:

  看來這種方式下,是按照分區列的順序進行分區的,知足第一個條件後就會直接被分配到對應分區。

 

子分區:

  子分區是在分區的基礎上再進行分區,有時也稱這種分區爲複合分區。MySQL數據庫容許在Range和List的分區上再進行Hash分區或Key的子分區。一個創建子分區的例子:

CREATE TABLE sub_t(
a INT,
b DATE
)engine = InnoDB
PARTITION BY RANGE(YEAR(b))
SUBPARTITION BY HASH(TO_DAYS(b))
SUBPARTITIONS 2 (
    PARTITION p0 VALUES LESS THAN (1990),
    PARTITION p1 VALUES LESS THAN (2000),
    PARTITION p2 VALUES LESS THAN MAXVALUE   
);

  關於子分區,有幾個地方須要注意一下:

  • 每一個子分區的數量必須相同。
  • 要在一個分區表的任何分區上使用SUBPARTITION來明肯定義任何子分區,就必須定義全部的子分區。
  • 每一個SUBPARTITION子句必須包括子分區的一個名字
  • 子分區的名字必須是惟一的。

NULL值:

  MySQL數據庫容許對NULL值作分區,但處理方式可能不一樣於其餘數據庫。在MySQL的分區中,Null值被認爲老是小於任何一個非NULL值。而且對於不一樣的分區類型,處理方式也稍有不一樣。

  • 對於Range分區,Null值會被放入最左邊的分區。所以,若是刪除最左側的分區,假設該分區是定義是 LESS THAN 10,那麼刪除的其實是小於10的全部記錄和包含Null值的記錄。
  • 在List分區中則必須顯示的指出哪一個分區中放入Null值,不然會報錯。
  • 而在HASH和Key分區中,任何分區函數都會將含有NULL值的記錄返回爲0。

分區和性能:

  其實有些相似於索引,並非說無腦地添加索引,或是使用分區,數據庫的查詢就會更快。咱們真正要作的是根據實際業務需求去具體的看待問題,如前面提到的按時間記錄的交易記錄的表,假設有這樣的一張大表,而且能夠明確的按照時間分區,且須要頻繁訪問。那麼確實是可使用分區來提升效率,每次查詢時儘可能只訪問對應的分區便可。

  但實際狀況中也可能存在這麼一種類型的表,它的數據量也很大,訪問也很頻繁。但每次可能只是會經過索引去訪問幾條記錄,而不須要一次返回不少不少記錄。這種狀況下,分區可能會帶來很差的影響。咱們知道,正常狀況下B+樹索引(MySQL索引採用B+樹結構)只須要2~3次IO操做便可找到對應的記錄。若是盲目地使用分區,反而可能會增長IO操做的次數。

相關文章
相關標籤/搜索