无论是在Oracle还是MySQL数据库,又或者是其他关系型数据库,分区表用起来还是很讲究的,像是双刃剑,用好了是宝剑,用不好是锤子。

这是分区相关的历史文章,

《truncate分区表的操作,会导致全局索引失效?》

《如何获得Oracle分区索引类型》

《删除分区如何不让全局索引失效?》

《删除分区提示ORA-00942》

《间隔分区报错ORA-14758》

《Oracle各版本的分区表演进》

《EDB无法删除分区子表的错误》

《操作分区表提示ORA-01502》

《时间间隔分区,及其默认表空间的几个使用场景》

《普通堆表在线转换为分区表》

《普通堆表导入为分区表需求》

《一张几亿的分区表,能改名么?》
《非分区表是否可以创建分区索引?》

《interval间隔分区STORE IN参数的作用范围》

我目前所看到的MySQL很少有用到分区表的,但实际上他是支持的,虽然支持度不如Oracle,但是针对合适的场景,分区表在某些场景还是可以起到加速访问的效果,究竟合适不合适,就得结合实际的业务场景来考量了。

社群的这篇文章,《MySQL时间类分区具体实现》就介绍了时间类型分区在MySQL中的应用实现,如有相近场景,就可参考下的。

适用分区或者说分表最多的场景依然是针对时间字段做拆分,我们详细讲讲如何更好的基于时间字段来拆分。分别按照年、月、日几个维度的实现方法以及一些细节注意事项。

1. 以年为维度做拆分

日期字段拆分粒度的选择跟业务检索请求密切相关。例如保留10年数据,每次查询基于某个具体年份做为过滤条件,那按照年拆分肯定最好。

例如下面SQL,

select * from ytt_pt1 where log_date >='2018-01-01' and log_date < '2019-01-01';

那我们来看下按照年单独拆分的实际例子:表 ytt_pt1 ,包含1000W条记录,以年为粒度建立分区表,

mysql> create table ytt_pt1(id bigint, log_date date);
Query OK, 0 rows affected (0.18 sec)mysql> insert into ytt_pt1 select id,log_date from ytt_p1 limit 10000000;
Query OK, 10000000 rows affected (3 min 49.53 sec)
Records: 10000000  Duplicates: 0  Warnings: 0mysql> ALTER TABLE ytt_pt1 PARTITION BY RANGE (year(log_date))-> (-> PARTITION p0001 VALUES LESS THAN (2012),-> PARTITION p0002 VALUES LESS THAN (2013),-> PARTITION p0003 VALUES LESS THAN (2014),-> PARTITION p0004 VALUES LESS THAN (2015),-> PARTITION p0005 VALUES LESS THAN (2016),-> PARTITION p0006 VALUES LESS THAN (2017),-> PARTITION p0007 VALUES LESS THAN (2018),-> PARTITION p0008 VALUES LESS THAN (2019),-> PARTITION p0009 VALUES LESS THAN (2020),-> PARTITION p0010 VALUES LESS THAN (2021),-> PARTITION p_max VALUES LESS THAN (maxvalue)-> );
Query OK, 10000000 rows affected (2 min 33.31 sec)
Records: 10000000  Duplicates: 0  Warnings: 0

看下按年为粒度的查询效果:以下SQL直接走分区p0008,查询时间0.91秒, 这个时间不算短,后期可以增加过滤条件来减少查询时间,

mysql> select count(*) from ytt_pt1 where log_date >='2018-01-01' and log_date < '2019-01-01';
+----------+
| count(*) |
+----------+
|  1000204 |
+----------+
1 row in set (0.91 sec)mysql> explain  select count(*) from ytt_pt1 where log_date >='2018-01-01' and log_date < '2019-01-01'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: ytt_pt1partitions: p0008type: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 998002filtered: 11.11Extra: Using where
1 row in set, 1 warning (0.00 sec)

