哈希分区
哈希分区主要用于确保数据在预定数量的分区中均匀分布。使用范围分区或列表分区时,用户必须明确指定给定的列值或一组列值存储在哪个分 区中;使用哈希分区时,MySQL会帮你处理这个问题,你只需要根据要哈希的列值和要划分的分区数指定一个列值或表达式。
要使用哈希分区对表进行分区,需要在CREATE table语句中添加一个partition BY HASH (expr)子句,其中expr是一个返回整数的表达式。这 可以是MySQL整数类型的列的名称。此外,你很可能想在接下来使用分区num,其中num是一个正整数,表示表被划分的分区数。
为简单起见,下面示例中的表不使用任何键。你应该注意,如果一张表有任何唯一键,那么在这个表的分区表达式中使用的每一列都必须是每个 唯一键的一部分,包括主键。
下面的语句创建了一个在store_id列上使用哈希的表,它被分为4个分区:
mysql> 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;
Query OK, 0 rows affected (0.01 sec)
如果不包含PARTITIONS子句,则分区的数量默认为1。
使用PARTITIONS关键字而后面没有数字会导致语法错误。
你也可以使用在expr中返回整数的SQL表达式。例如,你可能想根据雇员受雇的年份进行分区。可以这样做:
mysql> 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;
Query OK, 0 rows affected (0.02 sec)
expr必须返回一个非常量、非随机的整数值(换句话说,它应该是可变的,但是确定的),而且不能包含任何禁止使用的结构。你还应该记住, 每当插入或更新(或删除)一行时,这个表达式都会被计算;这意味着非常复杂的表达式可能会导致性能问题,特别是在执行一次影响很多行的 操作(如批量插入)时。
最高效的散列函数是对表中的单个列进行操作,其值随列值的增加或减少而增加或减少,因为这允许在分区范围上进行“修剪”。也就是说,该 表达式与所基于的列的值变化越密切,MySQL使用该表达式进行哈希分区的效率就越高。
例如,如果 date_col 是 DATE 类型的列,那么表达式 TO_DAYS(date_col) 就被认为与 date_col 的值成正比变化,因为 date_col 的值每发 生一次变化,该表达式的值都会以一致的方式变化。表达式 YEAR(date_col) 相对于 date_col 的变化不像 TO_DAYS(date_col) 那样直接,因 为 date_col 的每个可能变化都不会在 YEAR(date_col) 中产生同等的变化。即便如此,YEAR(date_col) 仍是一个不错的哈希函数候选,因为 它与 date_col 的一部分成正比变化,并且 date_col 的任何变化都不会在 YEAR(date_col) 中产生不成比例的变化。
相比之下,假设你有一个名为int_col的列,其类型为INT。现在考虑表达式POW(5-int_col,3) + 6。对于哈希函数来说,这是一个糟糕的选择, 因为改变int_col的值并不一定会导致表达式的值成比例地改变。将int_col的值改变一个给定的值,可能会导致表达式的值发生很大不同的变化 。例如,将int_col从5改为6,表达式的值会改变-1,而将int_col的值从6改为7,表达式的值会改变-7。
换句话说,列值的图形与表达式的值越接近,就像方程y=cx所示的直线,其中c是某个非零常数,表达式越适合散列。这是因为表达式越非线性 ,其产生的分区之间的数据分布就越不均匀。
理论上,对于涉及多个列值的表达式,也可以进行“修剪”,但确定哪一个表达式适合是非常困难和耗时的。因此,不推荐使用涉及多列的散列 表达式。
当使用PARTITION BY HASH时,MySQL根据表达式结果的模值来确定使用num个分区中的哪个分区。换句话说,对于给定的表达式expr,存储记录 的分区是分区号N,其中N = MOD(expr, num)。假设表t1定义如下,它有4个分区:
mysql> CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
-> PARTITION BY HASH( YEAR(col3) )
-> PARTITIONS 4;
Query OK, 0 rows affected (0.03 sec)
如果你在t1中插入一条col3值为‘2005-09-15’的记录,那么存储它的分区如下所示:
MOD(YEAR('2005-09-01'),4)
= MOD(2005,4)
= 1
MySQL 5.7还支持哈希分区的一种变体,称为线性哈希,它采用一种更复杂的算法来确定插入到分区表中的新行的位置。
每次插入或更新记录时,都会对用户提供的表达式进行计算。根据具体情况,还可以在删除记录时对其进行评估。
线性哈希分区
MySQL还支持线性哈希,它与常规哈希的不同之处在于,线性哈希利用线性2次幂算法,而常规哈希使用哈希函数值的模数。
从语法上讲,线性哈希分区和常规哈希之间的唯一区别是添加了在PARTITION BY子句中添加LINEAR关键字,如下所示:
mysql> 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;
Query OK, 0 rows affected (0.03 sec)
给定表达式expr,使用线性哈希时存储记录的分区为num个分区中的第N个分区,其中N根据以下算法推导:
1.求下一个大于num的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)
假设表t1使用线性哈希分区,有6个分区,使用下面的语句创建:
mysql> CREATE TABLE t1 (col1 INT, col2 CHAR(5), col3 DATE)
-> PARTITION BY LINEAR HASH( YEAR(col3) )
-> PARTITIONS 6;
Query OK, 0 rows affected (0.03 sec)
现在假设你想在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 is FALSE: record stored in partition #3)
存储第二条记录的分区编号计算如下所示:
V = 8
N = YEAR('1998-10-19') & (8 - 1)
= 1998 & 7
= 6
(6 >= 6 is TRUE: additional step required)
N = 6 & ((8 / 2) - 1)
= 6 & 3
= 2
(2 >= 6 is FALSE: record stored in partition #2)
通过线性哈希进行分区的优点是,分区的添加、删除、合并和拆分都快得多,这在处理包含大量(tb级)数据的表时非常有用。缺点是,与使用 常规哈希分区获得的分布相比,数据不太可能在分区之间均匀分布。