第18章:MYSQL分區

第18章:分區

本章討論MySQL 5.1.中實現的分區。關於分區和分區概念的介紹能夠在18.1節,「MySQL中的分區概述」中找到。MySQL 5.1 支持哪幾種類型的分區,在18.2節,「分區類型」 中討論。關於子分區在18.2.5節,「子分區」 中討論。現有分區表中分區的增長、刪除和修改的方法在18.3節,「分區管理」 中介紹。 和分區表一同使用的表維護命令在18.3.3節,「分區維護」 中介紹。html

請注意MySQL 5.1中的分區實現仍然很新(pre-alpha品質),此時還不是可生產的(not production-ready)。 一樣,許多也適用於本章:在這裏描述的一些功能尚未實際上實現(分區維護和從新分區命令),其餘的可能尚未徹底如所描述的那樣實現(例如用於分區的數據目錄(DATA DIRECTORY)和索引目錄(INDEX DIRECTORY)選項受到Bug #13520不利的影響). 咱們已經設法在本章中標出這些差別。在提出缺陷報告前,咱們鼓勵參考下面的一些資源:mysql

  • MySQL 分區論壇算法

    這是一個爲對MySQL分區技術感興趣或用MySQL分區技術作試驗提供的官方討論論壇。來自MySQL 的開發者和其餘的人,會在上面發表和更新有關的材料。它由分區開發和文獻團隊的成員負責監控。sql

  • 分區缺陷報告shell

    已經歸檔在缺陷系統中的、全部分區缺陷的一個列表,而不管這些缺陷的年限、嚴重性或當前的狀態如何。根據許多規則能夠對這些缺陷進行篩選,或者能夠從MySQL缺陷系統主頁開始,而後查找你特別感興趣的缺陷。數據庫

  • Mikael Ronström's Blogbash

    MySQL分區體系結構和領先的開發者Mikael Ronström 常常在這裏貼關於他研究MySQL 分區和MySQL簇的文章。服務器

  • PlanetMySQL數據結構

    一個MySQL 新聞網站,它以聚集MySQL相關的網誌爲特色,那些使用個人MySQL的人應該對此有興趣。咱們鼓勵查看那些研究MySQL分區的人的網誌連接,或者把你本身的網誌加到這些新聞報道中。

MySQL 5.1的二進制版本目前還不可用;可是,能夠從BitKeeper知識庫中得到源碼。要激活分區,須要使用--with-分區選項編譯服務器。關於創建MySQL 的更多信息,請參見2.8節,「使用源碼分發版安裝MySQL」。若是在編譯一個激活分區的MySQL 5.1建立中碰到問題,能夠在MySQL分區論壇中查找解決辦法,若是在論壇中已經貼出的文章中沒有找到問題的解決辦法,能夠在上面尋找幫助。

18.1. MySQL中的分區概述

本節提供了關於MySQL 5.1.分區在概念上的概述。

SQL標準在數據存儲的物理方面沒有提供太多的指南。SQL語言的使用獨立於它所使用的任何數據結構或圖表、表、行或列下的介質。可是,大部分高級數據庫管理系統已經開發了一些根據文件系統、硬件或者這二者來肯定將要用於存儲特定數據塊物理位置的方法。在MySQL,InnoDB存儲引擎長期支持表空間的概念,而且MySQL服務器甚至在分區引入以前,就能配置爲存儲不一樣的數據庫使用不一樣的物理路徑(關於如何配置的解釋,請參見7.6.1節,「使用符號連接」)

分區又把這個概念推動了一步,它容許根據能夠設置爲任意大小的規則,跨文件系統分配單個表的多個部分。實際上,表的不一樣部分在不一樣的位置被存儲爲單獨的表。用戶所選擇的、實現數據分割的規則被稱爲分區函數這在MySQL中它能夠是模數,或者是簡單的匹配一個連續的數值區間或數值列表,或者是一個內部HASH函數,或一個線性HASH函數。函數根據用戶指定的分區類型來選擇,把用戶提供的表達式的值做爲參數。該表達式能夠是一個整數列值,或一個做用在一個或多個列值上並返回一個整數的函數。這個表達式的值傳遞給分區函數,分區函數返回一個表示那個特定記錄應該保存在哪一個分區的序號。這個函數不能是常數,也不能是任意數。它不能包含任何查詢,可是實際上可使用MySQL 中任何可用的SQL表達式,只要該表達式返回一個小於MAXVALUE(最大可能的正整數)的正數值。分區函數的例子能夠在本章後面關於分區類型的討論中找到 (請參見18.2節,「分區類型」 ),也可在13.1.5節,「CREATE TABLE語法」的分區語法描述中找到。

當二進制碼變成可用時(也就是說,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_ROWSMIN_ROWS選項能夠用來設定最大和最小的行數,它們能夠各自保存在每一個分區裏。關於這些選項的更多信息,請參見18.3節,「分區管理」註釋這個特殊的功能因爲Bug #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. 分區類型

本節討論在MySQL 5.1中可用的分區類型。這些類型包括:

·         RANGE 分區基於屬於一個給定連續區間的列值,把多行分配給分區。參見18.2.1節,「RANGE分區」

·         LIST 分區相似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇。參見18.2.2節,「LIST分區」

·         HASH分區基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數能夠包含MySQL 中有效的、產生非負整數值的任何表達式。參見18.2.3節,「HASH分區」

·         KEY 分區:相似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值。參見18.2.4節,「KEY分區」

不管使用何種類型的分區,分區老是在建立時就自動的順序編號,且從0開始記錄,記住這一點很是重要。當有一新行插入到一個分區表中時,就是使用這些分區編號來識別正確的分區。例如,若是你的表使用4個分區,那麼這些分區就編號爲0123。對於RANGELIST分區類型,確認每一個分區編號都定義了一個分區,頗有必要。對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認爲分區名字mypartMyPart沒有區別。

註釋在下面的章節中,咱們沒有必要提供能夠用來建立每種分區類型語法的全部可能形式,這些信息能夠在13.1.5節,「CREATE TABLE語法」 中找到。

18.2.1. RANGE分區

按照RANGE分區的表是經過以下一種方式進行分區的,每一個分區包含那些分區表達式的值位於一個給定的連續區間內的行。這些區間要連續且不能相互重疊,使用VALUES LESS THAN操做符來進行定義。在下面的幾個例子中,假定你建立了一個以下的一個表,該表保存有20家音像店的職員記錄,這20家音像店的編號從120

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)
)