需要注意,查询只能基于字段来直接过滤,如果基于字段表达式来过滤,MySQL不确定走哪个分区,会扫描所有分区,处理方法和单表查询一样,例如语句,

select count(*) from ytt_pt1 where year(log_date) = '2018' ;

看下执行情况,MySQL扫描所有分区,查询执行时间9秒多,

mysql> select count(*) from ytt_pt1 where year(log_date) = '2018' ;
+----------+
| count(*) |
+----------+
|  1000204 |
+----------+
1 row in set (9.19 sec)mysql> explain select count(*) from ytt_pt1 where year(log_date) = '2018' \G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: ytt_pt1partitions: p0001,p0002,p0003,p0004,p0005,p0006,p0007,p0008,p0009,p0010,p_maxtype: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 9982648filtered: 100.00Extra: Using where
1 row in set, 1 warning (0.00 sec)

如果非坚持这种写法,可以给优化器一个提示,具体到指定分区去检索数据,或者是基于字段表达式建一个虚拟列,

mysql> select count(*) from ytt_pt1 partition(p0008) where year(log_date) = '2018' ;
+----------+
| count(*) |
+----------+
|  1000204 |
+----------+
1 row in set (0.84 sec)

如果查询按照月作为维度过滤比较频繁,那肯定是按照月来拆最好,例如需要检索2020年当月的某些记录来做后续数据处理,大致SQL如下,

select * from ytt_pt1_按月拆分表 where log_date in ('2020-01-01','2020-01-02',...)
2. 以月为维度做拆分
按照月来拆分,有以下两种写法:

第一种:直接按照月来拆12个分区,下面表ytt_pt1_month1分区类型为LIST,基于函数month直接计算,

