MySQL(十三):分區表( Partitioning Table)

1、MySQL 分區簡介

1.1、什麼是分區表

分區表就是將表數據通過指定的規則存儲在不同的文件中,這些文件可以在單個磁盤或多個磁盤上。分區後,邏輯上還是一張數據表,對分區表進行讀寫時,數據庫會自動找到請求的分區。

1.2、分區表優點

在日常的工作中,我們會遇到一張表裏面保存了千萬或上億的記錄。當對數據進行操作時,經常會攜帶多個where條件,但是這樣仍會對數據庫的造成了很大壓力,很難達到我們期望的效果。
即使我們刪除了一些數據,但底層的數據文件並沒有變小。針對這些問題,使用分區表便可以迎刃而解了。

1、單個分區表可以存儲更多的數據

一個數據表通過分區可以將數據存儲在多個磁盤或多個文件中,與單個磁盤或文件系統分區相比,可以存儲更多的數據。

2、可以靈活刪除和新增數據

通過刪除一個或多個分區來刪除無用數據,使刪除變得更簡單。相反,在某些情況下添加新數據的過程可以通過添加一個或多個新分區來專門存儲該數據而得到極大的便利。

3、MySQL支持顯式的分區選擇查詢

例如,SELECT * FROM t PARTITION(p0,p1)WHERE c <5 僅選擇分區p0和p1中與WHERE條件匹配的那些行。
在這種情況下,MySQL不會檢查表t的其他分區。當已經知道要檢查的分區時,這可以大大加快查詢速度。
數據修改語句DELETE,INSERT,REPLACE,UPDATE和LOAD DATA,LOAD XML也支持分區選擇。

4、可以很容易使用並行處理

涉及到例如SUM()和COUNT()這樣聚合函數的查詢,可以很容易地進行並行處理。
例如,SELECT username, COUNT (address) as address_num FROM tb_user GROUP BY username。
該查詢語句可以在多個分區上同時進行,通過彙總各個分區結果得到最終數據。

5、通過跨多個磁盤來分散數據查詢,可以獲得更大的查詢吞吐量。

6、在創建分區表後可以更改,因此可以重新組織數據,以調整爲合適的分區方案。

1.3、分區表缺點

1、目前應用中的大部分表都有主鍵,受限於分區表達式的規則,往往需要重新創建表結構,可能會影響現有業務。

2、分區表的實現機制有額外的開銷,當分區表很多時,開銷會越來越大。“根據實際經驗對於大多數系統100個左右的分區是沒問題的”摘自《高性能MySQL》。

3、打開並鎖住所有底層表的成本可能很高,當查詢分區表的時候,MySQL需要打開並鎖住所有的底層表,這是分區表的另外一個開銷。

4、維護分區的成本可能會很高,新增、刪除分區速度會很快。但是重組或ALERT分區表時,會先創建一個臨時分區,然後將數據複製到其中,再刪除原分區。

2、MySQL 分區類型

爲簡單起見,以下示例中的表不使用任何鍵。 如果表具有任何唯一鍵,則該表的分區表達式中使用的每一列都必須是唯一鍵(包括主鍵)的一部分。

MySQL 8.0中包含以下可用的分區類型:

(1)RANGE分區:基於屬於一個給定連續區間的列值,把多行分配給分區。

(2)LIST分區:類似於按RANGE分區,區別在於LIST分區是基於列值匹配一個離散值集合中的某個值來進行選擇。

(3)HASH分區:基於用戶定義的表達式的返回值來進行選擇的分區,該表達式使用將要插入到表中的這些行的列值進行計算。這個函數可以包含MySQL 中有效的、產生非負整數值的任何表達式。

(4)KEY分區:類似於按HASH分區,區別在於KEY分區只支持計算一列或多列,且MySQL 服務器提供其自身的哈希函數。必須有一列或多列包含整數值

(5)複合分區:基於RANGE/LIST 類型的分區表中每個分區的再次分割。子分區可以是 HASH/KEY等類型。