按照這種分區方案,在商店15工做的僱員相對應的全部行被保存在分區P0中,商店610的僱員保存在P1中,依次類推。注意,每一個分區都是按順序進行定義,從最低到最高。這是PARTITION BY RANGE 語法的要求;在這點上,它相似於CJava中的「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節,「分區管理」 )

在幾乎同樣的結構中,你還能夠基於僱員的工做代碼來分割表,也就是說,基於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,separatedjob_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語法」 和 18.3節,「分區管理」)。對於有大量行的表,這比運行一個如「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語法」 

對於下面給出的例子,咱們假定將要被分區的表的基本定義是經過下面的「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分區」 中的例子中使用的是同一個表)。 

假定有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不能在用於定義分區pNorthpEastpWest,pCentral的值列表中找到。要重點注意的是,LIST分區沒有相似如「VALUES LESS THAN MAXVALUE」這樣的包含其餘值在內的定義。將要匹配的任何值都必須在值列表中找到。

LIST分區除了能和RANGE分區結合起來生成一個複合的子分區,與HASHKEY分區結合起來生成複合的子分區也是可能的。 關於這方面的討論,請參考18.2.5節,「子分區」

18.2.3. HASH分區

HASH分區主要用來確保數據在預先肯定數目的分區中平均分佈。在RANGELIST分區中,必須明確指定一個給定的列值或列值集合應該保存在哪一個分區中;而在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簇

若是在關鍵字「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 = MOD(exprnum)」。例如,假定表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分區」 

每當插入或更新一條記錄,用戶函數都要計算一次。當刪除記錄時,用戶函數也可能要進行計算,這取決於所處的環境。

註釋若是將要分區的表有一個惟一的鍵,那麼用來做爲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)))