mysql> show create table ytt_pt1_month1\G
*************************** 1. row ***************************Table: ytt_pt1_month1
Create Table: CREATE TABLE `ytt_pt1_month1` (`id` bigint DEFAULT NULL,`log_date` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50100 PARTITION BY LIST (month(`log_date`))
(PARTITION p0001 VALUES IN (1) ENGINE = InnoDB,PARTITION p0002 VALUES IN (2) ENGINE = InnoDB,PARTITION p0003 VALUES IN (3) ENGINE = InnoDB,PARTITION p0004 VALUES IN (4) ENGINE = InnoDB,PARTITION p0005 VALUES IN (5) ENGINE = InnoDB,PARTITION p0006 VALUES IN (6) ENGINE = InnoDB,PARTITION p0007 VALUES IN (7) ENGINE = InnoDB,PARTITION p0008 VALUES IN (8) ENGINE = InnoDB,PARTITION p0009 VALUES IN (9) ENGINE = InnoDB,PARTITION p0010 VALUES IN (10) ENGINE = InnoDB,PARTITION p0011 VALUES IN (11) ENGINE = InnoDB,PARTITION p0012 VALUES IN (12) ENGINE = InnoDB) */
1 row in set (0.00 sec)

例如要查询2020年前半个月的记录,查询限定在分区p0001里,但是时间不太理想,得0.66秒,

mysql> select count(*) from ytt_pt1_month1 where log_date in ('2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05','2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10','2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15');
+----------+
| count(*) |
+----------+
|    41540 |
+----------+
1 row in set (0.66 sec)mysql> explain select count(*) from ytt_pt1_month1 where log_date in ('2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05','2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10','2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15')\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: ytt_pt1_month1partitions: p0001type: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 848224filtered: 50.00Extra: Using where
1 row in set, 1 warning (0.00 sec)

第二种:对于每年的数据,单独划分12个分区,也就是按照年月联合维度来分区,一共有144个分区,每个分区对应具体某一年某一月数据。

添加这个分区稍微麻烦些,克隆表ytt_pt1_month1为ytt_pt1_month2,这里写个存储过程来添加分区信息,

DELIMITER $$USE `ytt`$$DROP PROCEDURE IF EXISTS `sp_add_partition_ytt_pt1_month2`$$CREATE DEFINER=`root`@`%` PROCEDURE `sp_add_partition_ytt_pt1_month2`()
BEGINDECLARE i,j INT UNSIGNED DEFAULT 1;DECLARE v_tmp_date DATE;SET @stmt = '';SET @stmt_begin = 'ALTER TABLE ytt_pt1_month2 PARTITION BY RANGE COLUMNS (log_date)(';SET i = 2010;        WHILE i <= 2020 DOSET j = 1;WHILE j <= 12 DOSET v_tmp_date = CONCAT(i,'-01-01');SET @stmt = CONCAT(@stmt,'PARTITION p',i,'_',LPAD(j,2,"0"),' VALUES LESS THAN (''',DATE_ADD(v_tmp_date,INTERVAL j MONTH),'''),');SET j = j + 1;END WHILE;SET i = i + 1;END WHILE;   SET @stmt_end = 'PARTITION p_max VALUES LESS THAN (maxvalue))';SET @stmt = CONCAT(@stmt_begin,@stmt,@stmt_end);PREPARE s1 FROM @stmt;EXECUTE s1;DROP PREPARE s1;SET @stmt = NULL;SET @stmt_begin = NULL;SET @stmt_end = NULL;   END$$DELIMITER ;mysql> call sp_add_partition_ytt_pt1_month2;
Query OK, 0 rows affected (2 min 20.47 sec)

结果类似这样,

PARTITION p2010_01 VALUES LESS THAN ('2010-02-01') ENGINE = InnoDB,
...
PARTITION p2010_12 VALUES LESS THAN ('2011-01-01') ENGINE = InnoDB,
PARTITION p2011_01 VALUES LESS THAN ('2011-02-01') ENGINE = InnoDB,
...
PARTITION p2011_12 VALUES LESS THAN ('2012-01-01') ENGINE = InnoDB,...
PARTITION p2020_12 VALUES LESS THAN ('2021-01-01') ENGINE = InnoDB,
PARTITION p_max VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB)

加好分区后,来观察下刚才那个获取2020年前半个月记录的查询,

mysql> select count(*) from ytt_pt1_month2 where log_date in ('2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05','2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10','2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15');
+----------+
| count(*) |
+----------+
|    41540 |
+----------+
1 row in set (0.06 sec)mysql> explain   select count(*) from ytt_pt1_month2 where log_date in ('2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05','2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10','2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15')\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: ytt_pt1_month2partitions: p2020_01type: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 85498filtered: 50.00Extra: Using where
1 row in set, 1 warning (0.00 sec)

查询时间为0.06秒,比第一种拆分方法要快10倍。

是不是说按照年月联合来分区一定比按照单月来分区更加优化?也不一定,如果查询语句过滤条件包含分区的固定月,比如每年的12月份都要查,这时候直接按月来分区肯定要优于按照年月来分区。

3. 以天为维度做拆分

按照天为维度过滤比较频繁的查询,以天来拆分最好,例如只想查询2020年1月1日当天的数据,大致SQL如下,

select * from ytt_pt1 where log_date = '2020-01-01'

类似按照年月联合维度,写个脚本或者存储过程来添加分区,这里唯一要注意的一点就是MySQL分区表数量有限制,最大为8192个,所以如果按照天来分区,存放10年数据,分区数量为3650个,也在限制之内。

修改下之前的存储过程,代码如下,

DELIMITER $$USE `ytt`$$DROP PROCEDURE IF EXISTS `sp_add_partition_ytt_pt1_day`$$CREATE DEFINER=`root`@`%` PROCEDURE `sp_add_partition_ytt_pt1_day`(
IN f_year_start YEAR,
IN f_year_end YEAR
)
BEGINDECLARE v_days INT UNSIGNED DEFAULT 365;DECLARE v_year DATE DEFAULT '2010-01-01';DECLARE v_partition_name VARCHAR(64) DEFAULT '';DECLARE v_log_date DATE;DECLARE i,j INT UNSIGNED DEFAULT 1;SET @stmt = '';SET @stmt_begin = 'ALTER TABLE ytt_pt1_day PARTITION BY RANGE COLUMNS (log_date)(';SET i = f_year_start;WHILE i <= f_year_end DO SET v_year = CONCAT(i,'-01-01');SET v_days = DATEDIFF(DATE_ADD(v_year,INTERVAL 1 YEAR),v_year);           SET j = 1;WHILE j <= v_days DOSET v_log_date = DATE_ADD(v_year,INTERVAL j DAY);SET v_partition_name = CONCAT('p',i,'_',LPAD(j,3,'0'));SET @stmt = CONCAT(@stmt,'PARTITION ',v_partition_name,' VALUES LESS THAN (''',v_log_date,'''),');SET j = j + 1;        END WHILE;SET i = i + 1;    END WHILE;SET @stmt_end = 'PARTITION p_max VALUES LESS THAN (maxvalue))';SET @stmt = CONCAT(@stmt_begin,@stmt,@stmt_end);PREPARE s1 FROM @stmt;EXECUTE s1;DROP PREPARE s1;SELECT NULL,NULL,NULL INTO @stmt,@stmt_begin,@stmt_end;
END$$DELIMITER ;mysql> CALL sp_add_partition_ytt_pt1_day('2010','2020');
Query OK, 1 row affected (14 min 13.69 sec)

接下来,以天来查询时间一定是最短的,只有0.01秒,

mysql> select count(*) from ytt_pt1_day where log_date = '2020-01-01';
+----------+
| count(*) |
+----------+
|     2675 |
+----------+
1 row in set (0.01 sec)

此时这样的查询要是基于年或者月性能肯定不是最优,

mysql> select count(*) from ytt_pt1 where log_date = '2020-01-01';
+----------+
| count(*) |
+----------+
|     2675 |
+----------+
1 row in set (0.68 sec)mysql> select count(*) from ytt_pt1_month1 where log_date = '2020-01-01';
+----------+
| count(*) |
+----------+
|     2675 |
+----------+
1 row in set (0.87 sec)mysql> select count(*) from ytt_pt1_month2 where log_date = '2020-01-01';
+----------+
| count(*) |
+----------+
|     2675 |
+----------+
1 row in set (0.09 sec)

可以看到,此类查询基于其他方法分区时间明显比按天来的长。

因此分区表的设置还得结合实际的业务场景,设置的合理,用起来才顺畅,否则就适得其反,从这个角度,技术这个活儿,有点意思,虽然不易,但是各种关卡,打怪升级,苦中作乐,“彼方尚有荣光在”。

近期更新的文章:

《一道热热身的算术题》

《SQL Server生成随机日期模拟测试数据的需求》

《《SQL Cookbook》 - 第三章 多表查询》

《超多绑定变量导致异常的一个案例》

《学习工行MySQL研发管控和治理实践的过程》

文章分类和索引:

《公众号900篇文章分类和索引》

MySQL时间分区的实现相关推荐

  1. MySQL时间类分区写SQL的一些注意事项

    杨老师上篇文章<MySQL时间分区的实现>介绍了时间类分区的实现方法,这篇是上篇的一个延伸,介绍基于此类分区的相关SQL编写注意事项. 对于分区表的检索无非有两种,一种是带分区键,另一种则 ...

  2. 【mysql分区分表】mysql 按时间分区 【partition】

    大家好,我是烤鸭:     今天分享一下有关 mysql 分区. 需求: 按时间分区. 对千万数据左右的表,进行分区,数据的增加量大概千万/年. 代码实现: 模拟之前已经存在的表: DROP TABL ...

  3. mysql 表分区、按时间函数分区、删除分区、自动添加表分区

    mysql 表分区的几种方式: RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区. LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进 ...

  4. mysql 不同分区 同时insert_一文看懂mysql数据库分区表概念、类型、适用场景、优缺点及原理...

    概述 最近对项目上部分表按时间做了分区,所以顺便整理下mysql分区表的一些内容,仅供参考. 一.分区表概念 分区是将一个表的数据按照某种方式,比如按照时间上的月份,分成多个较小的,更容易管理的部分, ...

  5. MySQL 表分区详解MyiSam引擎和InnoDb 区别(实测)

    MySQL 表分区详解MyiSam引擎和InnoDb 区别(实测) 一.什么是表分区 通俗地讲表分区是将一大表,根据条件分割成若干个小表.mysql5.1开始支持数据表分区了. 如:某用户表的记录超过 ...

  6. mysql 分区表优化_Sql优化之Mysql表分区

    一  分区表适用于以下场景 1:表非常大以至于无法全部放在内存中,或者只在标的最后部分有热点数据,其他均是历史数据 2:分区表的数据更容易维护.例如想批量删除大量数据可以使用清除整个分区的方式.另外还 ...

  7. mysql创建分区是否存在_mysql中如何判断是否支持分区

    mysql可以通过下面语句判断是否支持分区: SHOW VARIABLES LIKE '%partition%'; 如果输出: have_partitioning   YES 表示支持分区. 或者通过 ...

  8. mf怎么使mysql信息分区_细聊MySQL的分区功能

    此篇主要介绍下MySQL的分区功能.我们分别从分区的概念.分区对于MySQL应用的优点.分区的类别及设置来和大家一起探讨下MySQL的分区. 什么是分区? MySQL在未启用分区功能时,数据库的单个表 ...

  9. mysql gui 分区_一文彻底搞懂MySQL分区

    一.InnoDB逻辑存储结构 首先要先介绍一下InnoDB逻辑存储结构和区的概念,它的所有数据都被逻辑地存放在表空间,表空间又由段,区,页组成. 段 段就是上图的segment区域,常见的段有数据段. ...

最新文章

  1. 1476. Lunar Code
  2. ABP官方文档翻译 9.2 Entity Framework Core
  3. Tomcat unable to start within 45 seconds.
  4. Yabbly:让经验缔结因果
  5. 1155 Heap Paths (30 分)【难度: 一般 / 知识点: 堆 堆的遍历】
  6. myqsl cluster error code 2310
  7. MySQL concat()函数
  8. 饶毅教授纵论“科学家的九个层次”;看到最后一句,终于绷不住了
  9. Bash中命令连接符的用法——一次执行多个命令-转
  10. [JNI]开发之旅(6)JNI函数中访问java类中对象的属性
  11. 网络安全面试题及答案
  12. vsCode编写Latex文本( texlive +vsCode )
  13. python特殊字符替换
  14. 多媒体技术计算机系统由组成,多媒体技术概述及多媒体计算机系统的组成
  15. 机器学习笔记 - Moore-Penrose 伪逆
  16. PlatoFarm进展不断,接连上线正式版以及推出超级原始人NFT
  17. 软件测试方法—动态测试
  18. 多源数据 单源数据是什么意思
  19. tableau制作日历图学习
  20. 无言以队——需求规格说明书

热门文章

  1. 1200亿基层医疗市场的利好政策大盘点
  2. SSM常见的CRUD操作
  3. 如何用php实现crud,php实现简单的CRUD操作
  4. SFDC:Plural Label无法设置
  5. (摘自网络)范跑跑对话录
  6. 【工业控制】Wincc是什么?
  7. python 面向对象高级
  8. html 隐藏元素点击事件,css隐藏元素的几种方法中可以触发点击事件的是?
  9. 这几年为什么Python在中国突然就火了起来了?
  10. MongoDB(4):Docker下使用命令操作Mongo数据库