杨老师上篇文章《MySQL时间分区的实现》介绍了时间类分区的实现方法,这篇是上篇的一个延伸,介绍基于此类分区的相关SQL编写注意事项。

对于分区表的检索无非有两种,一种是带分区键,另一种则不带分区键。一般来讲检索条件带分区键则执行速度快,不带分区键则执行速度变慢。这种结论适应于大多数场景,但不能以偏概全,要针对不同的分区表定义来写最合适的SQL语句。用分区表的目的是为了减少SQL语句检索时的记录数,如果没有达到预期效果,则分区表只能带来副作用。

接下来我列举几个经典的 SQL 语句:

细心的读者在阅读完上篇可能心中就有一些疑问,基于表ytt_p1的SQL语句如下:

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

同样是分区表 ytt_pt1_month1 ,基于这张表的SQL语句如下:

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');

两张表的检索需求类似,为何写法差异不小?后者为何要写成列表形式而不继续写成简单的范围检索形式?带着这点疑问,我们继续。

MySQL针对分区表有一项优化技术叫partition pruning ,翻译过来就是分区裁剪。其大致含义是MySQL会根据SQL语句的过滤条件对应的分区函数进行计算,并把计算结果穿透到底层分区表从而减小扫描记录数的一种优化策略。对于时间类型(DATE、TIMESTAMP、TIME、DATETIME),MySQL仅支持部分函数的分区裁剪:to_days、to_seconds、year、unix_timestamp。那么我们再来看之前的疑问:表ytt_pt1_month1分区函数为month,MySQL分区表虽然支持month函数,但是分区裁剪技术却不包含这个函数。接下来,分两部分来介绍本篇内容。

(1) 来体验下MySQL的分区裁剪技术,新建一张表pt_pruning:分区函数为to_days。

create table pt_pruning (
id int,
r1 int,
r2 int,
log_date date)
partition by range(to_days(log_date))
(PARTITION p_01 VALUES LESS THAN (to_days('2020-02-01')) ENGINE = InnoDB,PARTITION p_02 VALUES LESS THAN (to_days('2020-03-01')) ENGINE = InnoDB,PARTITION p_03 VALUES LESS THAN (to_days('2020-04-01')) ENGINE = InnoDB,PARTITION p_04 VALUES LESS THAN (to_days('2020-05-01')) ENGINE = InnoDB,PARTITION p_05 VALUES LESS THAN (to_days('2020-06-01')) ENGINE = InnoDB,PARTITION p_06 VALUES LESS THAN (to_days('2020-07-01')) ENGINE = InnoDB,PARTITION p_07 VALUES LESS THAN (to_days('2020-08-01')) ENGINE = InnoDB,PARTITION p_08 VALUES LESS THAN (to_days('2020-09-01')) ENGINE = InnoDB,PARTITION p_09 VALUES LESS THAN (to_days('2020-10-01')) ENGINE = InnoDB,PARTITION p_10 VALUES LESS THAN (to_days('2020-11-01')) ENGINE = InnoDB,PARTITION p_11 VALUES LESS THAN (to_days('2020-12-01')) ENGINE = InnoDB,PARTITION p_12 VALUES LESS THAN (to_days('2021-01-01')) ENGINE = InnoDB,PARTITION p_max VALUES LESS THAN MAXVALUE ENGINE = InnoDB
)
此表包含2020年一整年的数据,大概100W条,此处省略造数据过程。
<mysql>select min(log_date),max(log_date),count(*) from pt_pruning;
+---------------+---------------+----------+
| min(log_date) | max(log_date) | count(*) |
+---------------+---------------+----------+
| 2020-01-02    | 2020-12-31    |  1000000 |
+---------------+---------------+----------+
1 row in set (0.72 sec)
分别执行下面几条SQL:

SQL 1:求日期包含'2020-01-02'的记录条数。

SQL 1:select count(*) from pt_pruning where log_date <= '2020-01-02';

SQL 2和SQL 3:求2020年1月份的记录条数。

SQL 2:select count(*) from pt_pruning where log_date < '2020-02-01';SQL 3:  select count(*) from pt_pruning where log_date between '2020-01-01' and '2020-01-31';

SQL 1和 SQL 2执行时间为0.04秒,SQL 3执行时间为0.06秒。在没有使用索引的条件下效果还是比较理想的。