(例如,假定num13。那麼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.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 3RANGE分區。這3個分區中的每個分區——p0p1和 p2 ——又被進一步分紅了2個子分區。實際上,整個表被分紅了3 * 2 = 6個分區。可是,因爲PARTITION BY RANGE子句的做用,這些分區的頭2個只保存「purchased」列中值小於1990的那些記錄。

MySQL 5.1中,對於已經經過RANGELIST分區了的表再進行子分區是可能的。子分區既可使用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.2.6. MySQL分區處理NULL值的方式

MySQL 中的分區在禁止空值(NULL)上沒有進行處理,不管它是一個列值仍是一個用戶定義表達式的值。通常而言,在這種狀況下MySQL NULL視爲0。若是你但願迴避這種作法,你應該在設計表時不容許空值;最可能的方法是,經過聲明列「NOT NULL」來實現這一點。

在本節中,咱們提供了一些例子,來講明當決定一個行應該保存到哪一個分區時,MySQL 是如何處理NULL值的。

若是插入一行到按照RANGELIST分區的表,該行用來肯定分區的列值爲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)

在按HASHKEY分區的狀況下,任何產生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. 分區管理

MySQL 5.1 提供了許多修改分區表的方式。添加、刪除、從新定義、合併或拆分已經存在的分區是可能的。全部這些操做均可以經過使用ALTER TABLE 命令的分區擴展來實現(關於語法的定義,請參見13.1.2節,「ALTER TABLE語法」 )。也有得到分區表和分區信息的方式。在本節,咱們討論下面這些主題:

·         RANGELIST分區的表的分區管理的有關信息,請參見18.3.1節,「RANGE和LIST分區的管理」

·         關於HASHKEY分區管理的討論,請參見18.3.2節,「HASH和KEY分區的管理

·         MySQL 5.1中提供的、得到關於分區表和分區信息的機制的討論,請參見18.3.4節,「獲取關於分區的信息」 

·         關於執行分區維護操做的討論,請參見18.3.3節,「分區維護」

註釋:在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分區的管理

關於如何添加和刪除分區的處理,RANGELIST分區很是類似。基於這個緣由,咱們在本節討論這兩種分區的管理。關於HASHKEY分區管理的信息,請參見18.3.2節,「HASH和KEY分區的管理。刪除一個RANGELIST分區比增長一個分區要更加簡單易懂,因此咱們先討論前者。

從一個按照RANGELIST分區的表中刪除一個分區,可使用帶一個DROP PARTITION子句的ALTER TABLE命令來實現。這裏有一個很是基本的例子,假設已經使用下面的CREATE TABLEINSERT語句建立了一個按照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語法」

若是但願改變表的分區而又不丟失數據,使用「ALTER TABLE ... REORGANIZE PARTITION」語句。參見下面的內容,或者在13.1.2節,「ALTER TABLE語法」 中參考關於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分區」 

要增長一個新的RANGELIST分區到一個前面已經分區了的表,使用「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年(以及之後年份)出生的成員。能夠按照下面的方式,修改爲員表來容納出生在19901999年之間的成員:

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)
)

能夠經過下面的方法添加一個新的分區,用來保存擁有數據列值71421的行:

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分紅了兩個新的分區s0s1。同時,它還根據包含在兩個「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 把保存在分區s0s1中的全部數據都移到分區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語法」)。應當注意到,在把多少個分區合併到一個分區或把一個分區拆分紅多少個分區方面,沒有限制。例如,能夠從新組織成員表的四個分區成兩個分區,具體實現以下:

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」來對已經按照RANGELIST分區表進行從新分區時,下面是一些要記住的關鍵點:

·         用來肯定新分區模式的PARTITION子句使用與用在CREATE TABLE中肯定分區模式的PARTITION子句相同的規則。

最重要的是,應該記住:新分區模式不能有任何重疊的區間(適用於按照RANGE分區的表)或值集合(適用於從新組織按照LIST分區的表)。

·         partition_definitions 列表中分區的合集應該與在partition_list 中命名分區的合集佔有相同的區間或值集合。

例如,在本節中用做例子的成員表中,分區p1p2總共覆蓋了19801999的這些年。所以,對這兩個分區的從新組織都應該覆蓋相同範圍的年份。

·         對於按照RANGE分區的表,只能從新組織相鄰的分區;不能跳過RANGE分區。

