第18章:分區

目錄

18.1. MySQL中的分區概述
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值的方式
18.3. 分區管理
18.3.1. RANGE和LIST分區的管理
18.3.2. HASH和KEY分區的管理
18.3.3. 分區維護
18.3.4. 獲取關於分區的訊息

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

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

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個分區,那麼這些分區就編號為0, 1, 2, 3。對於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語句中使用一個「catchallVALUES 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, 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語法」 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不能在用於定義分區pNorth, pEast, pWest,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 ,其中「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分區」

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

註釋如果將要分區的資料表有一個唯一的鍵,那麼用來作為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個分區中的每一個分區——p0, p1, 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延伸得以實現,我們將更新手冊相關部分的內容。


這是MySQL參考手冊的翻譯版本,關於MySQL參考手冊,請訪問dev.mysql.com。原始參考手冊為英文版,與英文版參考手冊相比,本翻譯版可能不是最新的。