慢SQL文本如下:


SQL执行时长达38S,获取361条数据结果返回。

SQL执行计划如下:


初步索引实现SQL优化:

由执行计划可知,SQL首先从minute_time(minute_time数据大概估算1440条)表中获取数据,然后嵌套驱动month_show_data(month_show_data数据大概估算333389条),两个表都是全表扫描!可以通过添加索引将SQL优化。

为两个表添加如下索引:

ALTER TABLE `minute_time`  ADD INDEX `idx_minute`(`minute`);

ALTER TABLE `month_show_data` ADD INDEX `idx_sz_type_ct`(`server_zone`, `type`, `create_time`);

添加索引后,SQL执行情况如下:


SQL 执行10s左右返回361条结果集。

添加索引后的执行计划如下:


由执行计划可知,SQL通过索引idx_minute先从minute_time中过滤出361条数据结果集,然后与通过索引idx_sz_type_ct从month_show_data中过滤出931条数据结果集相关联!

虽然SQL已经得到优化,但SQL长达10s的执行时间,对业务来说无法接受,随着数据量的增加,SQL执行时间也会越来越慢。

虚拟列实现SQL优化:

分析SQL可知,SQL的性能瓶颈在于a.minute = DATE_FORMAT(b.create_time, '%H:%i') 两表之间的关联关系,SQL无法通过表之间的关联关系直接驱动month_show_data直接返回数据。并且MySQL不支持函数索引。无法通过创建函数索引来优化该SQL。

这时候SQL如何在不改变业务的需求下继续深入优化呢?

MySQL 5.7 增加了虚拟列的新功能,可以类似的实现Oracle函数索引。由此思路,month_show_data增加虚拟列vr_time,并添加虚拟列索引idx_vr_time。

ALTER TABLE `month_show_data` ADD vr_time VARCHAR(10) AS (DATE_FORMAT(create_time, '%H:%i')) STORED, ADD KEY idx_vr_time (vr_time);

并将SQL改写为:


再次执行,0.27s左右即可返回所需的数据。

执行计划如下:


此SQL的优化是否就这样结束了呢?明显不是。

伪列实现SQL优化:

由数据量、表之间的关联关系及返回的结果集推断可知,只有在minute_time和month_show_data分别过滤后,再对符合条件的结果集进行关联才是最优的执行计划,而由前面的分析可知,执行计划并没有按照我们想象中方式的实现关联。伪列的引入,可以强制SQL改变表之间的关联顺序,获得想要的执行计划。将SQL改写成如下方式:


SQL在0.004s之后即返回查询结果。

执行计划如下:


分析执行计划可知,SQL先取得DERIVED3结果集(即month_show_data表符合条件的结果集),然后取得DERIVED2结果集(即minute_time表符合条件的结果集),最后以DERIVED2嵌套关联DERIVED3,执行计划达到最优。

此次SQL的优化难点在于,MySQL并不支持函数索引,在表设计和业务设计的时候,尽量避免对关联谓词和过滤谓词进行函数运算。MySQL 5.7 虚拟列的引入可以在不改动业务实现的情况下,实现函数索引类似的需求。基于代价的优化器存在缺陷,并不能每次都会选出最优的执行计划。能准确分析表之间的关联关系,才是SQL优化的制胜关键!

Logo

更多推荐