數據庫分區的一種非常常見的用法是按日期分區。 某些數據庫系統支持顯式日期分區,而MySQL在8.0中未實現。 但是,在MySQL中可以創建基於DATE,TIME或DATETIME列或基於使用此類列的表達式的分區。

通過KEY或LINEAR KEY進行分區時,可以將DATE,TIME或DATETIME列用作分區列,而無需對列值進行任何修改。

2.1、範圍分區(RANGE)

按範圍分區的表的分區方式是,基於屬於一個給定連續區間的列值,把多行分配給分區。最常見的是基於時間字段. 基於分區的列最好是整型,如果日期型的可以使用函數轉換爲整型。

範圍應該是連續的,但不能重疊,並使用VALUES LESS THAN運算符定義。

2.1.1、基於時間間隔的分區方案。

如果希望在MySQL 8.0中基於時間範圍實現分區方案,則有兩個選擇:

2.1.1.1、按 RANGE 對錶進行分區

按 RANGE 對錶進行分區,對於分區表達式,要使用對 DATE、TIME、DATETIME 列操作返回整數值的函數,如下所示:

CREATE TABLE `employees_range_date` (
  `emp_no` INT NOT NULL,
  `birth_date` DATE NOT NULL,
  `first_name` VARCHAR(14) NOT NULL,
  `last_name` VARCHAR(16) NOT NULL,
  `gender` ENUM('M','F') NOT NULL,
  `hire_date` DATE NOT NULL
) 
PARTITION BY RANGE(YEAR(hire_date))(
    PARTITION p0 VALUES LESS THAN (1960),
    PARTITION p1 VALUES LESS THAN (1970),
    PARTITION p2 VALUES LESS THAN (1980),
    PARTITION p3 VALUES LESS THAN (1990),
    PARTITION p4 VALUES LESS THAN MAXVALUE
);

插入數據:

INSERT INTO employees_range_date SELECT * FROM employees;

查看查詢的執行計劃:

EXPLAIN SELECT * FROM employees_range_date WHERE hire_date >= '1986-06-26' AND hire_date <='1990-12-01'; 

    id  select_type  table                 partitions  type    possible_keys  key     key_len  ref       rows  filtered  Extra        
------  -----------  --------------------  ----------  ------  -------------  ------  -------  ------  ------  --------  -------------
     1  SIMPLE       employees_range_date  p3,p4       ALL     (NULL)         (NULL)  (NULL)   (NULL)  299298     11.11  Using where  

從執行計劃中的partitions的內容來看,只查詢了p3,p4 兩個分區。

p4 是一個默認分區,所有大於 1990 的記錄都會在這個分區。MAXVALUE 是一個無窮大的值。p4 是一個可選分區。如果在定義表的沒有指定的這個分區,當我們插入大於 1990 的數據的時,會收到一個錯誤。

在 MySQL 8.0 中,還可以使用UNIX_TIMESTAMP()函數根據TIMESTAMP列的值按RANGE對錶進行分區,如以下示例所示:

CREATE TABLE `employees_range_timestamp` (
  `emp_no` INT NOT NULL,
  `birth_date` DATE NOT NULL,
  `first_name` VARCHAR(14) NOT NULL,
  `last_name` VARCHAR(16) NOT NULL,
  `gender` ENUM('M','F') NOT NULL,
  `hire_date` TIMESTAMP NOT NULL
) 
PARTITION BY RANGE(UNIX_TIMESTAMP(hire_date))(    
    PARTITION p0 VALUES LESS THAN ( UNIX_TIMESTAMP('1960-12-31 00:00:00') ),
    PARTITION p1 VALUES LESS THAN ( UNIX_TIMESTAMP('1970-12-31 00:00:00') ),
    PARTITION p2 VALUES LESS THAN ( UNIX_TIMESTAMP('1980-12-31 00:00:00') ),
    PARTITION p3 VALUES LESS THAN ( UNIX_TIMESTAMP('1990-12-31 00:00:00') ),
    PARTITION p4 VALUES LESS THAN ( UNIX_TIMESTAMP('2020-12-31 00:00:00') )
);