例如,不能使用以「ALTER TABLE members REORGANIZE PARTITION p0,p2 INTO ...」開頭的語句,來從新組織本節中用做例子的成員表。由於,p0覆蓋了1970年之前的年份,而p2覆蓋了從19901999(包括19901999)之間的年份,於是這兩個分區不是相鄰的分區。

·         不能使用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 ...」尚未實現。做爲替代,要麼使用先刪除表,而後使用想要的分區重建表,或者——若是須要保留已經存儲在表中的數據——可使用「CREATE TABLE ... SELECT ...」來建立新的表,而後從舊錶中把數據拷貝到新表中,再刪除舊錶,若有必要,最後從新命名新表。

18.3.2. HASH和KEY分區的管理

在改變分區設置方面,按照HASH分區或KEY分區的表彼此很是類似,可是它們又與按照RANGELIST分區的表在不少方面有差異。因此,本節只討論按照HASHKEY分區表的修改。關於添加和刪除按照RANGELIST進行分區的表的分區的討論,參見18.3.1節,「RANGE和LIST分區的管理」

不能使用與從按照RANGELIST分區的表中刪除分區相同的方式,來從HASHKEY分區的表中刪除分區。可是,可使用「ALTER TABLE ... COALESCE PARTITION」命令來合併HASHKEY分區。例如,假定有一個包含顧客信息數據的表,它被分紅了12個分區。該顧客表的定義以下:

CREATE TABLE clients (
    id INT,
    fname VARCHAR(30),
    lname VARCHAR(30),
    signed DATE
)
PARTITION BY HASH( MONTH(signed) )
PARTITIONS 12

要減小分區的數量從126,執行下面的ALTER TABLE命令:

mysql> ALTER TABLE clients COALESCE PARTITION 6
Query OK, 0 rows affected (0.02 sec)

對於按照HASHKEYLINEAR HASH,或LINEAR KEY分區的表, COALESCE能起到一樣的做用。下面是一個相似於前面例子的另一個例子,它們的區別只是在於表是按照LINEAR KEY 進行分區:

mysql> CREATE TABLE clients_lk (
    ->     id INT,
    ->     fname VARCHAR(30),
    ->     lname VARCHAR(30),
    ->     signed DATE
    -> )
    -> PARTITION BY LINEAR KEY(signed)
    -> PARTITIONS 12
Query OK, 0 rows affected (0.03 sec)
 
mysql> ALTER TABLE clients_lk COALESCE PARTITION 6
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

COALESCE不能用來增長分區的數量,若是你嘗試這麼作,結果會出現相似於下面的錯誤:

mysql> ALTER TABLE clients COALESCE PARTITION 18;
錯誤1478 (HY000): 不能移動全部分區,使用DROP TABLE代替

要增長顧客表的分區數量從1218,使用「ALTER TABLE ... ADD PARTITION」,具體以下:

ALTER TABLE clients ADD PARTITION PARTITIONS 18

註釋ALTER TABLE ... REORGANIZE PARTITION」不能用於按照HASHHASH分區的表。

18.3.3. 分區維護

註釋實際上,本節討論的命令尚未在MySQL 5.1中實現, 在這裏提出的目的,是爲了在5.1版投產前的開發週期期間,引出來自用戶測試該軟件的反饋意見。(換句話說,就是「請不要反饋這樣的缺陷,說這些命令不起做用」)隨着MySQL5.1版開發的繼續,這些信息頗有可能發生變化。隨着分區功能的實現和提升,咱們將更新本節的內容。

MySQL 5.1中能夠執行許多分區維護的任務。對於分區表,MySQL不支持命令CHECK TABLEOPTIMIZE TABLEANALYZE TABLE,或REPAIR TABLE。做爲替代,可使用ALTER TABLE 的許多擴展來在一個或多個分區上直接地執行這些操做,以下面列出的那樣:

·         重建分區這和先刪除保存在分區中的全部記錄,而後從新插入它們,具備一樣的效果。它可用於整理分區碎片。

示例:

ALTER TABLE t1 REBUILD PARTITION (p0, p1)

