Mysql在创建表时使用partition by子句定义每个分区存放的数据,将数据安装一个较粗的刻度分在不同的表中。这样做可以将相关的树存放在一起,另外如果想一次批量删除整个分区的数据也将变的方便。
使用场景:
1、 表非常大以至于无法将全部的数据放在内存中或者表中的最后部分的数据才是热数据其他都是历史数据;
2、 分区表的数据可以放在不同的物理机上,更好的利用多个硬件设备;
3、 使用分区表来比年某些特殊的瓶颈,如innoDB的单个索引的互斥访问;
优势:
1、与单个磁盘或文件系统分区相比,可以存储更多的数据。
2、对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。通常和分区有关的其他优点包括下面列出的这些。MySQL分区中的这些功能目前还没有实现,但是在我们的优先级列表中,具有高的优先级;
3、一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。
4、涉及到例如SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales
GROUP BY salesperson_id;”。通过“并行”,这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。
5、通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。
分区表的原理
数据库表分区把一个大的物理表分成若干个小的物理表,并使得这些小物理表在逻辑上可以被当成一张表来使用。
Select操作:想要充分的返回分区的优势,查询的条件一定要有分区的字段,这边优化器才可以过滤不需要的部分分区;
Insert操作和delete操作:分区层先打开所有的底层表,确认那个分区接收,在将记录写入底层表;
Update操作:分区层先打开所有的底层表,确认那个分区接收,将数据拿出更新,然后确认是那个分区,将数据写入,原数据删除操作;
虽然每一个操作很多回先打开锁住所有的底层表,但这并不代表在处理过程中是锁住全表的。如果存储引擎能够自己实现行级锁(innoDB),则会在分区层释放对应的表锁,所以建议使用类似innodb的引擎;
分区表的类型
1RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
2LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
3HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。
4KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
RANGE分区
基于属于一个给定连续区间的列值,把多行分配给分区;使用values less than操作符来定义;
Sql如下:
CREATE TABLE employees (
id INT NOT NULL,
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)
);
这边以员工表中的门店id为分区依据:p0为store_id小于6的数据,那store_id大于20会如何?
直接报错,为了避免上图的问题,可以在建表的时候加上 values less
than maxvalue,将最大的值放入另外一张表;
Sql如下:
CREATE TABLE employees (
id INT NOT NULL,
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),
partition p4values less than maxvalue;
);
这边的话,当store_id大于21的时候,数据就会加入到p4中;这样来看,range有点类似与java当中的SWTICH case;
List分区
类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
LIST分区通过使用“PARTITION
BY LIST(expr)”来实现,其中“expr”是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。
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 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)
)
假定有20个音像店,分布在4个有经销权的地区,如下表所示:
地区 商店ID 号
北区3, 5, 6, 9, 17
东区1, 2, 10, 11, 19, 20
西区4, 12, 13, 14, 18
中心区7, 8,15, 16
这样的话,会根据插入的store_id,插入到对应的分区,指得注意的是如果插入了store_id=21的,将会报错,也没有类似range的 less than maxvalue的功能;
HASH分区
基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。
要使用HASH分区来分割一个表,要在CREATE TABLE 语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL整型的一列的名字。此外,你很可能需要在后面再添加一个“PARTITIONS
num”子句,其中num是一个非负的整数,它表示表将要被分割成分区的数量。
Sql代码:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULLDEFAULT '1970-01-01',
separated DATE NOT NULLDEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY HASH(store_id)
PARTITIONS 4;
如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1。例外:对于NDB Cluster(簇)表,默认的分区数量将与簇数据节点的数量相同,这种修正可能是考虑任何MAX_ROWS设置,以便确保所有的行都能合适地插入到分区中。
LINER HASH
MySQL还支持线性哈希功能,它与常规哈希的区别在于,线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则,而常规哈希使用的是求哈希函数值的模数。线性哈希分区和常规哈希分区在语法上的唯一区别在于,在“PARTITION BY”子句中添加“LINEAR”关键字。
Sql代码:
CREATE TABLE employees (
id INT NOT NULL,
fname VARCHAR(30),
lname VARCHAR(30),
hired DATE NOT NULLDEFAULT '1970-01-01',
separated DATE NOT NULLDEFAULT '9999-12-31',
job_code INT,
store_id INT
)
PARTITION BY LINEAR HASH(YEAR(hired))
PARTITIONS 4;
1. 它的优点是在数据量大的场景,譬如TB级,增加、删除、合并和拆分分区会更快,缺点是,相对于HASH分区,它数据分布不均匀的概率更大。
KSY分区
类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
总结:
1. MySQL分区中如果存在主键或唯一键,则分区列必须包含在其中。优化器会根据分区函数来过滤一些分区,通过过滤分区让查询扫描的数据变少;如果没有的话就有访问全表;
可以使用explain partitions select *from ** 来知道sql语句是否用了分区,如下图:
2. 对于原生的RANGE分区,LIST分区,HASH分区,分区对象返回的只能是整数值。
3. RANGE COLUMNS,LIST COLUMNS,KEY,LINEAR KEY分区对象只能是列,不能是基于列的表达式。
4.分区的值为null值会是分区过滤无效,记录会放在第一个分区;建议创建第一个分区为无效分区,例如创建一个partition by range(col)(partition p0 less than 0,**),达到第一个无法过滤的数据很小;
查询优化
1、 查询的时候,where一定要带上分区列,且只能是列本身(和独立索引相似),如果是列的函数,优化器将过滤不出分区:如下图
2、分区表关联操作中的第二张表,关联条件需要是分区键,这样的话mysql会在对应的分区里面匹配行;
问题
1、实际生产环境下,分区需要实时的增加,如使用range类型的,按时间分区;这样的话必然导致分区越来越多(按官方上说mysql支持1024个分区),或者不增加分区的话(maxvalue的数据越来越多),所以个人的想法是:
每一段时间通过语句:-- 删除分区
alter table employees REMOVE PARTITIONING ;删除分区,数据不会删除
然后在去创建分区,保证热点(最近)数据在分区里面;语句为:
#新增分区
ALTER TABLE 表名 add PARTITION (PARTITION 分区名 VALUES LESS THAN (规则));
各位有什么好的想法?
最后数据大于千万级,单表优化之后还是慢?可以适当考虑分表分库操作,随之带来的是逻辑的直线上升;