分区表
1. 目的
主要目的是将数据按照一个较粗的颗粒度分在不同的表中
2. 某些实际场景能起到非常强大的作用
1. 表非常大,以至于无法全部放到内存中,或者旨在表的最后部分有热点数据,其它都是历史数据
2. 分区表的数据更容易维护,比如,想批量删除大量数据,可以使用清除整个分区的做法
还可以对一个独立分区进行优化、检查、修复操作
3. 分区表的数据是放在不同的物理内存中,可以更高效的利用设备
4. 分区可以避免某些特殊瓶颈,如InnoDB单个索引的互斥访问,ext3文件系统的Inde锁竞争
问题来了?什么是索引互斥访问
5. 还可以对独立的分区进行备份和恢复,这在非常大的数据集场景下非常高效
3. 分区的限制
1. 一个表最多有1024个分区
2. 无法使用外键
3. 分区字段中有主键或者唯一索引的列,那么所有的主键列和唯一索引列都必须包含进来
4. 分区的索引
分区表的索引只是再各个底层表上各加上一个完全相同的索引
5. 建立分区表
1. 用于做分区表的列必须是主键,或包含于主键中。
2. 如果ID自增,可以把用于分区的列也加入到主键中,形成复合主键,然后再执行
//可以根据日期来
CREATE TABLE `card1` (
`id` int(11) NOT NULL,
`card_id` varchar(18) COLLATE utf8mb4_general_ci DEFAULT NULL,
`NAME` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
`birthday` date NOT NULL,
PRIMARY KEY (`id`,`birthday`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
PARTITION BY RANGE COLUMNS(birthday)
(
PARTITION p08 VALUES LESS THAN ('2018-12-31'),
PARTITION p09 VALUES LESS THAN ('2019-12-31'),
PARTITION p10 VALUES LESS THAN ('2020-12-31')
);
//也可以根据值来
CREATE TABLE `card2` (
`id` int(11) NOT NULL,
`card_id` varchar(18) COLLATE utf8mb4_general_ci DEFAULT NULL,
`NAME` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
`birthday` date NOT NULL,
PRIMARY KEY (`id`,`birthday`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
PARTITION BY RANGE(year(birthday))
(
PARTITION p08 VALUES LESS THAN (2020),
PARTITION p09 VALUES LESS THAN (2030),
PARTITION p10 VALUES LESS THAN (2040)
);
1. mysql 还支持键值、哈希、列表分区还有不常用的子分区
2. 根据键值分区,可以减少InnoDB互斥竞争
3. 使用数学函数分区,然后将数据轮询放入不同的分区
例如:对日期做模7运行,或者更简单使用返回周几的函数
4. 对于自增ID分区,希望根据时间将最近的热点数据集中存放。
那么问题来了,这样肯定要把日期加到主键中才行,而这又和主键本身的意义相反,怎么办呢?
可以 HASH(id DIV 10000) 这将为1万数据建立一个分区,这样实现了相同的目的,而且
比时间更好的一定是,如果是时间分区,当某个时间的数据超过一定的阈值时,就必须新增分区
//一万个数据是一个分区
CREATE TABLE `cardId` (
`id` int(11) NOT NULL,
`card_id` varchar(18) COLLATE utf8mb4_general_ci DEFAULT NULL,
`NAME` varchar(10) COLLATE utf8mb4_general_ci DEFAULT NULL,
`birthday` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
PARTITION BY HASH(id DIV 10000);
6. 当有超大数据怎么办?10T?
1. 这么多数据,肯定不能每次查询全表扫描,考虑到空间和维护,不能使用索引
2. 当数据超大的时候,BTree就无法起作用了,除非是《索引覆盖查询》
3.区分出现
其实这正是分区要做的事情,分区其实是定位数据到某一块区域,可以看作索引的最初形态
在这块区域中,你可以顺序扫描、建立索引、还可以将数据缓存到内存中
分区不需要记录当前分区有哪些数据和每条数据精确位置,也就无需额外数据结构,代价很低
7. 使用分区可能出现的问题?
1. NULL值(或者非法的值)会使分区过滤无效
比如:如果按照年份分区,日期都为空,或者日期都在分区的日期范围之外,那么数据可能都会出现在一个分区中
当然我们可以建立一个无用的分区来解决,其实我们还可以用另一种方式来解决,
---》用列本身而不是基于列的函数进行分区 partition by range columns(date)
当然现在MySQL8.0的我测试了一下,如果一个数值不在任何分区,它将不能插入
而且任何带分区的列都不能为空,否则创建表都创建不了
2. 分区列和索引列不匹配
我们上面说过,建立分区的规范:
分区字段中有主键或者唯一索引的列,那么所有的主键列和唯一索引列都必须包含进来
例如:A列定义了索引、按照B列进行分区。
因为每一个分区都有独立的索引,所以扫描列B上的索引就需要扫描每一个分区内对应的索引
索引我们想跳过某些分区索引,就应该避免建立和分区列不相匹配的索引
3. 选择分区的成本很高(分区数量太多不好)
3.1 对于范围分区
因为系统每次插入数据的时候,都要区查询分区的定义,选择对应的分区
一般来说,100个就可以了,再大就不太好了
3.2 对于HASH 、键值分区 不会出现这样的问题
4. 打开并锁住所有底层表的成本很高
因为在查询访问分区表的时候,MySQL需要打开并锁住所有底层表,这是分区的一个开销
而且这个开销和分区类型无关,会影响所有的查询,对于一些非常快的查询,比如按照主键查找单行
会带来明显的开销,降低插叙速度。
建议:插入和删除的时候用批量操作或者降低分区的个数
5. 维护分区的成本可能很高
1. 某些分区的操作可能会很快,比如新增或者删除一个分区
2. 而有些操作比如,重组分区或者类是alter语句的操作,这类数据需要复制数据
和alter类似,先建立一个临时分区,把数据复制到其中,然后再删掉原来的分区
8 一个重要的原则
即使再分区创建的时候可以使用表达式,但在查询的时候却只能根据列来过滤分区
比如:
PARTITION BY RANGE(year(birthday)) ,分区按照2017 2018 2019 建立三个分区
这个分区创建的时候用的就是表达式
我们想: EXPLAIN select * from card2 where year(birthday) =2019
结果发现它查询了所有的分区
1. EXPLAIN select * from card2 where birthday = '2019-01-17'
EXPLAIN select * from card2 where birthday >= '2019-01-17'
1 查询了一个分区
2. EXPLAIN select * from card2 where birthday BETWEEN '2019-01-01' and '2019-12-31'
2 查询了一个分区