插入數據:

mysql> INSERT INTO employees_range_timestamp SELECT emp_no,birth_date,first_name,last_name,gender,hire_date FROM employees;

Query OK, 300024 rows affected (2 min 9.72 sec)
Records: 300024  Duplicates: 0  Warnings: 0

查看查詢的執行計劃:

mysql> EXPLAIN SELECT * FROM employees_range_timestamp WHERE hire_date >= '1986-06-26' AND hire_date <='1990-12-01'; 
+----+-------------+---------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table                     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees_range_timestamp | p3         | ALL  | NULL          | NULL | NULL    | NULL | 190059 |    11.11 | Using where |
+----+-------------+---------------------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

2.1.1.2、按 RANGE COLUMNS 對錶分區

使用DATE或DATETIME列作爲分區列,按RANGE COLUMNS分區表。如下所示:

CREATE TABLE `employees_range_columns` (
  `emp_no` INT NOT NULL,
  `birth_date` DATE NOT NULL,
  `first_name` VARCHAR(14) NOT NULL,
  `last_name` VARCHAR(16) NOT NULL,
  `gender` ENUM('M','F') NOT NULL,
  `hire_date` DATE NOT NULL
) 
PARTITION BY RANGE COLUMNS(hire_date)(    
    PARTITION p0 VALUES LESS THAN ('1960-12-31 00:00:00'),
    PARTITION p1 VALUES LESS THAN ('1970-12-31 00:00:00'),
    PARTITION p2 VALUES LESS THAN ('1980-12-31 00:00:00'),
    PARTITION p3 VALUES LESS THAN ('1990-12-31 00:00:00'),
    PARTITION p4 VALUES LESS THAN ('2020-12-31 00:00:00')
);

插入數據:

mysql> INSERT INTO employees_range_columns SELECT emp_no,birth_date,first_name,last_name,gender,hire_date FROM employees;
Query OK, 300024 rows affected (15.91 sec)
Records: 300024  Duplicates: 0  Warnings: 0

查看查詢計劃:

mysql> EXPLAIN SELECT * FROM employees_range_columns WHERE hire_date >= '1970-06-26' AND hire_date <='2020-12-01';
+----+-------------+-------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table                   | partitions  | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees_range_columns | p1,p2,p3,p4 | ALL  | NULL          | NULL | NULL    | NULL | 299495 |    11.11 | Using where |
+----+-------------+-------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

注意:RANGE COLUMNS 不支持使用 DATE 或 DATETIME 以外的日期類型的分區列。

2.2、列表分區(LIST)

MySQL中的列表分區在很多方面類似於範圍分區。 與按RANGE進行分區一樣,必須明確定義每個分區。 兩種類型的分區之間的主要區別在於,在列表分區中,每個分區都是基於一組值列表中的一個而不是一組連續範圍中的列值的成員來定義和選擇的。
這是通過使用PARTITION BY LIST(expr)
(其中expr是列值或基於列值的表達式)並返回整數值,然後通過VALUES IN(value_list)定義每個分區來完成的,其中value_list是一個以逗號分隔的整數列表。

CREATE TABLE t_list (
    a INT NOT NULL,    
    b INT NOT NULL
)
PARTITION BY LIST(b ) (
    PARTITION p0 VALUES IN (3,5,6,9,17),
    PARTITION p1 VALUES IN (1,2,10,11,19,20)
);

插入數據:

insert into `t_list` (`a`, `b`) values('1','1');
insert into `t_list` (`a`, `b`) values('2','2');
insert into `t_list` (`a`, `b`) values('3','3');
insert into `t_list` (`a`, `b`) values('5','5');
insert into `t_list` (`a`, `b`) values('6','6');
insert into `t_list` (`a`, `b`) values('9','9');
insert into `t_list` (`a`, `b`) values('17','17');

查看查詢計劃:

mysql> EXPLAIN SELECT * FROM t_list WHERE b<10;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t_list | p0,p1      | ALL  | NULL          | NULL | NULL    | NULL |    7 |    33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

