分区讲解1
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分区讲解1相关的知识,希望对你有一定的参考价值。
- SQL标准对于数据存储的物理方面没有提供太多的指导。SQL语言本身旨在独立于其工作的模式,表,行或列的任何数据结构或媒体。尽管如此,大多数先进的数据库管理系统已经开发了一些方法来确定用于存储特定数据块的物理位置,比如文件系统、硬件,甚至两者。在mysql中,InnoDB存储引擎长期以来一直支持表空间的概念,而MySQL服务器,甚至在引入分区之前,可以配置为使用不同的物理目录来存储不同的数据库。
- 分区将这个概念更进一步,通过使您能够根据需要在很大程度上设置的规则,将各个表的部分分布到一个文件系统中。实际上,表的不同部分作为单独的表存储在不同的位置。用于完成数据划分的用户选择规则称为分区函数,它在在 MySQL 可以是模数, 简单的匹配一组范围或值列表,一个 内部哈希函数, 或一个线性哈希函数。函数是根据用户指定的分区类型选择的,并以用户提供的表达式的值作为参数。这个表达式可以是一个列值,作用于一个或多个列值的函数,也可以是一个或多个列值的集合,这取决于所使用的分区类型
- 在RANGE, LIST和 [LINEAR] HASH 分区的情况下,分区列的值被传递给分区函数,该函数返回一个整数值, 该值表示存储特定记录的分区的编号。这个函数必须是非常量和非随机的。它可能不包含任何查询,但是可以使用在MySQL中有效的SQL表达式,只要该表达式返回NULL或整数就行了。
- MySQL支持水平分区, 即可以将表的不同行分配给不同的物理分区。MySQL 5.7不支持垂直分区,其中一个表的不同列被分配给不同的物理分区。目前还没有将垂直分区引入MySQL的计划。
- 每个分区的数据和索引可以被分配到一个特定的目录中,目录使用CREATE TABLE语句的PARTITION子句的 DATA DIRECTORY 和INDEX DIRECTORY选项指定
- 表的分区表达式中使用的所有列必须是表可能具有的每个唯一键的一部分,包括任何主键
- 分区的优点
- 分区可以在一个表中存储比单个磁盘或文件系统分区上的数据更多的数据。
- 通过删除仅包含该数据的分区(或多个分区),可以很容易地从分区表中删除其有用性的数据。相反添加新数据的过程在某些情况下可以很容易地通过添加一个或多个新的分区来存储特定的数据。
- 由于满足给定 where 子句的数据只能存储在一个或多个分区上, 从而自动将所有剩余的分区从搜索中排除, 因此可以极大地优化某些查询,因为在创建了分区表之后,由于在创建分区表后可以更改分区, 因此可以重新组织数据, 以增强在首次设置分区方案时可能不经常使用的频繁查询。。这种排除非匹配分区的能力 (因此, 它们所包含的任何行) 通常称为分区修剪。
- 此外,MySQL 5.7还支持查询的显式分区选择。例如, SELECT * FROM t PARTITION (p0,p1) WHERE c < 5 只选择在分区p0和p1中匹配WHERE条件的行。在本例中,MySQL不检查表t的任何其他分区;当您已经知道要检查哪些分区或分区时,这可以大大加快查询速度。分区选择还支持数据修改语句 DELETE, INSERT, REPLACE, UPDATE
- 类型:
- RANGE
- LIST
- COLUMNS
- RANGE COLUMNS
- LIST COLUMNS
- HASH
- KEY
- 不管您使用的分区类型是什么,都必须记住,在创建时,分区总是自动编号,从0开始。当将新行插入到分区表中时,将使用这些分区号来标识正确的分区。例如,如果您的表使用4个分区,那么这些分区的编号为0,1,2和3。对于RANGE和LIST分区类型,必要确保为每个分区号定义了一个分区。对于HASH分区,用户提供的表达式必须计算为大于0的整数值。对于KEY分区,这个问题由MySQL服务器内部使用的哈希函数自动处理。
- RANGE分区
- 按范围分区的表以这样的方式分区,即每个分区包含分区表达式值位于给定范围内的行。范围应该是连续的,但不是重叠的,并使用VALUES LESS THAN运算符定义。
- mysql> create table order_log(
- -> id int not null auto_increment,
- -> user_id int not null default 0 comment ‘用户ID‘,
- -> goods_id int not null default 0 comment ‘商品ID‘,
- -> add_time int not null default 0 comment ‘订单创建时间‘,
- -> order_year smallint not null default 0 comment ‘订单创建时间的对应年份‘,
- -> primary key(
id
,order_year
) - -> )engine=innodb charset=utf8 comment="订单表"
- -> PARTITION BY RANGE(order_year) (
- -> PARTITION p0 VALUES LESS THAN (2018),
- -> PARTITION p1 VALUES LESS THAN (2019),
- -> PARTITION p2 VALUES LESS THAN (2020),
- -> PARTITION p3 VALUES LESS THAN (2021),
- -> PARTITION p4 VALUES LESS THAN (2022),
- -> PARTITION p5 VALUES LESS THAN (2023),
- -> PARTITION p6 VALUES LESS THAN (2024),
- -> PARTITION p7 VALUES LESS THAN MAXVALUE
- -> );
- #上面的分区的意思是: 2018年以前的数据都存在p0分区中,2019年以前的都存在p1分区中,一直到p7,大于等于2024年的数据都存在p7中。
- #我们创建一个按年份分区的分区表,大家可能会有疑问,为什么我单独用了order_year字段来单独表示年份,而不是直接用add_time来进行分区,这是因为innodb的索引机制导致的,innodb的所有辅助索引都会默认加上主键索引,所以主键索引越小越好,如果用id+add_time那就是8个字节,现在就是6个字节了
- 我们先查看分区表对应的数据
- mysql> select TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,PARTITION_EXPRESSION,TABLE_ROWS from information_schema.PARTITIONS where TABLE_NAME=‘order_log‘;
- +--------------+------------+----------------+----------------------+------------+
- | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | PARTITION_EXPRESSION | TABLE_ROWS |
- +--------------+------------+----------------+----------------------+------------+
- | wordpress | order_log | p0 | order_year | 0 |
- | wordpress | order_log | p1 | order_year | 0 |
- | wordpress | order_log | p2 | order_year | 0 |
- | wordpress | order_log | p3 | order_year | 0 |
- | wordpress | order_log | p4 | order_year | 0 |
- | wordpress | order_log | p5 | order_year | 0 |
- | wordpress | order_log | p6 | order_year | 0 |
- | wordpress | order_log | p7 | order_year | 0 |
- +--------------+------------+----------------+----------------------+------------+
- 8 rows in set (0.00 sec)
- #我们可以看到TABLE_ROWS都是0行,这时候我们插入三条数据看一下
- mysql> insert into order_log select 1,1,1,unix_timestamp(‘2018-12-12 12:12:12‘),2018;
- mysql> insert into order_log select 1,1,1,unix_timestamp(‘2019-12-12 12:12:12‘),2019;
- mysql> insert into order_log select 1,1,1,unix_timestamp(‘2025-12-12 12:12:12‘),2025;
- #按照我们前面的分区算法来看的话,应该是p1有1条,p2有1条,p7有一条,那么这时候我们再看下
- mysql> select TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,PARTITION_EXPRESSION,TABLE_ROWS from information_schema.PARTITIONS where TABLE_NAME=‘order_log‘;
- +--------------+------------+----------------+----------------------+------------+
- | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | PARTITION_EXPRESSION | TABLE_ROWS |
- +--------------+------------+----------------+----------------------+------------+
- | wordpress | order_log | p0 | order_year | 0 |
- | wordpress | order_log | p1 | order_year | 1 |
- | wordpress | order_log | p2 | order_year | 1 |
- | wordpress | order_log | p3 | order_year | 0 |
- | wordpress | order_log | p4 | order_year | 0 |
- | wordpress | order_log | p5 | order_year | 0 |
- | wordpress | order_log | p6 | order_year | 0 |
- | wordpress | order_log | p7 | order_year | 1 |
- +--------------+------------+----------------+----------------------+------------+
- 8 rows in set (0.00 sec)
- #的确跟我们的预期一样,我们可以在查询中直接利用到分区,为了看到效果,我们插入一些测试数据
- mysql> DELIMITER $$
- mysql> CREATE PROCEDURE insert_test_val(in num_limit int,in rand_limit int)
- -> BEGIN
- -> DECLARE i int default 1;
- -> DECLARE user_id int default 1;
- -> DECLARE goods_id int default 1;
- -> DECLARE add_time int default 1;
- -> DECLARE order_year int default 1;
- -> WHILE i<=num_limit do
- -> set user_id = FLOOR(rand()*rand_limit);
- -> set goods_id = FLOOR(rand()*rand_limit);
- -> set add_time = unix_timestamp(now())+(86400floor(730rand()));
- -> set order_year=from_unixtime(add_time,‘%Y‘);
- -> INSERT into order_log values (null,user_id,goods_id,add_time,order_year);
- -> set i = i + 1;
- -> END WHILE;
- -> END;
- -> $$
- Query OK, 0 rows affected (0.01 sec)
- mysql> DELIMITER ;
- mysql> call insert_test_val(100000,10);
- #这时候我们再看一下数据
- mysql> select TABLE_SCHEMA,TABLE_NAME,PARTITION_NAME,PARTITION_EXPRESSION,TABLE_ROWS from information_schema.PARTITIONS where TABLE_NAME=‘order_log‘;
- +--------------+------------+----------------+----------------------+------------+
- | TABLE_SCHEMA | TABLE_NAME | PARTITION_NAME | PARTITION_EXPRESSION | TABLE_ROWS |
- +--------------+------------+----------------+----------------------+------------+
- | wordpress | order_log | p0 | order_year | 0 |
- | wordpress | order_log | p1 | order_year | 1 |
- | wordpress | order_log | p2 | order_year | 50045 |
- | wordpress | order_log | p3 | order_year | 61557 |
- | wordpress | order_log | p4 | order_year | 10705 |
- | wordpress | order_log | p5 | order_year | 0 |
- | wordpress | order_log | p6 | order_year | 0 |
- | wordpress | order_log | p7 | order_year | 1 |
- +--------------+------------+----------------+----------------------+------------+
- 8 rows in set (0.00 sec)
- #我们针对order_year=2020的去查询
- mysql> explain select * from order_log where order_year=2020 order by id asc limit 20;
- +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
- +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- | 1 | SIMPLE | order_log | p3 | index | NULL | PRIMARY | 6 | NULL | 20 | 10.00 | Using where |
- +----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
- 1 row in set, 1 warning (0.00 sec)
- #这时候我们可以看到partitions列有p3,那么说明查询了时候过滤了其他分区
- LIST分区
- MySQL中的List分区在许多方面类似于RANGE分区。在按RANGE进行分区时,必须显式地定义每个分区。这两种类型的分区之间的主要区别是,在LIST分区中,每个分区都是根据一组值列表中的一个列值的成员来定义和选择的,而不是在一系列连续的值范围内。这是通过用PARTITION BY LIST(expr)来完成的,其中expr是一个列值或基于列值的表达式,返回一个整数值,然后用一个 VALUES IN (value_list)来定义每个分区,其中value_list是一个逗号分隔的整数列表
- 例如我是某个商品在某个省的总代理,然后我要看下我每个子代理下面的数据
- mysql> create table goods_sales(
- -> id int not null auto_increment ,
- -> num int not null default 0 comment ‘数量‘,
- -> money int not null default 0 comment ‘金额‘,
- -> agency_id tinyint unsigned not null default 0 comment ‘代理ID‘,
- -> primary key (
id
,agency_id
) - -> )engine=innodb charset=utf8
- -> PARTITION BY LIST(agency_id) (
- -> PARTITION p0 VALUES IN (1,2,3,4,5),
- -> PARTITION p1 VALUES IN (6,7,8,9,10),
- -> PARTITION p2 VALUES IN (11,12,13,14,15),
- -> PARTITION p3 VALUES IN (16,17,18,19,20)
- -> );
- 与RANGE分区的情况不同,没有“catch-all”例如MAXVALUE;分区表达式的所有期望值都应该包含在PARTITION ... VALUES IN (...) 子句中,包含一个未匹配的分区列值的INSERT 语句会出现错误,如本例所示:
- mysql> insert into goods_sales select 1,20,10000,21;
- ERROR 1526 (HY000): Table has no partition for value 21
- 您可以使用IGNORE关键字来忽略此类型的错误。如果这样做, 则不会插入包含不匹配的分区列值的行, 但会插入具有匹配值的任何行, 并且不会报告任何错误:
- mysql> insert IGNORE into goods_sales (
id
,num
,money
,agency_id
) values (1,2,3,4),(2,3,4,5),(1,2,3,100); - Query OK, 2 rows affected, 1 warning (0.01 sec)
- Records: 3 Duplicates: 1 Warnings: 1
- mysql> select * from goods_sales;
- +----+-----+-------+-----------+
- | id | num | money | agency_id |
- +----+-----+-------+-----------+
- | 1 | 2 | 3 | 4 |
- | 2 | 3 | 4 | 5 |
- +----+-----+-------+-----------+
- 2 rows in set (0.00 sec)
- #在这里,我们可以看到(1,2,3,100)这条数据并没有插入成功,但也没影响到前面的两条数据
- MySQL 5.7 提供对 LIST COLUMNS分区的支持。这是 LIST分区的变体, 使您可以使用非整数类型的列来分区列, 也可以使用多列作为分区键
- COLUMNS分区
- 接下来的两部分讨论COLUMNS分区,它们是RANGE和LIST分区的变体.. COLUMNS分区允许在分区键中使用多个列。所有这些列都将被考虑在哪个分区中放置行, 以及确定在分区修剪中要检查哪些分区以匹配行。
- 此外,RANGE COLUMNS分区和 LIST COLUMNS分区支持使用非整数列来定义值范围或列表成员。允许的数据类型如下表所示:
- 所有的数字类型 : TINYINT, SMALLINT, MEDIUMINT, INT (INTEGER), and BIGINT(这与按RANGE和 LIST进行分区相同),其他数值数据类型(如DECIMAL or FLOAT)不能作为分区列。
- 日期类型:DATE 和 DATETIME.
- 字符串类型:CHAR, VARCHAR, BINARY和 VARBINARY, 不支持TEXT和BLOB列
- RANGE COLUMNS分区
- Range columns分区类似于range分区,但是允许您使用基于多个列值的范围来定义分区。此外,您可以使用除整数类型以外的类型的列来定义范围。
- RANGE COLUMNS分区与RANGE 分区有很大不同,有以下几种方式:
- RANGE COLUMNS不接受表达式,只接受列的名称
- RANGE COLUMNS接受一个或多个列的列表。
- RANGE COLUMNS分区基于元组之间的比较(列值列表),而不是标量值之间的比较。 在RANGE COLUMNS分区中放置行也是基于元组之间的比较。
- RANGE COLUMN分区列不限制为integer列.string、DATE和DATETIME列也可以用作分区列。
- 下面我们创建一个RANGE COLUMNS分区
- mysql> CREATE TABLE test1(
- -> id int not null auto_increment,
- -> key_a int not null default 0,
- -> key_b int not null default 0,
- -> PRIMARY KEY (
id
,key_a
,key_b
) - -> )engine=innodb charset=utf8
- -> PARTITION BY RANGE COLUMNS(key_a, key_b) (
- -> PARTITION p0 VALUES LESS THAN (1, 20),
- -> PARTITION p1 VALUES LESS THAN (2, 15),
- -> PARTITION p2 VALUES LESS THAN (2, 20),
- -> PARTITION p3 VALUES LESS THAN (MAXVALUE, MAXVALUE)
- -> );
- #然后我们插入几条数据看一下
- mysql> insert into test1 (
key_a
,key_b
) values (1,15),(2,10),(2,20),(2,30) - mysql> SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = ‘test1‘;
- +----------------+------------+
- | PARTITION_NAME | TABLE_ROWS |
- +----------------+------------+
- | p0 | 1 |
- | p1 | 1 |
- | p2 | 0 |
- | p3 | 2 |
- +----------------+------------+
- 4 rows in set (0.01 sec)
- #我们对比一下,看下这数据是怎么进去的,多列分区我们比较的是行而不是标量值。我们可以将插入的行值与用于在表test1中定义分区的VALUES THAN LESS THAN子句中插入的行值进行比较,如下所示:
- mysql> SELECT (1,15) < (1,20), (2,10) < (1,20), (2,20) < (1,20),(2,30)< (1,20);
- +-----------------+-----------------+-----------------+----------------+
- | (1,15) < (1,20) | (2,10) < (1,20) | (2,20) < (1,20) | (2,30)< (1,20) |
- +-----------------+-----------------+-----------------+----------------+
- | 1 | 0 | 0 | 0 |
- +-----------------+-----------------+-----------------+----------------+
- 1 row in set (0.01 sec)
- #说明p0有一条数据
- mysql> SELECT (1,15) < (2,15), (2,10) < (2,15), (2,20) < (2,15),(2,30)< (2,15);
- +-----------------+-----------------+-----------------+----------------+
- | (1,15) < (2,15) | (2,10) < (2,15) | (2,20) < (2,15) | (2,30)< (2,15) |
- +-----------------+-----------------+-----------------+----------------+
- | 1 | 1 | 0 | 0 |
- +-----------------+-----------------+-----------------+----------------+
- 1 row in set (0.00 sec)
- #这里看p1有两条数据,但(1,15)已经在p0了,所以也只有一条数据
- mysql> SELECT (1,15) < (2,20), (2,10) < (2,20), (2,20) < (2,20),(2,30)< (2,20);
- +-----------------+-----------------+-----------------+----------------+
- | (1,15) < (2,20) | (2,10) < (2,20) | (2,20) < (2,20) | (2,30)< (2,20) |
- +-----------------+-----------------+-----------------+----------------+
- | 1 | 1 | 0 | 0 |
- +-----------------+-----------------+-----------------+----------------+
- 1 row in set (0.00 sec)
- #p2一条数据都没有,因为(1,15),(2,10)分别存在p0和p1里面去了
- LIST COLUMNS分区
- Mysql 5.7支持 LIST COLUMNS分区,这是LIST分区的一种变体,它允许使用多列作为分区键, 以及用非整数类型的列作为分区列的类型,您可以使用string类型、 DATE和DATETIME列
- mysql> CREATE TABLE test3(
- -> id int not null auto_increment,
- -> key_a int not null default 0,
- -> key_b int not null default 0,
- -> PRIMARY KEY (
id
,key_a
) - -> )engine=innodb charset=utf8
- -> PARTITION BY LIST COLUMNS(key_a) (
- -> PARTITION p0 VALUES IN(1, 2,3,4,5),
- -> PARTITION p1 VALUES IN(6, 7,8,9,10),
- -> PARTITION p2 VALUES IN(11,12,13,14, 15)
- -> );
- #这个LIST COLUMNS分区,我目前感觉跟LIST分区没什么两样,最多就是可以string,DATE和DATETIME列分区,根本就看不出多列的感觉,远远没有RANGE COLUMNS分区和RANGE分区的效果差异
- HASH分区
- 通过HASH分区主要用于确保预定数量的分区之间的数据的均匀分布。 使用range或list分区,您必须明确指定要存储给定列值或列值集合的分区; 使用hash分区,MySQL会为您处理此问题,并且您只需根据要进行hash的列值和分区表的分区数量指定列值或表达式即可。
- 使用HASH分区来对表进行分区,需要在 CREATE TABLE语句上面追加一个PARTITION BY HASH (expr)子句,expr是一个表达式,返回一个整数,这也可以简单是一个列的名称, 其类型是 MySQL 的整数类型之一,另外,您很可能希望使用PARTITIONS num来执行这个操作,其中num是一个正整数,表示表要划分的分区数
- 下面我们开始一个案例
- mysql> CREATE TABLE test4(
- -> id int not null auto_increment,
- -> key_a int not null default 0,
- -> key_b int not null default 0,
- -> PRIMARY KEY (
id
,key_a
) - -> )engine=innodb charset=utf8
- -> PARTITION BY HASH(key_a)
- -> PARTITIONS 4;
- Query OK, 0 rows affected (0.28 sec)
- #它不是均匀分布吗,我们添加一万条数据看一下,直接把RANGE分区下的存储过程改下,具体改成什么样我就不发了,然后调用一下插入了一万条数据,
- mysql> SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = ‘test4‘;
- +----------------+------------+
- | PARTITION_NAME | TABLE_ROWS |
- +----------------+------------+
- | p0 | 3039 |
- | p1 | 3003 |
- | p2 | 2012 |
- | p3 | 1946 |
- +----------------+------------+
- 4 rows in set (0.02 sec)
- #感觉差异有点大,我们尝试着再插入十万条数据看一下
- mysql> SELECT PARTITION_NAME,TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = ‘test4‘;
- +----------------+------------+
- | PARTITION_NAME | TABLE_ROWS |
- +----------------+------------+
- | p0 | 28017 |
- | p1 | 27469 |
- | p2 | 27016 |
- | p3 | 26785 |
- +----------------+------------+
- 4 rows in set (0.00 sec)
- KEY分区
- 按key分区与通过hash分区类似,不同之处在于hash分区采用用户定义的表达式,用于key分区的hash函数由MySQL服务器提供。NDB集群为此目的使用MD5();对于使用其他存储引擎的表,服务器使用它自己的内部哈希函数,该函数基于与PASSWORD()相同的算法。
- CREATE TABLE ... PARTITION BY KEY 的语法规则类似于创建一个由散列分区的表。主要区别在这里:
- 使用KEY而不是HASH
- KEY仅包含零个或多个列名称的列表。作为分区键的任何列必须包含表的主键的部分或全部,如果该表有一个。如果没有指定列名称作为分区键,则使用表的主键,如果有的话。如果没有主键,但有一个唯一键,那么分区键使用唯一键,但是,如果惟一键列没有定义为 NOT NULL,那么就会创建分区失败
- 与其他分区类型不同,用于按KEY分区的列不限于整数或NULL值。例如下面的create_table语句是有效的
- 针对key分区表,你不能执行ALTER TABLE DROP PRIMARY KEY,如果执行了会报一个错误 ERROR 1466 (HY000): Field in list of fields for partition function not found in table.
- 下面我们看一下如何创建
- mysql> create table test5(
- -> id char(11) not null primary key,
- -> add_time int not null default 0
- -> )engine=innodb charset=utf8
- -> PARTITION BY LINEAR KEY ()
- -> PARTITIONS 3;
参考资料:https://dev.mysql.com/doc/refman/5.7/en/partitioning.html
以上是关于分区讲解1的主要内容,如果未能解决你的问题,请参考以下文章