<mysql> select count(*) from pt_pruning where log_date <= '2020-01-02';
+----------+
| count(*) |
+----------+
|     2621 |
+----------+
1 row in set (0.04 sec)<mysql>select count(*) from pt_pruning where log_date < '2020-02-01';
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (0.04 sec)<mysql>select count(*) from pt_pruning where log_date between '2020-01-01' and '2020-01-31';
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (0.06 sec)
所以切记使用MySQL分区裁剪技术规定的分区函数来建立分区表,这样写SQL就会相对随意些。如果由于历史原因,分区表没有使用以上规定的分区函数,可以有以下两项可能的优化策略:

(1) 手工改 SQL 语句让其达到最优。

(2) 加 HINT 来提示 MySQL 使用具体的分区。

(2) 如果分区表使用的分区函数未满足MySQL分区裁剪技术的规则,该如何优化此类SQL语句?

为避免和上篇内容混淆,建张新表pt_month,复制表ytt_pt1_month1的表定义。表pt_month和表pt_pruning一样,存放了2020年一整年的记录,总条数也为100W。

<mysql>select min(log_date),max(log_date),count(*) from pt_month;
+---------------+---------------+----------+
| min(log_date) | max(log_date) | count(*) |
+---------------+---------------+----------+
| 2020-01-02    | 2020-12-31    |  1000000 |
+---------------+---------------+----------+
1 row in set (0.72 sec)

再次执行之前的三条SQL,并把表名替换为pt_month:

SQL 1执行时间为1.26秒,相比之前慢了不少。查看执行计划,发现未使用MySQL分区裁剪技术,扫描了不必要的表分区。(这里是全部表分区)

<mysql>select count(*) from pt_month where log_date <= '2020-01-02';
+----------+
| count(*) |
+----------+
|     2621 |
+----------+
1 row in set (1.26 sec)<mysql>explain -> select count(*) from pt_month where log_date <= '2020-01-02'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: pt_monthpartitions: p_01,p_02,p_03,p_04,p_05,p_06,p_07,p_08,p_09,p_10,p_11,p_max
...rows: 992805filtered: 33.33Extra: Using where
1 row in set, 1 warning (0.00 sec)

接下来对SQL 1进行一项简单的优化:既然是求日期为’2020-01-02‘那天的记录,那就不要使用<=来过滤,直接用=过滤:执行时间0.03秒。查看执行计划,改后的SQL直接定位到表分区p_01,达到了分区裁剪的效果。

<mysql>select count(*) from pt_month where log_date = '2020-01-02';
+----------+
| count(*) |
+----------+
|     2621 |
+----------+
1 row in set (0.03 sec)<mysql>explain -> select count(*) from pt_month where log_date = '2020-01-02'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: pt_monthpartitions: p_01type: ALL
...rows: 82522filtered: 10.00Extra: Using where
1 row in set, 1 warning (0.00 sec)

继续执行SQL 2和SQL 3:执行时间都是1秒到2秒之间,效率很差,也未使用MySQL分区裁剪技术。

<mysql>select count(*) from pt_month where log_date < '2020-02-01';
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (1.35 sec)<mysql>select count(*) from pt_month where log_date between '2020-01-01' and '2020-01-31';
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (1.93 sec)

来继续优化SQL 2和SQL 3,由于两个需求一致,可以把范围检索改为指定列表检索:执行时间仅为0.04秒。

<mysql>select count(*) from pt_month 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','2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20','2020-01-21','2020-01-22','2020-01-23','2020-01-24','2020-01-25','2020-01-26','2020-01-27','2020-01-28','2020-01-29','2020-01-30','2020-01-31');
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (0.04 sec)

把范围查询改为IN列表后,效率得到很大提升,查询计划显示MySQL优化器只在分区p_01上检索记录。

...partitions: p_01
...

除了改造SQL语句,还可以给语句加HINT的方式来让MySQL使用分区裁剪技术:比如给SQL 2加上HINT后,执行时间为0.04秒,和之前改造后的语句执行效率相当。

<mysql>select count(*) from pt_month partition (p_01) where log_date < '2020-02-01';
+----------+
| count(*) |
+----------+
|    82410 |
+----------+
1 row in set (0.04 sec)

因此,如果由于历史原因分区表未使用MySQL分区裁剪技术,可以按照以下规则来手动对分区表进行裁剪优化。(查询语句必须包含分区键并且是等值查询或者是IN(OR)列表查询)具体表现形式为:

(1) select * from tbname where partition_key = value;

(2) select * from tbname

where partition_key in (value1,value2,...,valueN);

(3) 以上两种规则对于多表 JOIN 依然适用。

Oracle同样有分区剪裁的功能,但是不存在MySQL这种对某些函数不适用的场景,这可能就和实现的方式相关了。不同数据库之间,一些功能还是存在相同点和不同点,使用的时候,还是要知道。