2.3、HASH分區(HASH)

通過HASH進行分區主要用於確保在預定數量的分區之間均勻分佈數據。 對於範圍或列表分區,必須明確指定將給定列值或一組列值存儲在哪個分區中;
對於HASH分區,您只需要根據要散列的列值以及要將分區表劃分的分區數來指定列值或表達式。

要使用HASH分區對錶進行分區,必須在 CREATE TABLE 語句後附加 PARTITION BY HASH(expr)子句,其中expr是返回整數的表達式。
如果是列的名稱,該列的類型必須是MySQL的整數類型之一。
此外,您需要在此後加上 PARTITIONS num,其中num是一個正整數,表示表將被劃分的分區數。

根據指定列值和指定分區數進行分區,示例中根據員工號將數據表劃分爲4個分區:

CREATE TABLE `employees_hash` (
  `emp_no` INT NOT NULL,
  `birth_date` DATE NOT NULL,
  `first_name` VARCHAR(14) NOT NULL,
  `last_name` VARCHAR(16) NOT NULL,
  `gender` ENUM('M','F') NOT NULL,
  `hire_date` DATE NOT NULL
) PARTITION BY HASH(emp_no)
PARTITIONS 4;

(1)HASH 分區可以不用指定 PARTITIONS 子句,如上文中的PARTITIONS 4,則默認分區數爲1。

(2)不允許只寫PARTITIONS,而不指定分區數。

(3)同RANGE分區和LIST分區一樣,PARTITION BY HASH (expr)子句中的expr返回的必須是整數值。

(4)HASH分區的底層實現其實是基於MOD函數。比如,對於下表

CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    PARTITION BY HASH( YEAR(col3) )
    PARTITIONS 4;

如果你要插入一個col3爲 ‘2005-09-15’ 的記錄,則分區的選擇是根據以下值決定的:

MOD(YEAR('2005-09-01'),4)
=  MOD(2005,4)
=  1

2.3.1、LINEAR HASH 分區

MySQL 還支持線性HASH,這與常規HASH的不同之處在於,線性HASH使用線性二乘冪算法,而常規HASH則使用散列函數值的模數。

CREATE TABLE `employees_linear_hash` (
  `emp_no` INT NOT NULL,
  `birth_date` DATE NOT NULL,
  `first_name` VARCHAR(14) NOT NULL,
  `last_name` VARCHAR(16) NOT NULL,
  `gender` ENUM('M','F') NOT NULL,
  `hire_date` DATE NOT NULL
) PARTITION BY LINEAR HASH( emp_no )
PARTITIONS 4;

(1)查找下一個大於2的冪。 我們稱這個值爲V; 可以計算爲:

V = POWER(2, CEILING(LOG(2, num)))

假設num爲13。則LOG(2,13)爲3.7004397181411。CEILING(3.7004397181411)爲4,而V = POWER(2,4)爲16。

(2)N = F(column_list) & (V - 1)

(3)當N> = num時:設定 V = V / 2,設置N = N&(V-1)

假設使用以下語句創建使用線性哈希分區並具有6個分區的表t1:

CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
    PARTITION BY LINEAR HASH( YEAR(col3) )
    PARTITIONS 6;

現在假設您要在t1中插入兩個記錄,它們的col3列值爲’2003-04-14’和’1998-10-19’。 其中第一個的分區號如下:

V = POWER(2, CEILING( LOG(2,6) )) = 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 >= 6 是 TRUE:繼續執行)

N = 6 & ((8 / 2) - 1)
  = 6 & 3
  = 2

(2 >= 6 是 FALSE: 記錄存儲在分區號 2 中)

它的優點是在數據量大的場景,比如TB級,增加、刪除、合併和拆分分區會更快,缺點是,相對於HASH分區,它數據分佈不均勻的概率更大。

2.4、KEY 分區(KEY)

KEY分區類似於 HASH 分區,不同點如下:

(1)KEY分區允許多列,而HASH分區只允許一列。