·         優化分區:若是從分區中刪除了大量的行,或者對一個帶有可變長度的行(也就是說,有VARCHARBLOB,或TEXT類型的列)做了許多修改,可使用「ALTER TABLE ... OPTIMIZE PARTITION」來收回沒有使用的空間,並整理分區數據文件的碎片。

示例:

ALTER TABLE t1 OPTIMIZE PARTITION (p0, p1)

在一個給定的分區表上使用「OPTIMIZE PARTITION」等同於在那個分區上運行CHECK PARTITIONANALYZE PARTITION,和REPAIR PARTITION

·         分析分區:讀取並保存分區的鍵分佈。

示例:

ALTER TABLE t1 ANALYZE PARTITION (p3)

·         修補分區: 修補被破壞的分區。

示例:

ALTER TABLE t1 REPAIR PARTITION (p0,p1);

·         檢查分區: 可使用幾乎與對非分區表使用CHECK TABLE 相同的方式檢查分區。

示例:

ALTER TABLE trb3 CHECK PARTITION (p1)

這個命令能夠告訴你表t1的分區p1中的數據或索引是否已經被破壞。若是發生了這種狀況,使用「ALTER TABLE ... REPAIR PARTITION」來修補該分區。

還可使用mysqlcheckmyisamchk 應用程序,在對錶進行分區時所產生的、單獨的MYI文件上進行操做,來完成這些任務。請參見8.7節,「mysqlcheck:表維護和維修程序」。(在pre-alpha編碼中,這個功能已經可使用)。

18.3.4. 獲取關於分區的信息

本節討論獲取關於現有分區的信息。這個功能仍然處於計劃階段,因此現階段在這裏描述的,其實是咱們想要在MySQL 5.1中實現的一個概觀。

如在本章中別處討論的同樣,在SHOW CREATE TABLE的輸出中包含了用於建立分區表的PARTITION BY子句。例如:

mysql> SHOW CREATE TABLE trb3\G
*************************** 1. row ***************************
       Table: trb3
Create Table: CREATE TABLE `trb3` (
  `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 p2 VALUES LESS THAN (2000) ENGINE = MyISAM, 
  PARTITION p3 VALUES LESS THAN (2005) ENGINE = MyISAM
)
1 row in set (0.00 sec)

註釋:當前,對於按HASHKEY分區的表,PARTITIONS子句並不顯示。 (Bug #14327)

SHOW TABLE STATUS用於分區表,它的輸出與用於非分區表的輸出相同,除了引擎(Engine)列老是包含'PARTITION'值。(關於這個命令的更多信息,參見13.5.4.18節,「SHOW TABLE STATUS語法」要獲取單個分區的狀態信息,咱們計劃實現一個SHOW PARTITION STATUS命令請參見下面)

計劃用於分區表的、兩個附加的SHOW命令是:

·         SHOW PARTITIONS

這個命令預期其功能相似於SHOW TABLESSHOW DATABASES,除了該命令將列出的是分區而不是表或數據庫。這個命令的輸出可能包含單個稱爲Partitions_in_tbl_name 的列,其中tbl_name 是分區表的名字。對於SHOW TABLES命令而言,若是一旦選擇了一個數據庫,隨後該數據庫將做爲SHOW TABLES命令的默認數據庫。可是因爲SHOW PARTITIONS命令不可能用這樣的方式來選擇一個表,它極可能須要使用FROM子句,以便MySQL知道要顯示的是哪一個表的分區信息。

·         SHOW PARTITION STATUS

這個命令將提供關於一個或多個分區的詳細狀態信息。它的輸出極可能包含有與SHOW TABLE STATUS 的輸出相同或相似的列,此外,還包括顯示用於分區的數據和索引路徑的附加列。這個命令可能支持LIKEFROM子句,這樣使得經過名字得到關於一個給定分區的信息,或者得到關於屬於指定表或數據庫的分區的信息,成爲可能。

擴展INFORMATION_SCHEMA 數據庫的計劃也在進行中,以便提供關於分區表和分區的信息。這個計劃當前還處一個在很是早的階段;隨着補充的信息變得可用,以及任何新的、與分區有關的INFORMATION_SCHEMA擴展得以實現,咱們將更新手冊相關部分的內容。

相關文章
相關標籤/搜索