分區概述
數據庫分區是一種物理數據庫設計技術。雖然分區技術能夠實現不少效果,但其主要目的是爲了在特定的SQL操做中減小數據讀寫的總量以縮減sql語句的響應時間,同時對於應用來講分區徹底是透明的。分區表在物理上表現爲多個文件,在邏輯上表現爲一個表。謹慎選擇分區鍵,跨分區查詢效率可能更低。對分區表進行查詢最好在where從句中包含分區鍵。mysql從5.1版本開始支持分區。每一個分區的名稱是不區分大小寫。同個表中的分區表名稱要惟一。
能夠用 show variables like '%partition%'命令查詢當前的mysql數據庫版本是否支持分區。
mysql> show variables like "%partition%"; +-------------------+-------+ | Variable_name | Value | +-------------------+-------+ | have_partitioning | YES | +-------------------+-------+ 1 row in set (0.01 sec)
RANGE分區
基於屬於一個給定連續區間的列值,把多行分配給分區
優勢:適合日期類型,支持複合分區複合(子)分區
缺點:有限的分區
共性:通常只針對某一列
每一個分區包含那些分區表達式的值位於一個給定的連續區間內的行。這些區間要連續且不能相互重疊,使用VALUES LESS THAN操做符來進行定義。在下面的幾個例子中,假定你建立了一個以下的一個表,該表保存有20家音像店的職員記錄,這20家音像店的編號從1到20。
這個表能夠有多種方式來按照區間進行分區。一種方式是使用store_id 列。例如,你可能決定經過添加一個PARTITION BY RANGE子句把這個表分割成4個區間,以下所示:
create table employees( id int not null, start_time date not null default '1970-01-01', end_time date not null default '9999-12-31', type int not null ) partition by range (type)( partition t0 values less than (6), partition t1 values less than (11), partition t2 values less than (17), partition t3 values less than (23) )
按照這種分區方案,在商店1到5工做的僱員相對應的全部行被保存在分區P0中,商店6到10的僱員保存在P1中,依次類推。注意,每一個分區都是按順序進行定義,從最低到最高。這是PARTITION BY RANGE 語法的要求;在這點上,它相似於C或Java中的「switch ... case」語句。
對於包含數據(72, 'Michael', 'Widenius', '1998-06-25', NULL, 13)的一個新行,能夠很容易地肯定它將插入到p2分區中,可是若是增長了一個編號爲第21的商店,將會發生什麼呢?在這種方案下,因爲沒有規則把store_id大於20的商店包含在內,服務器將不知道把該行保存在何處,將會致使錯誤。 要避免這種錯誤,能夠經過在CREATE TABLE語句中使用一個「catchall」 VALUES LESS THAN子句,該子句提供給全部大於明確指定的最高值的值:
create table employees( id int not null, start_time date not null default '1970-01-01', end_time date not null default '9999-12-31', type int not null ) partition by range (type)( partition t0 values less than (6), partition t1 values less than (11), partition t2 values less than (17), partition t3 values less than maxvalue )
MAXVALUE 表示最大的可能的整數值。如今,store_id 列值大於或等於16(定義了的最高值)的全部行都將保存在分區p3中。在未來的某個時候,當商店數已經增加到25, 30, 或更多 ,可使用ALTER TABLE語句爲商店21-25, 26-30,等等增長新的分區
除了能夠根據商店編號分割表數據外,你還可使用一個基於兩個DATE (日期)中的一個的表達式來分割表數據。例如,假定你想基於每一個僱員離開公司的年份來分割表,也就是說,YEAR(separated)的值。實現這種分區模式的CREATE TABLE 語句的一個例子以下所示:
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY RANGE (YEAR(separated)) ( PARTITION p0 VALUES LESS THAN (1991), PARTITION p1 VALUES LESS THAN (1996), PARTITION p2 VALUES LESS THAN (2001), PARTITION p3 VALUES LESS THAN MAXVALUE );
當須要刪除「舊的」數據時。若是你使用上面最近的那個例子給出的分區方案,你只需簡單地使用 「ALTER TABLE employees DROP PARTITION p0;」來刪除全部在1991年前就已經中止工做的僱員相對應的全部行。
LIST分區
相似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇
優勢:適合固定取值的列,支持複合分區複合(子)分區
缺點:有限的分區,插入記錄在這一列的值,不在list中,則數據丟失
共性:通常只針對某一列
MySQL中的LIST分區在不少方面相似於RANGE分區。和按照RANGE分區同樣,每一個分區必須明肯定義。它們的主要區別在於,LIST分區中每一個分區的定義和選擇是基於某列的值從屬於一個值列表集中的一個值,而RANGE分區是從屬於一個連續區間值的集合。LIST分區經過使用「PARTITION BY LIST(expr)」來實現,其中「expr」 是某列值或一個基於某個列值、並返回一個整數值的表達式,而後經過「VALUES IN (value_list)」的方式來定義每一個分區,其中「value_list」是一個經過逗號分隔的整數列表。
註釋:在MySQL 5.1中,當使用LIST分區時,有可能只能匹配整數列表。
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT );
(這和「RANGE分區」 中的例子中使用的是同一個表)。
假定有20個音像店,分佈在4個有經銷權的地區,以下表所示:
地區
商店ID 號
北區
3, 5, 6, 9, 17
東區
1, 2, 10, 11, 19, 20
西區
4, 12, 13, 14, 18
中心區
7, 8, 15, 16
要按照屬於同一個地區商店的行保存在同一個分區中的方式來分割表,可使用下面的「CREATE TABLE」語句:
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY LIST(store_id) PARTITION pNorth VALUES IN (3,5,6,9,17), PARTITION pEast VALUES IN (1,2,10,11,19,20), PARTITION pWest VALUES IN (4,12,13,14,18), PARTITION pCentral VALUES IN (7,8,15,16) );
這使得在表中增長或刪除指定地區的僱員記錄變得容易起來。例如,假定西區的全部音像店都賣給了其餘公司。那麼與在西區音像店工做僱員相關的全部記錄(行)可使用查詢「ALTER TABLE employees DROP PARTITION pWest;」來進行刪除,它與具備一樣做用的DELETE (刪除)查詢「DELETE query DELETE FROM employees WHERE store_id IN (4,12,13,14,18);」比起來,要有效得多。
要點:若是試圖插入列值(或分區表達式的返回值)不在分區值列表中的一行時,那麼「INSERT」查詢將失敗並報錯。例如,假定LIST分區的採用上面的方案,下面的查詢將失敗:
INSERT INTO employees VALUES (224, 'Linus', 'Torvalds', '2002-05-01', '2004-10-12', 42, 21);
這是由於「store_id」列值21不能在用於定義分區pNorth, pEast, pWest,或pCentral的值列表中找到。要重點注意的是,LIST分區沒有相似如「VALUES LESS THAN MAXVALUE」這樣的包含其餘值在內的定義。將要匹配的任何值都必須在值列表中找到。
LIST分區除了能和RANGE分區結合起來生成一個複合的子分區,與HASH和KEY分區結合起來生成複合的子分區也是可能的。
HASH分區
基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算,這個函數能夠包含MySQL中有效的、產生非負整數值的任何表達式
優勢:線性HASH使增長、刪除、合併分區更快捷
缺點:線性HASH的數據分佈不均勻,而通常HASH的數據分佈較均勻
共性:通常只針對某一列
HASH分區主要用來確保數據在預先肯定數目的分區中平均分佈。在RANGE和LIST分區中,必須明確指定一個給定的列值或列值集合應該保存在哪一個分區中;而在HASH分區中,MySQL 自動完成這些工做,你所要作的只是基於將要被哈希的列值指定一個列值或表達式,以及指定被分區的表將要被分割成的分區數量。
要使用HASH分區來分割一個表,要在CREATE TABLE 語句上添加一個「PARTITION BY HASH (expr)」子句,其中「expr」是一個返回一個整數的表達式。它能夠僅僅是字段類型爲MySQL 整型的一列的名字。此外,你極可能須要在後面再添加一個「PARTITIONS num」子句,其中num 是一個非負的整數,它表示表將要被分割成分區的數量。
例如,下面的語句建立了一個使用基於「store_id」列進行 哈希處理的表,該表被分紅了4個分區:
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY HASH(store_id) PARTITIONS 4;
若是沒有包括一個PARTITIONS子句,那麼分區的數量將默認爲1。 例外: 對於NDB Cluster(簇)表,默認的分區數量將與簇數據節點的數量相同,這種修正多是考慮任何MAX_ROWS 設置,以便確保全部的行都能合適地插入到分區中。
若是在關鍵字「PARTITIONS」後面沒有加上分區的數量,將會出現語法錯誤。
「expr」還能夠是一個返回一個整數的SQL表達式。例如,也許你想基於僱用僱員的年份來進行分區。這能夠經過下面的語句來實現:
CREATE TABLE employees ( id INT NOT NULL, fname VARCHAR(30), lname VARCHAR(30), hired DATE NOT NULL DEFAULT '1970-01-01', separated DATE NOT NULL DEFAULT '9999-12-31', job_code INT, store_id INT ) PARTITION BY HASH(YEAR(hired)) PARTITIONS 4;
「expr」還能夠是MySQL 中有效的任何函數或其餘表達式,只要它們返回一個既很是數、也非隨機數的整數。(換句話說,它既是變化的但又是肯定的)。可是應當記住,每當插入或更新(或者可能刪除)一行,這個表達式都要計算一次;這意味着很是複雜的表達式可能會引發性能問題,尤爲是在執行同時影響大量行的運算(例如批量插入)的時候。
最有效率的哈希函數是隻對單個表列進行計算,而且它的值隨列值進行一致地增大或減少,由於這考慮了在分區範圍上的「修剪」。也就是說,表達式值和它所基於的列的值變化越接近,MySQL就能夠越有效地使用該表達式來進行HASH分區。
例如,「date_col」 是一個DATE(日期)類型的列,那麼表達式TO_DAYS(date_col)就能夠說是隨列「date_col」值的變化而發生直接的變化,由於列「date_col」值的每一個變化,表達式的值也將發生與之一致的變化。而表達式YEAR(date_col)的變化就沒有表達式TO_DAYS(date_col)那麼直接,由於不是列「date_col」每次可能的改變都能使表達式YEAR(date_col)發生同等的改變。即使如此,表達式YEAR(date_col)也仍是一個用於 哈希函數的、好的候選表達式,由於它隨列date_col的一部分發生直接變化,而且列date_col的變化不可能引發表達式YEAR(date_col)不成比例的變化。
做爲對照,假定有一個類型爲整型(INT)的、列名爲「int_col」的列。如今考慮表達式「POW(5-int_col,3) + 6」。這對於哈希函數就是一個很差的選擇,由於「int_col」值的變化並不能保證表達式產生成比例的變化。列 「int_col」的值發生一個給定數目的變化,可能會引發表達式的值產生一個很大不一樣的變化。例如,把列「int_col」的值從5變爲6,表達式的值將產生「-1」的改變,可是把列「int_col」的值從6變爲7時,表達式的值將產生「-7」的變化。
換句話說,若是列值與表達式值之比的曲線圖越接近由等式「y=nx(其中n爲非零的常數)描繪出的直線,則該表達式越適合於 哈希。這是由於,表達式的非線性越嚴重,分區中數據產生非均衡分佈的趨勢也將越嚴重。
理論上講,對於涉及到多列的表達式,「修剪(pruning)」也是可能的,可是要肯定哪些適於 哈希是很是困難和耗時的。基於這個緣由,實際上不推薦使用涉及到多列的哈希表達式。
當使用了「PARTITION BY HASH」時,MySQL將基於用戶函數結果的模數來肯定使用哪一個編號的分區。換句話,對於一個表達式「expr」,將要保存記錄的分區編號爲N ,其中「N = MOD(expr, num)」。例如,假定表t1 定義以下,它有4個分區:
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE) PARTITION BY HASH( YEAR(col3) ) PARTITIONS 4;
若是插入一個col3列值爲'2005-09-15'的記錄到表t1中,那麼保存該條記錄的分區肯定以下:
MOD(YEAR('2005-09-01'),4)
= MOD(2005,4)
= 1
MySQL 5.1 還支持一個被稱爲「linear hashing(線性哈希功能)」的變量,它使用一個更加複雜的算法來肯定新行插入到已經分區了的表中的位置。
每當插入或更新一條記錄,用戶函數都要計算一次。當刪除記錄時,用戶函數也可能要進行計算,這取決於所處的環境。
註釋:若是將要分區的表有一個惟一的鍵,那麼用來做爲HASH用戶函數的自變數或者主鍵的column_list的自變數的任意列都必須是那個鍵的一部分。
KEY分區
相似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL服務器提供自身的哈希函數,必須有一列或多列包含整數值
優勢:列能夠爲字符型等其餘非int類型
缺點:效率較以前的低,由於函數爲複雜的函數(MD5或SHA函數)
共性:通常只針對某一列
按照KEY進行分區相似於按照HASH分區,除了HASH分區使用的用戶定義的表達式,而KEY分區的 哈希函數是由MySQL 服務器提供。MySQL 簇(Cluster)使用函數MD5()來實現KEY分區;對於使用其餘存儲引擎的表,服務器使用其本身內部的 哈希函數,這些函數是基於與PASSWORD()同樣的運算法則。
「CREATE TABLE ... PARTITION BY KEY」的語法規則相似於建立一個經過HASH分區的表的規則。它們惟一的區別在於使用的關鍵字是KEY而不是HASH,而且KEY分區只採用一個或多個列名的一個列表。
經過線性KEY分割一個表也是可能的。下面是一個簡單的例子:
CREATE TABLE tk ( col1 INT NOT NULL, col2 CHAR(5), col3 DATE ) PARTITION BY LINEAR KEY (col1) PARTITIONS 3;
在KEY分區中使用關鍵字LINEAR和在HASH分區中使用具備一樣的做用,分區的編號是經過2的冪(powers-of-two)算法獲得,而不是經過模數算法。
不管使用何種類型的分區,分區老是在建立時就自動的順序編號,且從0開始記錄,記住這一點很是重要。當有一新行插入到一個分區表中時,就是使用這些分區編號來識別正確的分區。例如,若是你的表使用4個分區,那麼這些分區就編號爲0, 1, 2, 和3。對於RANGE和LIST分區類型,確認每一個分區編號都定義了一個分區,頗有必要。對HASH分區,使用的用戶函數必須返回一個大於0的整數值。對於KEY分區,這個問題經過MySQL服務器內部使用的 哈希函數自動進行處理。
分區的名字基本上遵循其餘MySQL 標識符應當遵循的原則,例如用於表和數據庫名字的標識符。可是應當注意,分區的名字是不區分大小寫的。例如,下面的CREATE TABLE語句將會產生以下的錯誤:
mysql> CREATE TABLE t2 (val INT) -> PARTITION BY LIST(val)( -> PARTITION mypart VALUES IN (1,3,5), -> PARTITION MyPart VALUES IN (2,4,6) -> );
錯誤1488 (HY000): 表的全部分區必須有惟一的名字。
這是由於MySQL認爲分區名字mypart和MyPart沒有區別。
分區test
查看MySQL版本
mysql> \s -------------- /usr/local/mysql/bin/mysql Ver 14.14 Distrib 5.5.23, for Linux (i686) using readline 5.1 Connection id: 2 Current database: Current user: root@localhost SSL: Not in use Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.5.23-log Source distribution >=5.1 Protocol version: 10 Connection: Localhost via UNIX socket Server characterset: utf8 Db characterset: utf8 Client characterset: utf8 Conn. characterset: utf8 UNIX socket: /tmp/mysql.sock Uptime: 3 hours 32 min 29 sec Threads: 1 Questions: 7 Slow queries: 0 Opens: 33 Flush tables: 1 Open tables: 26 Queries per second avg: 0.000 --------------