(2)如果在有主鍵或者唯一鍵的情況下,key中分區列可不指定,默認爲主鍵或者唯一鍵,如果沒有,則必須顯性指定列。

(3)KEY分區對象必須爲列,而不能是基於列的表達式。

(4)KEY分區和HASH分區的算法不一樣,PARTITION BY HASH (expr),MOD取值的對象是expr返回的值,而PARTITION BY KEY (column_list),基於的是列的MD5值。

如果沒有將列名指定爲分區鍵,則使用表的主鍵(如果有)。

CREATE TABLE k1 (
    id INT NOT NULL PRIMARY KEY,
    name VARCHAR(20)
)
PARTITION BY KEY()
PARTITIONS 2;

如果沒有主鍵,但是有一個唯一鍵,則將唯一鍵用於分區鍵:

CREATE TABLE k1 (
    id INT NOT NULL,
    name VARCHAR(20),
    UNIQUE KEY (id)
)
PARTITION BY KEY()
PARTITIONS 2;

與其他分區類型不同,用於KEY分區的列不限於整數或NULL值。 例如,以下CREATE TABLE語句有效:

CREATE TABLE tm1 (
    s1 CHAR(32) PRIMARY KEY
)
PARTITION BY KEY(s1)
PARTITIONS 10;

分區鍵不支持帶有索引前綴的列。 這意味着CHAR,VARCHAR,BINARY和VARBINARY列可以在分區鍵中使用,只要它們不使用前綴即可。 因爲必須在索引定義中爲BLOB和TEXT列指定前綴,所以不能在分區鍵中使用這兩種類型的列。

在MySQL 8.0.21之前,創建,更改或升級分區表時允許使用前綴的列,即使它們未包含在表的分區鍵中也是如此;

在MySQL 8.0.21和更高版本中,不贊成這種行爲,並且當使用一個或多個此類列時,服務器會顯示適當的警告或錯誤。

對於 key 分區表,無法執行ALTER TABLE DROP PRIMARY KEY,因爲這樣做會產生錯誤ERROR 1466(HY000):分區功能字段列表中的字段未在表中找到。

2.5、子分區(Subpartitioning)

子分區(也稱爲複合分區)是對分區表中每個分區的進一步劃分。 例如以下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分區。 這些分區(p0,p1和p2)中的每一個都進一步分爲2個子分區。 實際上,整個表分爲3 * 2 = 6個分區。

注意事項:

(1)每個分區必須具有相同數量的子分區。

(2)如果在分區表的任何分區上使用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
        )
    );

(3)每個SUBPARTITION子句必須(至少)包括子分區的名稱。 否則,您需要爲子分區設置所需的選項,或者允許其採用該選項的默認設置。

(4)子分區名稱在整個表中必須唯一。 例如,以下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 s2,
            SUBPARTITION s3
        ),
        PARTITION p2 VALUES LESS THAN MAXVALUE (
            SUBPARTITION s4,
            SUBPARTITION s5
        )
);

(5)分區表的所有分區必須具有相同數量的子分區。 一旦創建了表,就無法更改子分區。

3、MySQL分區如何處理NULL

在MySQL中進行分區並不能避免 NULL作爲分區表達式的值,無論它是列值還是用戶提供的表達式的值。 請務必記住NULL不是數字。 MySQL的分區實現將NULL視爲小於任何非NULL值,就像ORDER BY一樣。

(1)LIST 分區,如果枚舉列表裏面不存在null值會插入失敗

(2)RANGE 分區會將 NULL 作爲最小分區值存儲

(3)HASH 和 KEY 分區會將其轉換成0存儲

4、MySQL 分區管理

4.1、查看分區表

