### 第18章:分區
本章討論MySQL 5.1.中實現的分區。關于分區和分區概念的介紹可以在[18.1節,“MySQL中的分區概述”](# "18.1.?Overview of Partitioning in MySQL")中找到。MySQL 5.1 支持哪幾種類型的分區,在[18.2節,“分區類型”](# "18.2.?Partition Types")中討論。關于子分區在[18.2.5節,“子分區”](# "18.2.5.?Subpartitioning")中討論。現有分區表中分區的增加、刪除和修改的方法在[18.3節,“分區管理”](# "18.3.?Partition Management")中介紹。 和分區表一同使用的表維護命令在[18.3.3節,“分區維護”](# "18.3.3.?Maintenance of Partitions")中介紹。
**請注意**:MySQL 5.1中的分區實現仍然很新(pre-alpha品質),此時還不是可生產的(not production-ready)。 同樣,許多也適用于本章:在這里描述的一些功能還沒有實際上實現(分區維護和重新分區命令),其他的可能還沒有完全如所描述的那樣實現(例如, 用于分區的數據目錄(DATA DIRECTORY)和索引目錄(INDEX DIRECTORY)選項受到B[ug #13520](http://bugs.mysql.com/13520)) 不利的影響). 我們已經設法在本章中標出這些差異。在提出缺陷報告前,我們鼓勵參考下面的一些資源:
-
[MySQL 分區](http://forums.mysql.com/list.php?106)[論壇](http://forums.mysql.com/list.php?106)
這是一個為對MySQL分區技術感興趣或用MySQL分區技術做試驗提供的官方討論論壇。來自MySQL 的開發者和其他的人,會在上面發表和更新有關的材料。它由分區開發和文獻團隊的成員負責監控。
-
[分區缺陷報告](http://tinyurl.com/9wy6h)
已經歸檔在缺陷系統中的、所有分區缺陷的一個列表,而無論這些缺陷的年限、嚴重性或當前的狀態如何。根據許多規則可以對這些缺陷進行篩選,或者可以從[MySQL缺陷系統主頁](http://bugs.mysql.com/)開始,然后查找你特別感興趣的缺陷。
-
[Mikael Ronstrom's Blog](http://mikaelronstrom.blogspot.com/)
MySQL分區體系結構和領先的開發者Mikael Ronstrom 經常在這里貼關于他研究MySQL 分區和MySQL簇的文章。
-
[PlanetMySQL](http://www.planetmysql.org/)
一個MySQL 新聞網站,它以匯集MySQL相關的網志為特點,那些使用我的MySQL的人應該對此有興趣。我們鼓勵查看那些研究MySQL分區的人的網志鏈接,或者把你自己的網志加到這些新聞報道中。
MySQL 5.1的二進制版本目前還不可用;但是,可以從BitKeeper知識庫中獲得源碼。要激活分區,需要使用--with-分區選項編譯服務器。關于建立MySQL 的更多信息,請參見[2.8節,“使用源碼分發版安裝MySQL”](# "2.8.?MySQL Installation Using a Source Distribution")。如果在編譯一個激活分區的MySQL 5.1創建中碰到問題,可以在MySQL分區論壇中查找解決辦法,如果在論壇中已經貼出的文章中沒有找到問題的解決辦法,可以在上面尋找幫助。
### 18.1.?MySQL中的分區概述
本節提供了關于MySQL 5.1.分區在概念上的概述。
SQL標準在數據存儲的物理方面沒有提供太多的指南。SQL語言的使用獨立于它所使用的任何數據結構或圖表、表、行或列下的介質。但是,大部分高級數據庫管理系統已經開發了一些根據文件系統、硬件或者這兩者來確定將要用于存儲特定數據塊物理位置的方法。在MySQL中,InnoDB存儲引擎長期支持表空間的概念,并且MySQL服務器甚至在分區引入之前,就能配置為存儲不同的數據庫使用不同的物理路徑(關于如何配置的解釋,請參見[7.6.1節,“使用符號鏈接”](# "7.6.1.?Using Symbolic Links"))。
_分區_又把這個概念推進了一步,它允許根據可以設置為任意大小的規則,跨文件系統分配單個表的多個部分。實際上,表的不同部分在不同的位置被存儲為單獨的表。用戶所選擇的、實現數據分割的規則被稱為_分區函數_,這在MySQL中它可以是模數,或者是簡單的匹配一個連續的數值區間或數值列表,或者是一個內部HASH函數,或一個線性HASH函數。函數根據用戶指定的分區類型來選擇,把用戶提供的表達式的值作為參數。該表達式可以是一個整數列值,或一個作用在一個或多個列值上并返回一個整數的函數。這個表達式的值傳遞給分區函數,分區函數返回一個表示那個特定記錄應該保存在哪個分區的序號。這個函數不能是常數,也不能是任意數。它不能包含任何查詢,但是實際上可以使用MySQL 中任何可用的SQL表達式,只要該表達式返回一個小于MAXVALUE(最大可能的正整數)的正數值。分區函數的例子可以在本章后面關于分區類型的討論中找到 (請參見[18.2節,“分區類型”](# "18.2.?Partition Types") ),也可在[13.1.5節,“CREATE TABLE語法”](# "13.1.5.?CREATE TABLE Syntax")的分區語法描述中找到。
當二進制碼變成可用時(也就是說,5.1 -max二進制碼將通過--with-partition建立),分區支持就將包含在MySQL 5.1的-max版本中。如果MySQL二進制碼是使用分區支持建立的,那么激活它不需要任何其他的東西 (例如,在my.cnf文件中,不需要特殊的條目)。可以通過使用SHOW VARIABLES命令來確定MySQL是否支持分區,例如:
mysql> SHOW VARIABLES LIKE '%partition%';
?
+-----------------------+-------+
| Variable_name???????? | Value |
+-----------------------+-------+
| have_partition_engine | YES?? |
+-----------------------+-------+
1 row in set (0.00 sec)
在如上列出的一個正確的SHOW VARIABLES命令所產生的輸出中,如果沒有看到變量have_partition_engine的值為YES,那么MySQL的版本就不支持分區。(注意:在顯示任何有關分區支持信息的命令SHOW ENGINES的輸出中,不會給出任何信息;必須使用SHOW VARIABLES命令來做出這個判斷)。
對于創建了分區的表,可以使用你的MySQL 服務器所支持的任何存儲引擎;MySQL 分區引擎在一個單獨的層中運行,并且可以和任何這樣的層進行相互作用。在MySQL 5.1版中,同一個分區表的所有分區必須使用同一個存儲引擎;例如,不能對一個分區使用MyISAM,而對另一個使用InnoDB。但是,這并不妨礙在同一個 MySQL 服務器中,甚至在同一個數據庫中,對于不同的分區表使用不同的存儲引擎。
要為某個分區表配置一個專門的存儲引擎,必須且只能使用[STORAGE] ENGINE選項,這如同為非分區表配置存儲引擎一樣。但是,必須記住[STORAGE] ENGINE(和其他的表選項)必須列在用在CREATE TABLE語句中的其他任何分區選項之前。下面的例子給出了怎樣創建一個通過HASH分成6個分區、使用InnoDB存儲引擎的表:
CREATE TABLE ti (id INT, amount DECIMAL(7,2), tr_date DATE)
??? ENGINE=INNODB
??? PARTITION BY HASH(MONTH(tr_date))
??? PARTITIONS 6;
(注釋:每個PARTITION子句可以包含一個 [STORAGE] ENGINE選項,但是在MySQL 5.1版本中,這沒有作用)。
創建分區的臨時表也是可能的;但是,這種表的生命周期只有當前MySQL 的會話的時間那么長。對于非分區的臨時表,這也是一樣的。
**注釋**:分區適用于一個表的所有數據和索引;不能只對數據分區而不對索引分區,反之亦然,同時也不能只對表的一部分進行分區。
可以通過使用用來創建分區表的CREATE TABLE語句的PARTITION子句的DATA DIRECTORY(數據路徑)和INDEX DIRECTORY(索引路徑)選項,為每個分區的數據和索引指定特定的路徑。此外,MAX_ROWS和MIN_ROWS選項可以用來設定最大和最小的行數,它們可以各自保存在每個分區里。關于這些選項的更多信息,請參見[18.3節,“分區管理”](# "18.3.?Partition Management")。**注釋**:這個特殊的功能由于[Bu](http://bugs.mysql.com/13250)[g #13250](http://bugs.mysql.com/13250)的原因,目前還不能實用。在第一個5.1二進制版本投入使用時,我們應該已經把這個問題解決了。
分區的一些優點包括:
????????? 與單個磁盤或文件系統分區相比,可以存儲更多的數據。
????????? 對于那些已經失去保存意義的數據,通常可以通過刪除與那些數據有關的分區,很容易地刪除那些數據。相反地,在某些情況下,添加新數據的過程又可以通過為那些新數據專門增加一個新的分區,來很方便地實現。
通常和分區有關的其他優點包括下面列出的這些。MySQL 分區中的這些功能目前還沒有實現,但是在我們的優先級列表中,具有高的優先級;我們希望在5.1的生產版本中,能包括這些功能。
????????? 一些查詢可以得到極大的優化,這主要是借助于滿足一個給定WHERE 語句的數據可以只保存在一個或多個分區內,這樣在查找時就不用查找其他剩余的分區。因為分區可以在創建了分區表后進行修改,所以在第一次配置分區方案時還不曾這么做時,可以重新組織數據,來提高那些常用查詢的效率。
????????? 涉及到例如SUM()和 COUNT()這樣聚合函數的查詢,可以很容易地進行并行處理。這種查詢的一個簡單例子如 “SELECT salesperson_id, COUNT(orders) as order_total FROM sales GROUP BY salesperson_id;”。通過“并行”, 這意味著該查詢可以在每個分區上同時進行,最終結果只需通過總計所有分區得到的結果。
????????? 通過跨多個磁盤來分散數據查詢,來獲得更大的查詢吞吐量。
要經常檢查本頁和本章,因為它將隨MySQL 5.1后續的分區進展而更新。
### 18.2.?分區類型
[18.2.1. RANGE分區](#)[18.2.2. LIST分區](#)[18.2.3. HASH分區](#)[18.2.4. KEY分區](#)[ 18.2.5. 子分區](#)[ 18.2.6. MySQL分區處理NULL值的方式``](#)
本節討論在MySQL 5.1中可用的分區類型。這些類型包括:
????????? **RANGE****分區**:基于屬于一個給定連續區間的列值,把多行分配給分區。參見[18.2.1節,“RANGE分區”](# "18.2.1.?RANGE Partitioning")。
????????? **LIST****分區**:類似于按RANGE分區,區別在于LIST分區是基于列值匹配一個離散值集合中的某個值來進行選擇。參見[18.2.2節,“LIST分區”](# "18.2.2.?LIST Partitioning")。
????????? **HASH分區**:基于用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。參見[18.2.3節,“HASH分區”](# "18.2.3.?HASH Partitioning")。
????????? **KEY****分區**:類似于按HASH分區,區別在于KEY分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值。參見[18.2.4節,“KEY分區”](# "18.2.4.?KEY Partitioning")。
無論使用何種類型的分區,分區總是在創建時就自動的順序編號,且從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沒有區別。
**注釋**:在下面的章節中,我們沒有必要提供可以用來創建每種分區類型語法的所有可能形式,這些信息可以在[13.1.5節,“CREATE TABLE語法”](# "13.1.5.?CREATE TABLE Syntax")中找到。
### 18.2.1.?RANGE分區
按照RANGE分區的表是通過如下一種方式進行分區的,每個分區包含那些分區表達式的值位于一個給定的連續區間內的行。這些區間要連續且不能相互重疊,使用VALUES LESS THAN操作符來進行定義。在下面的幾個例子中,假定你創建了一個如下的一個表,該表保存有20家音像店的職員記錄,這20家音像店的編號從1到20。
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 NOT NULL,
??? store_id INT NOT NULL
);
根據你的需要,這個表可以有多種方式來按照區間進行分區。一種方式是使用store_id列。例如,你可能決定通過添加一個PARTITION BY RANGE子句把這個表分割成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 NOT NULL,
??? store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
??? PARTITION p0 VALUES LESS THAN (6),
??? PARTITION p1 VALUES LESS THAN (11),
??? PARTITION p2 VALUES LESS THAN (16),
??? PARTITION p3 VALUES LESS THAN (21)
);
按照這種分區方案,在商店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,
??? 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 NOT NULL,
??? store_id INT NOT NULL
)
PARTITION BY RANGE (store_id) (
??? PARTITION p0 VALUES LESS THAN (6),
??? PARTITION p1 VALUES LESS THAN (11),
??? PARTITION p2 VALUES LESS THAN (16),
??? PARTITION p3 VALUES LESS THAN MAXVALUE
);
MAXVALUE表示最大的可能的整數值。現在,store_id列值大于或等于16(定義了的最高值)的所有行都將保存在分區p3中。在將來的某個時候,當商店數已經增長到25, 30, 或更多 ,可以使用ALTER TABLE語句為商店21-25, 26-30,等等增加新的分區 (關于如何實現的詳細信息參見[18.3節,“分區管理”](# "18.3.?Partition Management") )。
在幾乎一樣的結構中,你還可以基于雇員的工作代碼來分割表,也就是說,基于job_code列值的連續區間。例如――假定2位數字的工作代碼用來表示普通(店內的)工人,三個數字代碼表示辦公室和支持人員,四個數字代碼表示管理層,你可以使用下面的語句創建該分區表:
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 NOT NULL,
??? store_id INT NOT NULL
)
PARTITION BY RANGE (job_code) (
??? PARTITION p0 VALUES LESS THAN (100),
??? PARTITION p1 VALUES LESS THAN (1000),
??? PARTITION p2 VALUES LESS THAN (10000)
);
在這個例子中, 店內工人相關的所有行將保存在分區p0中,辦公室和支持人員相關的所有行保存在分區p1中,管理層相關的所有行保存在分區p2中。
在VALUES LESS THAN子句中使用一個表達式也是可能的。這里最值得注意的限制是MySQL 必須能夠計算表達式的返回值作為LESS THAN (<)比較的一部分;因此,表達式的值不能為NULL。由于這個原因,雇員表的hired, separated, job_code,和store_id列已經被定義為非空(NOT NULL)。
除了可以根據商店編號分割表數據外,你還可以使用一個基于兩個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
);
在這個方案中,在1991年前雇傭的所有雇員的記錄保存在分區p0中,1991年到1995年期間雇傭的所有雇員的記錄保存在分區p1中,1996年到2000年期間雇傭的所有雇員的記錄保存在分區p2中,2000年后雇傭的所有工人的信息保存在p3中。
RANGE分區在如下場合特別有用:
????????? 當需要刪除“舊的”數據時。如果你使用上面最近的那個例子給出的分區方案,你只需簡單地使用 “ALTER TABLE employees DROP PARTITION p0;”來刪除所有在1991年前就已經停止工作的雇員相對應的所有行。(更多信息請參見[13.1.2節,“ALTER TABLE語法”](# "13.1.2.?ALTER TABLE Syntax")和 [ 18.3節,“分區管理”](# "18.3.?Partition Management"))。對于有大量行的表,這比運行一個如“DELETE FROM employees WHERE YEAR(separated) <= 1990;”這樣的一個DELETE查詢要有效得多。
????????? 想要使用一個包含有日期或時間值,或包含有從一些其他級數開始增長的值的列。
????????? 經常運行直接依賴于用于分割表的列的查詢。例如,當執行一個如“SELECT COUNT(*) FROM employees WHERE YEAR(separated) = 2000 GROUP BY store_id;”這樣的查詢時,MySQL可以很迅速地確定只有分區p2需要掃描,這是因為余下的分區不可能包含有符合該WHERE子句的任何記錄。**注釋**:這種優化還沒有在MySQL 5.1源程序中啟用,但是,有關工作正在進行中。
### 18.2.2.?LIST分區
MySQL中的LIST分區在很多方面類似于RANGE分區。和按照RANGE分區一樣,每個分區必須明確定義。它們的主要區別在于,LIST分區中每個分區的定義和選擇是基于某列的值從屬于一個值列表集中的一個值,而RANGE分區是從屬于一個連續區間值的集合。LIST分區通過使用“PARTITION BY LIST(_expr_)”來實現,其中“_expr”_是某列值或一個基于某個列值、并返回一個整數值的表達式,然后通過“VALUES IN (_value_list_)”的方式來定義每個分區,其中“_value_list_”是一個通過逗號分隔的整數列表。
**注釋**:在MySQL 5.1中,當使用LIST分區時,有可能只能匹配整數列表。
不像按照RANGE定義分區的情形,LIST分區不必聲明任何特定的順序。關于LIST分區更詳細的語法信息,請參考[13.1.5節,“CREATE TABLE語法”](# "13.1.5.?CREATE TABLE Syntax")。
對于下面給出的例子,我們假定將要被分區的表的基本定義是通過下面的“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
);
(這和[18.2.1節,“RANGE分區”](# "18.2.1.?RANGE Partitioning") 中的例子中使用的是同一個表)。?
假定有20個音像店,分布在4個有經銷權的地區,如下表所示:
<table border="1" cellpadding="0" id="table1"><tr><td> <p><strong><span> 地區</span></strong></p></td> <td> <p><strong><span> 商店<span>ID </span>號</span></strong></p></td> </tr><tr><td> <p>北區</p></td> <td> <p><span>3, 5, 6, 9, 17</span></p></td> </tr><tr><td> <p>東區</p></td> <td> <p><span>1, 2, 10, 11, 19, 20</span></p></td> </tr><tr><td> <p>西區</p></td> <td> <p><span>4, 12, 13, 14, 18</span></p></td> </tr><tr><td> <p>中心區</p></td> <td> <p><span>7, 8, 15, 16</span></p></td> </tr></table>
要按照屬于同一個地區商店的行保存在同一個分區中的方式來分割表,可以使用下面的“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分區結合起來生成復合的子分區也是可能的。 關于這方面的討論,請參考[18.2.5節,“子分區”](# "18.2.5.?Subpartitioning")。
### 18.2.3.?HASH分區
[18.2.3.1. LINEAR 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設置,以便確保所有的行都能合適地插入到分區中。(參見[第17章:](#)[_MySQL簇_](# "Chapter?17.?MySQL Cluster"))。
如果在關鍵字“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=_n_x(其中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(線性哈希功能)_”的變量,它使用一個更加復雜的算法來確定新行插入到已經分區了的表中的位置。關于這種算法的描述,請參見[18.2.3.1節,“LINEAR HASH分區”](# "18.2.3.1.?LINEAR HASH Partitioning")。
每當插入或更新一條記錄,用戶函數都要計算一次。當刪除記錄時,用戶函數也可能要進行計算,這取決于所處的環境。
**注釋**:如果將要分區的表有一個唯一的鍵,那么用來作為HASH用戶函數的自變數或者主鍵的_column_list_的自變數的任意列都必須是那個鍵的一部分。
#### 18.2.3.1.?LINEAR HASH分區
MySQL還支持線性哈希功能,它與常規哈希的區別在于,線性哈希功能使用的一個線性的2的冪(powers-of-two)運算法則,而常規 哈希使用的是求哈希函數值的模數。
線性哈希分區和常規哈希分區在語法上的唯一區別在于,在“PARTITION BY”子句中添加“LINEAR”關鍵字,如下面所示:
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 LINEAR HASH(YEAR(hired))
PARTITIONS 4;
假設一個表達式_expr_, 當使用線性哈希功能時,記錄將要保存到的分區是_num_個分區中的分區N,其中N是根據下面的算法得到:
1.??? 找到下一個大于_num_.的、2的冪,我們把這個值稱為_V_,它可以通過下面的公式得到:
2.?????????? V = POWER(2, CEILING(LOG(2, num)))
(例如,假定num是13。那么LOG(2,13)就是3.7004397181411。 CEILING(3.7004397181411)就是4,則_V_ = POWER(2,4), 即等于16)。
3.??? 設置 _N_ = _F_(_column_list_) & (_V_ - 1).
4.??? 當 _N_ >= _num_:
????????? 設置 _V_ = CEIL(_V_ / 2)
????????? 設置 _N_ = _N_ & (_V_ - 1)
例如,假設表t1,使用線性哈希分區且有4個分區,是通過下面的語句創建的:
CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
??? PARTITION BY LINEAR HASH( YEAR(col3) )
??? PARTITIONS 6;
現在假設要插入兩行記錄到表t1中,其中一條記錄col3列值為'2003-04-14',另一條記錄col3列值為'1998-10-19'。第一條記錄將要保存到的分區確定如下:
V = POWER(2, CEILING(LOG(2,7))) = 8
N = YEAR('2003-04-14') & (8 - 1)
?? = 2003 & 7
?? = 3
?
(3 >= 6 為假(FALSE): 記錄將被保存到#3號分區中)
第二條記錄將要保存到的分區序號計算如下:
V = 8
N = YEAR('1998-10-19') & (8-1)
? = 1998 & 7
? = 6
?
(6 >= 4 為真(TRUE): 還需要附加的步驟)
?
N = 6 & CEILING(5 / 2)
? = 6 & 3
? = 2
?
(2 >= 4 為假(FALSE): 記錄將被保存到#2分區中)
按照線性哈希分區的優點在于增加、刪除、合并和拆分分區將變得更加快捷,有利于處理含有極其大量(1000吉)數據的表。它的缺點在于,與使用常規HASH分區得到的數據分布相比,各個分區間數據的分布不大可能均衡。
### 18.2.4.?KEY分區
按照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)算法得到,而不是通過模數算法。關于該算法及其蘊涵式的描述請參考 [ 18.2.3.1節,“LINEAR HASH分區”](# "18.2.3.1.?LINEAR HASH Partitioning")。
### 18.2.5.?子分區
子分區是分區表中每個分區的再次分割。例如,考慮下面的CREATE TABLE語句:
CREATE TABLE ts (id INT, purchased DATE)
??? PARTITION BY RANGE(YEAR(purchased))
??? SUBPARTITION BY HASH(TO_DAYS(purchased))
??? SUBPARTITIONS 2
??? (
??????? PARTITION p0 VALUES LESS THAN (1990),
??????? PARTITION p1 VALUES LESS THAN (2000),
??????? PARTITION p2 VALUES LESS THAN MAXVALUE
??? );
表ts有3個RANGE分區。這3個分區中的每一個分區――p0, p1, 和 p2――又被進一步分成了2個子分區。實際上,整個表被分成了3 * 2 = 6個分區。但是,由于PARTITION BY RANGE子句的作用,這些分區的頭2個只保存“purchased”列中值小于1990的那些記錄。
在MySQL 5.1中,對于已經通過RANGE或LIST分區了的表再進行子分區是可能的。子分區既可以使用HASH希分區,也可以使用KEY分區。這也被稱為復合分區(_composite partitioning_)。
為了對個別的子分區指定選項,使用SUBPARTITION子句來明確定義子分區也是可能的。例如,創建在前面例子中給出的同一個表的、一個更加詳細的方式如下:
CREATE TABLE ts (id INT, purchased DATE)
??? PARTITION BY RANGE(YEAR(purchased))
??? SUBPARTITION BY HASH(TO_DAYS(purchased))
??? (
??????? PARTITION p0 VALUES LESS THAN (1990)
??????? (
??????????? SUBPARTITION s0,
??????????? SUBPARTITION s1
??????? ),
??????? PARTITION p1 VALUES LESS THAN (2000)
??????? (
??????????? SUBPARTITION s2,
? ??????????SUBPARTITION s3
??????? ),
??????? PARTITION p2 VALUES LESS THAN MAXVALUE
??????? (
??????????? SUBPARTITION s4,
??????????? SUBPARTITION s5
??????? )
??? );
幾點要注意的語法項:
????????? 每個分區必須有相同數量的子分區。
????????? 如果在一個分區表上的任何分區上使用SUBPARTITION來明確定義任何子分區,那么就必須定義所有的子分區。換句話說,下面的語句將執行失敗:
???????????????? CREATE TABLE ts (id INT, purchased DATE)
???????????????? ????PARTITION BY RANGE(YEAR(purchased))
???????????????? ????SUBPARTITION BY HASH(TO_DAYS(purchased))
???????????????? ????(
???????????????? ????????PARTITION p0 VALUES LESS THAN (1990)
???????????????? ????????(
???????????????? ????????????SUBPARTITION s0,
???????????????? ????????????SUBPARTITION s1
???????????????? ????????),
???????????????? ????????PARTITION p1 VALUES LESS THAN (2000),
???????????????? ????????PARTITION p2 VALUES LESS THAN MAXVALUE
???????????????? ????????(
???????????????? ????????????SUBPARTITION s2,
???????????????? ????????????SUBPARTITION s3
???????????????? ????????)
???????????????? ????);
即便這個語句包含了一個SUBPARTITIONS 2子句,但是它仍然會執行失敗。
????????? 每個SUBPARTITION子句必須包括 (至少)子分區的一個名字。否則,你可能要對該子分區設置任何你所需要的選項,或者允許該子分區對那些選項采用其默認的設置。
????????? 在每個分區內,子分區的名字必須是唯一的,但是在整個表中,沒有必要保持唯一。例如,下面的CREATE TABLE語句是有效的:
???????????????? CREATE TABLE ts (id INT, purchased DATE)
???????????????? ????PARTITION BY RANGE(YEAR(purchased))
???????????????? ????SUBPARTITION BY HASH(TO_DAYS(purchased))
???????????????? ????(
???????????????? ????????PARTITION p0 VALUES LESS THAN (1990)
???????????????? ????????(
???????????????? ????????????SUBPARTITION s0,
???????????????? ????????????SUBPARTITION s1
???????????????? ????????),
???????????????? ????????PARTITION p1 VALUES LESS THAN (2000)
???????????????? ????????(
???????????????? ????????????SUBPARTITION s0,
???????????????? ????????????SUBPARTITION s1
???????????????? ????????),
???????????????? ????????PARTITION p2 VALUES LESS THAN MAXVALUE
???????????????? ????????(
???????????????? ????????????SUBPARTITION s0,
???????????????? ????????????SUBPARTITION s1
???????????????? ????????)
???????????????? ????);
子分區可以用于特別大的表,在多個磁盤間分配數據和索引。假設有6個磁盤,分別為/disk0,/disk1,/disk2等。現在考慮下面的例子:
CREATE TABLE ts (id INT, purchased DATE)
??? PARTITION BY RANGE(YEAR(purchased))
??? SUBPARTITION BY HASH(TO_DAYS(purchased))
??? (
??????? PARTITION p0 VALUES LESS THAN (1990)
??????? (
??????????? SUBPARTITION s0
????????????????DATA DIRECTORY = '/disk0/data'
????????????????INDEX DIRECTORY = '/disk0/idx',
??????????? SUBPARTITION s1
????????????????DATA DIRECTORY = '/disk1/data'
????????????????INDEX DIRECTORY = '/disk1/idx'
??????? ),
??????? PARTITION p1 VALUES LESS THAN (2000)
??????? (
??????????? SUBPARTITION s0
????????????????DATA DIRECTORY = '/disk2/data'
????????????????INDEX DIRECTORY = '/disk2/idx',
??????????? SUBPARTITION s1
????????????????DATA DIRECTORY = '/disk3/data'
????????????????INDEX DIRECTORY = '/disk3/idx'
??????? ),
??????? PARTITION p2 VALUES LESS THAN MAXVALUE
??????? (
??????????? SUBPARTITION s0
????????????????DATA DIRECTORY = '/disk4/data'
????????????????INDEX DIRECTORY = '/disk4/idx',
??????????? SUBPARTITION s1
????????????????DATA DIRECTORY = '/disk5/data'
????????????????INDEX DIRECTORY = '/disk5/idx'
??????? )
??? );
在這個例子中,每個RANGE分區的數據和索引都使用一個單獨的磁盤。還可能有許多其他的變化;下面是另外一個可能的例子:
CREATE TABLE ts (id INT, purchased DATE)
??? PARTITION BY RANGE(YEAR(purchased))
??? SUBPARTITION BY HASH(TO_DAYS(purchased))
??? (
??????? PARTITION p0 VALUES LESS THAN (1990)
??????? (
??????????? SUBPARTITION s0a
????????????????DATA DIRECTORY = '/disk0'
????????????????INDEX DIRECTORY = '/disk1',
??????????? SUBPARTITION s0b
????????????????DATA DIRECTORY = '/disk2'
????????????????INDEX DIRECTORY = '/disk3'
??????? ),
??????? PARTITION p1 VALUES LESS THAN (2000)
??????? (
??????????? SUBPARTITION s1a
????????????????DATA DIRECTORY = '/disk4/data'
????????????????INDEX DIRECTORY = '/disk4/idx',
??????????? SUBPARTITION s1b
????????????????DATA DIRECTORY = '/disk5/data'
????????????????INDEX DIRECTORY = '/disk5/idx'
??????? ),
??????? PARTITION p2 VALUES LESS THAN MAXVALUE
??????? (
??????????? SUBPARTITION s2a,
??????????? SUBPARTITION s2b
??????? )
??? );
在這個例子中,存儲的分配如下:
????????? 購買日期在1990年前的記錄占了大量的存儲空間,所以把它分為了四個部分進行存儲,組成p0分區的兩個子分區(s0a和s0b)的數據和索引都分別用一個單獨的磁盤進行存儲。換句話說:
o??????? 子分區s0a的數據保存在磁盤/disk0中。
o??????? 子分區s0a的索引保存在磁盤/disk1中。
o??????? 子分區s0b的數據保存在磁盤/disk2中。
o??????? 子分區s0b的索引保存在磁盤/disk3中。
????????? 保存購買日期從1990年到1999年間的記錄(分區p1)不需要保存購買日期在1990年之前的記錄那么大的存儲空間。這些記錄分在2個磁盤(/disk4和/disk5)上保存,而不是4個磁盤:
o??????? 屬于分區p1的第一個子分區(s1a)的數據和索引保存在磁盤/disk4上 ― 其中數據保存在路徑/disk4/data下,索引保存在/disk4/idx下。
o??????? 屬于分區p1的第二個子分區(s1b)的數據和索引保存在磁盤/disk5上 ― 其中數據保存在路徑/disk5/data下,索引保存在/disk5/idx下。
????????? 保存購買日期從2000年到現在的記錄(分區p2)不需要前面兩個RANGE分區那么大的空間。當前,在默認的位置能夠足夠保存所有這些記錄。
將來,如果從2000年開始后十年購買的數量已經達到了默認的位置不能夠提供足夠的保存空間時,相應的記錄(行)可以通過使用“ALTER TABLE ... REORGANIZE PARTITION”語句移動到其他的位置。關于如何實現的說明,請參見[18.3節,“分區管理”](# "18.3.?Partition Management")。
### 18.2.6.?MySQL分區處理NULL值的方式``
MySQL 中的分區在禁止空值(NULL)上沒有進行處理,無論它是一個列值還是一個用戶定義表達式的值。一般而言,在這種情況下MySQL 把NULL視為0。如果你希望回避這種做法,你應該在設計表時不允許空值;最可能的方法是,通過聲明列“NOT NULL”來實現這一點。
在本節中,我們提供了一些例子,來說明當決定一個行應該保存到哪個分區時,MySQL 是如何處理NULL值的。
如果插入一行到按照RANGE或LIST分區的表,該行用來確定分區的列值為NULL,分區將把該NULL值視為0。例如,考慮下面的兩個表,表的創建和插入記錄如下:
mysql> CREATE TABLE tnlist (
??? ->???? id INT,
??? ->???? name VARCHAR(5)
??? -> )
??? -> PARTITION BY LIST(id) (
??? ->???? PARTITION p1 VALUES IN (0),
??? ->???? PARTITION p2 VALUES IN (1)
??? -> );
Query OK, 0 rows affected (0.09 sec)
?
mysql> CREATE TABLE tnrange (
??? ->???? id INT,
??? ->???? name VARCHAR(5)
??? -> )
??? -> PARTITION BY RANGE(id) (
??? ->???? PARTITION p1 VALUES LESS THAN (1),
??? ->???? PARTITION p2 VALUES LESS THAN MAXVALUE
??? -> );
Query OK, 0 rows affected (0.09 sec)
?
mysql> INSERT INTO tnlist VALUES (NULL, 'bob');
Query OK, 1 row affected (0.00 sec)
?
mysql> INSERT INTO tnrange VALUES (NULL, 'jim');
Query OK, 1 row affected (0.00 sec)
?
mysql> SELECT * FROM tnlist;
+------+------+
| id?? | name |
+------+------+
| NULL | bob? |
+------+------+
1 row in set (0.00 sec)
?
mysql> SELECT * FROM tnrange;
+------+------+
| id?? | name |
+------+------+
| NULL | jim? |
+------+------+
1 row in set (0.00 sec)
在兩個表中,id列沒有聲明為“NOT NULL”,這意味著它們允許Null值。可以通過刪除這些分區,然后重新運行SELECT語句,來驗證這些行被保存在每個表的p1分區中:
mysql> ALTER TABLE tnlist DROP PARTITION p1;
Query OK, 0 rows affected (0.16 sec)
?
mysql> ALTER TABLE tnrange DROP PARTITION p1;
Query OK, 0 rows affected (0.16 sec)
?
mysql> SELECT * FROM tnlist;
Empty set (0.00 sec)
?
mysql> SELECT * FROM tnrange;
Empty set (0.00 sec)
在按HASH和KEY分區的情況下,任何產生NULL值的表達式都視同好像它的返回值為0。我們可以通過先創建一個按HASH分區的表,然后插入一個包含有適當值的記錄,再檢查對文件系統的作用,來驗證這一點。假定有使用下面的語句在測試數據庫中創建了一個表tnhash:
CREATE TABLE tnhash (
??? id INT,
??? name VARCHAR(5)
)
PARTITION BY HASH(id)
PARTITIONS 2;
假如Linux 上的MySQL 的一個RPM安裝,這個語句在目錄/var/lib/mysql/test下創建了兩個.MYD文件,這兩個文件可以在**bash** shell中查看,結果如下:
/var/lib/mysql/test> ls *.MYD -l
-rw-rw----? 1 mysql mysql 0 2005-11-04 18:41 tnhash_p0.MYD
-rw-rw----? 1 mysql mysql 0 2005-11-04 18:41 tnhash_p1.MYD
注意:每個文件的大小為0字節。現在在表tnhash中插入一行id列值為NULL的行,然后驗證該行已經被插入:
mysql> INSERT INTO tnhash VALUES (NULL, 'sam');
Query OK, 1 row affected (0.00 sec)
?
mysql> SELECT * FROM tnhash;
+------+------+
| id?? | name |
+------+------+
| NULL | sam? |
+------+------+
1 row in set (0.01 sec)
回想一下,對于任意的整數N,NULL MOD _N_的值總是等于NULL。這個結果在確定正確的分區方面被認為是0。回到系統shell(仍然假定**bash用于這個目的**),通過再次列出數據文件,可以看出值被成功地插入到第一個分區(默認名稱為p0)中:
var/lib/mysql/test> ls *.MYD -l
-rw-rw----? 1 mysql mysql 20 2005-11-04 18:44 tnhash_p0.MYD
-rw-rw----? 1 mysql mysql? 0 2005-11-04 18:41 tnhash_p1.MYD
可以看出INSERT語句只修改了文件tnhash_p0.MYD,它在磁盤上的尺寸增加了,而沒有影響其他的文件。
假定有下面的一個表:
CREATE TABLE tndate (
??? id INT,
??? dt DATE
)
PARTITION BY RANGE( YEAR(dt) ) (
??? PARTITION p0 VALUES LESS THAN (1990),
??? PARTITION p1 VALUES LESS THAN (2000),
??? PARTITION p2 VALUES LESS THAN MAXVALUE
);
像其他的MySQL函數一樣,YEAR(NULL)返回NULL值。一個dt列值為NULL的行,其分區表達式的計算結果被視為0,該行被插入到分區p0中。
### 18.3.?分區管理
[ 18.3.1. RANGE和LIST分區的管理](#)[ 18.3.2. HASH和KEY分區的管理``](#)[ 18.3.3. 分區維護](#)[ 18.3.4. 獲取關于分區的信息](#)
MySQL 5.1 提供了許多修改分區表的方式。添加、刪除、重新定義、合并或拆分已經存在的分區是可能的。所有這些操作都可以通過使用ALTER TABLE命令的分區擴展來實現(關于語法的定義,請參見[13.1.2節,“ALTER TABLE語法”](# "13.1.2.?ALTER TABLE Syntax") )。也有獲得分區表和分區信息的方式。在本節,我們討論下面這些主題:
????????? 按RANGE或LIST分區的表的分區管理的有關信息,請參見[18.3.1節,“RANGE和LIST分區的管理”](# "18.3.1.?Management of RANGE and LIST Partitions")。
????????? 關于HASH和KEY分區管理的討論,請參見[18.3.2節,“HASH和KEY分區的管理``”](# "18.3.2.?Management of HASH and KEY Partitions")。
????????? MySQL 5.1中提供的、獲得關于分區表和分區信息的機制的討論,請參見[18.3.4節,“獲取關于分區的信息”](# "18.3.4.?Obtaining Information About Partitions")。
????????? 關于執行分區維護操作的討論,請參見[18.3.3節,“分區維護”](# "18.3.3.?Maintenance of Partitions")。
注釋:在MySQL 5.1中,一個分區表的所有分區都必須有子分區同樣的名字,并且一旦表已經創建,再改變子分區是不可能的。
**要點:**當前,從5.1系列起建立的MySQL 服務器就把“ALTER TABLE ... PARTITION BY ...”作為有效的語法,但是這個語句目前還不起作用。我們期望MySQL 5.1達到生產狀態時,能夠按照下面的描述實現該語句的功能。
要改變一個表的分區模式,只需要使用帶有一個“_partition_options_”子句的ALTER TABLE的命令。這個子句和與創建一個分區表的CREATE TABLE命令一同使用的子句有相同的語法,并且總是以關鍵字PARTITION BY開頭。例如,假設有一個使用下面CREATE TABLE語句建立的按照RANGE分區的表:
CREATE TABLE trb3 (id INT, name VARCHAR(50), purchased DATE)
??? PARTITION BY RANGE(YEAR(purchased))
??? (
??????? PARTITION p0 VALUES LESS THAN (1990),
??????? PARTITION p1 VALUES LESS THAN (1995),
????? ??PARTITION p2 VALUES LESS THAN (2000),
??????? PARTITION p3 VALUES LESS THAN (2005)
??? );
現在,要把這個表按照使用id列值作為鍵的基礎,通過KEY分區把它重新分成兩個分區,可以使用下面的語句:
ALTER TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2;
這和先刪除這個表、然后使用“CREATE TABLE trb3 PARTITION BY KEY(id) PARTITIONS 2;”重新創建這個表具有同樣的效果。
### 18.3.1.?RANGE和LIST分區的管理
關于如何添加和刪除分區的處理,RANGE和LIST分區非常相似。基于這個原因,我們在本節討論這兩種分區的管理。關于HASH和KEY分區管理的信息,請參見[18.3.2節,“HASH和KEY分區的管理``”](# "18.3.2.?Management of HASH and KEY Partitions")。刪除一個RANGE或LIST分區比增加一個分區要更加簡單易懂,所以我們先討論前者。
從一個按照RANGE或LIST分區的表中刪除一個分區,可以使用帶一個DROP PARTITION子句的ALTER TABLE命令來實現。這里有一個非常基本的例子,假設已經使用下面的CREATE TABLE和INSERT語句創建了一個按照RANGE分區的表,并且已經插入了10條記錄:
mysql> CREATE TABLE tr (id INT, name VARCHAR(50), purchased DATE)
??? ->???? PARTITION BY RANGE(YEAR(purchased))
??? ->???? (
??? ->???????? PARTITION p0 VALUES LESS THAN (1990),
??? ->???????? PARTITION p1 VALUES LESS THAN (1995),
??? ->???????? PARTITION p2 VALUES LESS THAN (2000),
??? ->???????? PARTITION p3 VALUES LESS THAN (2005)
??? ->???? );
Query OK, 0 rows affected (0.01 sec)
?
mysql> INSERT INTO tr VALUES
??? ->???? (1, 'desk organiser', '2003-10-15'),
??? ->???? (2, 'CD player', '1993-11-05'),
??? ->???? (3, 'TV set', '1996-03-10'),
??? ->???? (4, 'bookcase', '1982-01-10'),
??? ->???? (5, 'exercise bike', '2004-05-09'),
??? ->???? (6, 'sofa', '1987-06-05'),
??? ->???? (7, 'popcorn maker', '2001-11-22'),
??? ->???? (8, 'aquarium', '1992-08-04'),
??? ->???? (9, 'study desk', '1984-09-16'),
??? ->???? (10, 'lava lamp', '1998-12-25');
Query OK, 10 rows affected (0.01 sec)?????????????????
可以通過使用下面的命令查看那些記錄已經插入到了分區p2中:
mysql> SELECT * FROM tr
??? -> WHERE purchased BETWEEN '1995-01-01' AND '1999-12-31';
+------+-----------+------------+
| id?? | name????? | purchased? |
+------+-----------+------------+
|??? 3 | TV set??? | 1996-03-10 |
|?? 10 | lava lamp | 1998-12-25 |
+------+-----------+------------+
2 rows in set (0.00 sec)
要刪除名字為p2的分區,執行下面的命令:
mysql> ALTER TABLE tr DROP PARTITION p2;
Query OK, 0 rows affected (0.03 sec)
記住下面一點非常重要:_當刪除了一個分區,也同時刪除了該分區中所有的數據。_可以通過重新運行前面的SELECT查詢來驗證這一點:
mysql> SELECT * FROM tr WHERE purchased
????-> BETWEEN '1995-01-01' AND '1999-12-31';
Empty set (0.00 sec)
如果希望從所有分區刪除所有的數據,但是又保留表的定義和表的分區模式,使用TRUNCATE TABLE命令。(請參見[13.2.9節,“TRUNCATE語法”](# "13.2.9.?TRUNCATE Syntax"))。
如果希望改變表的分區而又不丟失數據,使用“ALTER TABLE ... REORGANIZE PARTITION”語句。參見下面的內容,或者在[13.1.2節,“ALTER TABLE語法”](# "13.1.2.?ALTER TABLE Syntax")中參考關于REORGANIZE PARTITION的信息。
如果現在執行一個SHOW CREATE TABLE命令,可以觀察到表的分區結構是如何被改變的:
mysql> SHOW CREATE TABLE tr\G
*************************** 1. row ***************************
?????? Table: tr
Create Table: CREATE TABLE `tr` (
? `id` int(11) default NULL,
? `name` varchar(50) default NULL,
? `purchased` date default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
PARTITION BY RANGE (YEAR(purchased)) (
? PARTITION p0 VALUES LESS THAN (1990) ENGINE = MyISAM,
??PARTITION p1 VALUES LESS THAN (1995) ENGINE = MyISAM,
??PARTITION p3 VALUES LESS THAN (2005) ENGINE = MyISAM
)
1 row in set (0.01 sec)
如果插入購買日期列的值在'1995-01-01'和 '2004-12-31'之間(含)的新行到已經修改后的表中時,這些行將被保存在分區p3中。可以通過下面的方式來驗證這一點:
mysql> INSERT INTO tr VALUES (11, 'pencil holder', '1995-07-12');
Query OK, 1 row affected (0.00 sec)
?
mysql> SELECT * FROM tr WHERE purchased
????-> BETWEEN '1995-01-01' AND '2004-12-31';
+------+----------------+------------+
| id?? | name?????????? | purchased? |
+------+----------------+------------+
|?? 11 | pencil holder? | 1995-07-12 |
|??? 1 | desk organiser | 2003-10-15 |
|??? 5 | exercise bike? | 2004-05-09 |
|??? 7 | popcorn maker? | 2001-11-22 |
+------+----------------+------------+
4 rows in set (0.00 sec)
?
mysql> ALTER TABLE tr DROP PARTITION p3;
Query OK, 0 rows affected (0.03 sec)
?
mysql> SELECT * FROM tr WHERE purchased
????-> BETWEEN '1995-01-01' AND '2004-12-31';
Empty set (0.00 sec)
注意:由“ALTER TABLE ... DROP PARTITION”語句引起的、從表中刪除的行數并沒有被服務器報告出來,就好像通過同等的DELETE查詢操作一樣。
刪除LIST分區使用和刪除RANGE分區完全相同的“ALTER TABLE ... DROP PARTITION”語法。但是,在對其后使用這個表的影響方面,還是有重大的區別:在這個表中,再也不能插入這么一些行,這些行的列值包含在定義已經刪除了的分區的值列表中 (有關示例,請參見[18.2.2節,“LIST分區”](# "18.2.2.?LIST Partitioning"))。
要增加一個新的RANGE或LIST分區到一個前面已經分區了的表,使用“ALTER TABLE ... ADD PARTITION”語句。對于使用RANGE分區的表,可以用這個語句添加新的區間到已有分區的序列的前面或后面。例如,假設有一個包含你所在組織的全體成員數據的分區表,該表的定義如下:
CREATE TABLE members (
??? id INT,
????fname VARCHAR(25),
??? lname VARCHAR(25),
????dob DATE
)
PARTITION BY RANGE(YEAR(dob)) (
??? PARTITION p0 VALUES LESS THAN (1970),
??? PARTITION p1 VALUES LESS THAN (1980),
??? PARTITION p2 VALUES LESS THAN (1990)
);
進一步假設成員的最小年紀是16歲。隨著日歷接近2005年年底,你會認識到不久將要接納1990年(以及以后年份)出生的成員。可以按照下面的方式,修改成員表來容納出生在1990-1999年之間的成員:
ALTER TABLE ADD PARTITION (PARTITION p3 VALUES LESS THAN (2000));
**要點:對于通過RANGE分區的表,只可以使用**ADD PARTITION添加新的分區到分區列表的高端。設法通過這種方式在現有分區的前面或之間增加一個新的分區,將會導致下面的一個錯誤:
mysql> ALTER TABLE members ADD PARTITION (PARTITION p3 VALUES LESS THAN (1960));
錯誤1463 (HY000): 對每個分區,VALUES LESS THAN 值必須嚴格增長
采用一個類似的方式,可以增加新的分區到已經通過LIST分區的表。例如,假定有如下定義的一個表:
CREATE TABLE tt (
??? id INT,
????data INT
)
PARTITION BY LIST(data) (
??? PARTITION p0 VALUES IN (5, 10, 15),
??? PARTITION p1 VALUES IN (6, 12, 18)
);
可以通過下面的方法添加一個新的分區,用來保存擁有數據列值7,14和21的行:
ALTER TABLE tt ADD PARTITION (PARTITION p2 VALUES IN (7, 14, 21));
注意:不能添加這樣一個新的LIST分區,該分區包含有已經包含在現有分區值列表中的任意值。如果試圖這樣做,將會導致錯誤:
mysql> ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8, 12));
錯誤1465 (HY000): 在LIST分區中,同一個常數的多次定義
因為帶有數據列值12的任何行都已經分配給了分區p1,所以不能在表tt上再創建一個其值列表包括12的新分區。為了實現這一點,可以先刪除分區p1,添加分區np,然后使用修正后的定義添加一個新的分區p1。但是,正如我們前面討論過的,這將導致保存在分區p1中的所有數據丟失――而這往往并不是你所真正想要做的。另外一種解決方法可能是,建立一個帶有新分區的表的副本,然后使用“CREATE TABLE ... SELECT ...”把數據拷貝到該新表中,然后刪除舊表,重新命名新表,但是,當需要處理大量的數據時,這可能是非常耗時的。在需要高可用性的場合,這也可能是不可行的。
幸運地是,MySQL 的分區實現提供了在不丟失數據的條件下重新定義分區的方式。讓我們首先看兩個涉及到RANGE分區的簡單例子。回想一下現在定義如下的成員表:
mysql> SHOW CREATE TABLE members\G
*************************** 1. row ***************************
?????? Table: members
Create Table: CREATE TABLE `members` (
? `id` int(11) default NULL,
? `fname` varchar(25) default NULL,
? `lname` varchar(25) default NULL,
? `dob` date default NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1
PARTITION BY RANGE (YEAR(dob)) (
? PARTITION p0 VALUES LESS THAN (1970) ENGINE = MyISAM,
??PARTITION p1 VALUES LESS THAN (1980) ENGINE = MyISAM,
??PARTITION p2 VALUES LESS THAN (1990) ENGINE = MyISAM.
? PARTITION p3 VALUES LESS THAN (2000) ENGINE = MyISAM
)
假定想要把表示出生在1960年前成員的所有行移入到一個分開的分區中。正如我們前面看到的,不能通過使用“ALTER TABLE ... ADD PARTITION”來實現這一點。但是,要實現這一點,可以使用ALTER TABLE上的另外一個與分區有關的擴展,具體實現如下:
ALTER TABLE members REORGANIZE PARTITION p0 INTO (
??? PARTITION s0 VALUES LESS THAN (1960),
??? PARTITION s1 VALUES LESS THAN (1970)
);
實際上,這個命令把分區p0分成了兩個新的分區s0和s1。同時,它還根據包含在兩個“PARTITION ... VALUES ...”子句中的規則,把保存在分區p0中的數據移入到兩個新的分區中,所以分區s0中只包含YEAR(dob)小于1960的那些行,s1中包含那些YEAR(dob)大于或等于1960但是小于1970的行。
一個REORGANIZE PARTITION語句也可以用來合并相鄰的分區。可以使用如下的語句恢復成員表到它以前的分區:
ALTER TABLE members REORGANIZE PARTITION s0,s1 INTO (
??? PARTITION p0 VALUES LESS THAN (1970)
);
使用“REORGANIZE PARTITION”拆分或合并分區,沒有數據丟失。在執行上面的語句中,MySQL 把保存在分區s0和s1中的所有數據都移到分區p0中。
“REORGANIZE PARTITION”的基本語法是:
ALTER TABLE tbl_name REORGANIZE PARTITION partition_list INTO (partition_definitions);
其中,_tbl_name_是分區表的名稱,_partition_list_是通過逗號分開的、一個或多個將要被改變的現有分區的列表。_partition_definitions_是一個是通過逗號分開的、新分區定義的列表,它遵循與用在“CREATE TABLE”中的_partition_definitions_相同的規則 (請參見[13.1.5節,“CREATE TABLE語法”](# "13.1.5.?CREATE TABLE Syntax"))。應當注意到,在把多少個分區合并到一個分區或把一個分區拆分成多少個分區方面,沒有限制。例如,可以重新組織成員表的四個分區成兩個分區,具體實現如下:
ALTER TABLE members REORGANIZE PARTITION p0,p1,p2,p3 INTO (
??? PARTITION m0 VALUES LESS THAN (1980),
??? PARTITION m1 VALUES LESS THAN (2000)
);
同樣,對于按LIST分區的表,也可以使用REORGANIZE PARTITION。讓我們回到那個問題,即增加一個新的分區到已經按照LIST分區的表tt中,但是因為該新分區有一個值已經存在于現有分區的值列表中,添加新的分區失敗。我們可以通過先添加只包含非沖突值的分區,然后重新組織該新分區和現有的那個分區,以便保存在現有的那個分區中的值現在移到了新的分區中,來處理這個問題:
ALTER TABLE tt ADD PARTITION (PARTITION np VALUES IN (4, 8));
ALTER TABLE tt REORGANIZE PARTITION p1,np INTO (
??? PARTITION p1 VALUES IN (6, 18),
??? PARTITION np VALUES in (4, 8, 12)
);
當使用“ALTER TABLE ... REORGANIZE PARTITION”來對已經按照RANGE和LIST分區表進行重新分區時,下面是一些要記住的關鍵點:
????????? 用來確定新分區模式的PARTITION子句使用與用在CREATE TABLE中確定分區模式的PARTITION子句相同的規則。
最重要的是,應該記住:新分區模式不能有任何重疊的區間(適用于按照RANGE分區的表)或值集合(適用于重新組織按照LIST分區的表)。
????????? _ partition_definitions_ 列表中分區的合集應該與在_partition_list_中命名分區的合集占有相同的區間或值集合。
例如,在本節中用作例子的成員表中,分區p1和p2總共覆蓋了1980到1999的這些年。因此,對這兩個分區的重新組織都應該覆蓋相同范圍的年份。
????????? 對于按照RANGE分區的表,只能重新組織相鄰的分區;不能跳過RANGE分區。
例如,不能使用以“ALTER TABLE members REORGANIZE PARTITION p0,p2 INTO ...”開頭的語句,來重新組織本節中用作例子的成員表。因為,p0覆蓋了1970年以前的年份,而p2覆蓋了從1990到1999(包括1990和1999)之間的年份,因而這兩個分區不是相鄰的分區。
????????? 不能使用REORGANIZE PARTITION來改變表的分區類型;也就是說,例如,不能把RANGE分區變為HASH分區,反之亦然。也不能使用該命令來改變分區表達式或列。如果想在不刪除和重建表的條件下實現這兩個任務,可以使用“ALTER TABLE ... PARTITION BY ....”,例如:
???????????????? ALTER TABLE members
???????????????? ????PARTITION BY HASH(YEAR(dob))
???????????????? ????PARTITIONS 8;
**注釋**:在MySQL 5.1發布前的版本中,“ALTER TABLE ... PARTITION BY ...”還沒有實現。作為替代,
- 前言
- 1. 一般信息
- 2. 安裝MySQL
- 3. 教程
- 4. MySQL程序概述
- 5. 數據庫管理
- 6. MySQL中的復制
- 7. 優化
- 8. 客戶端和實用工具程序
- 9. 語言結構
- 10. 字符集支持
- 11. 列類型
- 12. 函數和操作符
- 13. SQL語句語法
- 14. 插件式存儲引擎體系結構
- 15. 存儲引擎和表類型
- 16. 編寫自定義存儲引擎
- 17. MySQL簇
- 18. 分區
- 19. MySQL中的空間擴展
- 20. 存儲程序和函數
- 21. 觸發程序
- 22. 視圖
- 23. INFORMATION_SCHEMA信息數據庫
- 24. 精度數學
- 25. API和庫
- 26. 連接器
- 27. 擴展MySQL
- A. 問題和常見錯誤
- B. 錯誤代碼和消息
- C. 感謝
- D. MySQL變更史
- E. 移植到其他系統
- F. 環境變量
- G. MySQL正則表達式
- H. MySQL中的限制
- I. 特性限制
- J. GNU通用公共許可
- K. MySQL FLOSS許可例外
- 索引