近期更新的文章:

《最近碰到的问题》

《关于数据治理的读书笔记 - 什么是数据文化?》

《数字时代的冲击》

《关于数据治理的读书笔记 - 什么是组织机制?》

《Supercell带给我们的启示》

文章分类和索引:

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

MySQL时间类分区写SQL的一些注意事项相关推荐

  1. java mysql查询字段换行,java类中写sql语句,查询条件包含换行

    java类中写sql语句,查询条件包含换行 detachedCriteria.add(Restrictions.or( Restrictions.like("chengBanDanWeiId ...

  2. java sql范围查询语句,java类中写sql语句,查询条件包含换行

    java类中写sql语句,查询条件包含换行 detachedCriteria.add(Restrictions.or( Restrictions.like("chengBanDanWeiId ...

  3. java中sql语句怎么把开始和结束时间作为参数写sql查询_java程序员跳槽的一道坎,大公司面试官都会问的Mybatis...

    一.什么是Mybatis? 1. Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动.创建连接.创建statement ...

  4. java中sql语句怎么把开始和结束时间作为参数写sql查询_聊一聊MyBatis 和 SQL 注入间的恩恩怨怨

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 引言 MyBatis 是一种持久层框架,介于 JDBC 和 Hi ...

  5. mysql 开发基础系列22 SQL Model(带迁移事项)

    一.概述 与其它数据库不同,mysql 可以运行不同的sql model 下, sql model 定义了mysql应用支持的sql语法,数据校验等,这样更容易在不同的环境中使用mysql. sql ...

  6. mysql 时间盲注语句,sql注入学习记录(5)-基于时间延迟的SQL盲注

    上次说到了sql注入中的基于报错盲注的基本的方法. 今天说一说报错盲注 基于时间延时的SQL盲注 使用时间延时注入的场景: 1.不能使用union select 联合查询方式注入 2.有些网站没有回显 ...

  7. java中sql语句怎么把开始和结束时间作为参数写sql查询_JDBC数据库连接怎么操作?...

    之前一直听说过JDBC,但从来不知道它是何物的小伙伴们看过来啦! 一.概述 JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java A ...

  8. mysql外键怎么写sql文_mysql 创建外键sql语句

    createtableUserInfo(User_nIDintnotnull,--identityUser_sNovarchar(50)null,--编号User_sNamevarchar(50)nu ...

  9. MySQL时间分区的实现

    无论是在Oracle还是MySQL数据库,又或者是其他关系型数据库,分区表用起来还是很讲究的,像是双刃剑,用好了是宝剑,用不好是锤子. 这是分区相关的历史文章, <truncate分区表的操作, ...

最新文章

  1. find命令中的-print -exec -ok参数区别
  2. java int 127_Integer类型中奇怪的127和128
  3. 关于jq+easy-ui 中上传文件所遇到的问题
  4. System类的常用方法
  5. Windows消息ID号查看
  6. 单一登录云:SAML和OpenId
  7. python comprehensions_Python_基础
  8. AWVS14.1.2下载安装教程(2021.3.6版本)
  9. Guava: Joiner
  10. 数据库原理(2)——数据模型与概念模型
  11. RTI路由服务入门手册
  12. Linux环境Java给图片加水印中文乱码处理
  13. 通过动态NAT实现内网访问外网,通过静态NAT实现外网访问内网的WEB服务器
  14. winscp 进入mysql命令_Winscp使用密钥登录
  15. Word VBA自动排版(5)- 专利具体实施方式批量增加附图标记
  16. java图片加水印上传工具类_基于Spring Boot实现图片上传/加水印一把梭操作
  17. 传说对决服务器无响应怎么办,传说对决一直进不去怎么办
  18. 笔记:Bootstrap导航与router-link 不和谐
  19. 数据预处理与特征工程—12.常见的数据预处理与特征工程手段总结
  20. android 邮箱格式设置,Android对邮箱格式的验证

热门文章

  1. 启发式算法/人工蜂群算法
  2. 第二次作业(WordCount)重制版
  3. 如何制作变年轻特效?分享几个简单的方法给你
  4. ios --- 动态获取键盘高度
  5. 赛目科技2023校园招聘火热进行中!(算法/开发等多个岗位)
  6. MySQL 安装到基础 SQL 语法
  7. 和我一起学 Three.js【初级篇】:1. 搭建 3D 场景
  8. 荧光素标记双孢蘑菇凝集素(ABL);FITC-ABL
  9. 智汀如何连接小米智能音箱?
  10. 考研英语大连百家外语国际部考研英语五大题型学习方法