mysql> SHOW CREATE TABLE `employees_range_timestamp`;
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table                     | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| employees_range_timestamp | CREATE TABLE `employees_range_timestamp` (
  `emp_no` int NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` timestamp NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY RANGE (unix_timestamp(`hire_date`))
(PARTITION p0 VALUES LESS THAN (0) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (31449600) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (347068800) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (662601600) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (1609372800) ENGINE = InnoDB) */ |
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

4.2、增加分區

mysql> ALTER TABLE employees_range_timestamp ADD PARTITION ( PARTITION p5 VALUES LESS THAN ( UNIX_TIMESTAMP('2030-12-31 00:00:00') ) );
Query OK, 0 rows affected (0.65 sec)
Records: 0  Duplicates: 0  Warnings: 0

4.3、刪除分區

mysql> ALTER TABLE employees_range_timestamp DROP PARTITION p5;
Query OK, 0 rows affected (0.33 sec)
Records: 0  Duplicates: 0  Warnings: 0

4.4、重建分區

這與刪除存儲在分區中的所有記錄,然後重新插入它們的效果相同。 這對於碎片整理很有用。

ALTER TABLE t1 REBUILD PARTITION p0, p1;

4.5、優化分區

如果從分區中刪除了大量行,或者對具有可變長度行的分區表(即具有VARCHAR,BLOB或TEXT列)進行了許多更改,則可以使用ALTER TABLE … 優化分區以回收任何未使用的空間並對分區數據文件進行碎片整理。

ALTER TABLE t1 OPTIMIZE PARTITION p0, p1;

在指定分區上使用 OPTIMIZE PARTITION 等效於在該分區上運行CHECK PARTITION,ANALYZE PARTITION和REPAIR PARTITION。

某些MySQL存儲引擎(包括InnoDB)不支持按分區優化。 在這種情況下,ALTER TABLE … OPTIMIZE PARTITION 將分析並重建整個表,併發出適當的警告。 改爲使用 ALTER TABLE … REBUILD PARTITION 和 ALTER TABLE … ANALYZE PARTITION 可以避免此問題。

4.6、分析分區

讀取並存儲分區key的分佈。

ALTER TABLE t1 ANALYZE PARTITION p3;

4.7、修復分區

可以修復損壞的分區

ALTER TABLE t1 REPAIR PARTITION p0,p1;

通常,當分區包含重複的鍵錯誤時,REPAIR PARTITION 會失敗。您可以將此選項與 ALTER IGNORE TABLE 一起使用,在這種情況下,由於存在重複鍵而無法移動的所有行將從分區中刪除。

以上每個語句還支持關鍵字ALL來代替分區名稱列表。 使用ALL會使該語句作用於表中的所有分區。

可以使用 ALTER TABLE … TRUNCATE PARTITION 截斷分區。 該語句可用於刪除一個或多個分區中的所有行,其方式與 TRUNCATE TABLE 刪除表中的所有行的方式幾乎相同。

ALTER TABLE … TRUNCATE PARTITION ALL 會截斷表中的所有分區。

5、獲取有關分區信息的方式

(1)使用 SHOW CREATE TABLE 語句查看用於創建分區表的分區語句。

(2)使用 SHOW TABLE STATUS 語句確定表是否已分區。

(3)查詢 INFORMATION_SCHEMA.PARTITIONS 表。

(4)使用語句 EXPLAIN SELECT 可以查看給定 SELECT 使用的分區。

5.1、show create table 表名

mysql> SHOW CREATE TABLE `employees_range_timestamp`;
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table                     | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| employees_range_timestamp | CREATE TABLE `employees_range_timestamp` (
  `emp_no` int NOT NULL,
  `birth_date` date NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name` varchar(16) NOT NULL,
  `gender` enum('M','F') NOT NULL,
  `hire_date` timestamp NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY RANGE (unix_timestamp(`hire_date`))
(PARTITION p0 VALUES LESS THAN (0) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (31449600) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (347068800) ENGINE = InnoDB,
 PARTITION p3 VALUES LESS THAN (662601600) ENGINE = InnoDB,
 PARTITION p4 VALUES LESS THAN (1609372800) ENGINE = InnoDB) */ |
+---------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

5.2、show table status

查看錶是不是分區表

mysql> SHOW TABLE STATUS LIKE '%employees_range_timestamp%';
+---------------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| Name                      | Engine | Version | Row_format | Rows   | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time | Check_time | Collation          | Checksum | Create_options | Comment |
+---------------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| employees_range_timestamp | InnoDB |      10 | Dynamic    | 299690 |             59 |    17907712 |               0 |            0 |   8388608 |           NULL | 2020-05-26 09:27:39 | NULL        | NULL       | utf8mb4_0900_ai_ci |     NULL | partitioned    |         |
+---------------------------+--------+---------+------------+--------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
1 row in set (0.10 sec)

5.3、查詢 information_schema.partitions 表

查看錶具有哪幾個分區、分區的方法、分區中數據的記錄數等信息

mysql> SELECT
    -> partition_name part,
    -> partition_expression expr,
    -> partition_description descr,
    -> table_rows
    -> FROM
    -> information_schema.PARTITIONS
    -> WHERE
    -> table_schema = SCHEMA ()
    -> AND table_name = 'employees_range_timestamp';
+------+-----------------------------+------------+------------+
| part | expr                        | descr      | TABLE_ROWS |
+------+-----------------------------+------------+------------+
| p0   | unix_timestamp(`hire_date`) | 0          |          0 |
| p1   | unix_timestamp(`hire_date`) | 31449600   |          0 |
| p2   | unix_timestamp(`hire_date`) | 347068800  |          0 |
| p3   | unix_timestamp(`hire_date`) | 662601600  |     190059 |
| p4   | unix_timestamp(`hire_date`) | 1609372800 |     109631 |
+------+-----------------------------+------------+------------+
5 rows in set (0.00 sec)

5.4、explain partitions select

查看執行計劃,是否命中分區及哪些分區等。

mysql> EXPLAIN SELECT * FROM employees_range_timestamp WHERE hire_date >= '1970-06-26' AND hire_date <='2020-12-01'; 
+----+-------------+---------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table                     | partitions  | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees_range_timestamp | p1,p2,p3,p4 | ALL  | NULL          | NULL | NULL    | NULL | 299690 |    11.11 | Using where |
+----+-------------+---------------------------+-------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

6、MySQL分區注意事項

(1)日期時間函數的優化:
TO_DAYS(),YEAR()和TO_SECONDS()函數經過優化,可在分區中使用。還可以使用返回整數或NULL值的日期和時間函數,例如WEEKDAY(),DAYOFYEAR()或MONTH()。

(2)分區標識: 分區始終按順序編號,創建時會自動從0開始。使用分區號插入行以標識每一行的位置。例如,如果將一個表劃分爲四個,則MySQL將使用分區號0、1、2和3來標識每個分區。

(3)命名約定: 分區名稱應遵循用於表和數據庫的相同的MySQL命名約定。值得注意的是,分區名稱不區分大小寫。如果您嘗試爲同一個表命名兩個分區,即“ myVertabeloPart”和“ myvertabelopart”,則會收到以下錯誤消息:
錯誤1488(HY000),重複的分區名稱myvertabelopart

(4)數據和索引目錄: 可以在創建過程中將分區分配給特定目錄。您可以使用CREATE TABLE語句的PARTITION子句的DATA DIRECTORY和INDEX DIRECTORY選項來執行此操作。

(5)存儲引擎: 同一表的分區必須使用相同的存儲引擎。如果對MyISAM表進行分區,則所有分區都將是MyISAM。 InnoDB也是如此。

(6)索引分區: 分區適用於表中的所有數據和索引。您不能僅對數據進行分區,而不對索引進行分區,反之亦然。您也不能僅對錶的一部分進行分區。

(7)外鍵: 分區的InnoDB表不支持外鍵。這對數據完整性的影響非常重要。您不能在分區表中添加外鍵(指向另一個表)。相反,如果表具有外鍵,則將無法對其進行分區。另外,非分區表不能具有指向分區表列的外鍵列。

(8)分區列: 分區表達式中使用的所有列必須是分區表中唯一鍵或主鍵的一部分。

7、參考文獻

1、《高性能MySQL(第3版)》

2、《MySQL參考手冊》

3、《MySQL官方樣例數據庫》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章