DELIMITER $$

USE `xxx`$$

DROP FUNCTION IF EXISTS `F_getBuluDates`$$

CREATE DEFINER=`root`@`localhost` FUNCTION `F_getBuluDates`(`PuserId` INT(11)) RETURNS VARCHAR(3000) CHARSET gbk
BEGIN
DECLARE buluDate,buluDateTime,LiWaibuluDates,nowdate,bldate VARCHAR(3000) DEFAULT "";
DECLARE rtMsg,RuserName,maxRiZhiDate,btjrqs,blyqrq,tmpdate,yxblsj VARCHAR(255) DEFAULT "";
DECLARE Ra51count INT(30);
DECLARE RbeginDate,RendDate,RtmpDate VARCHAR(2550);
DECLARE u,gqcount,a21Count INT(30) DEFAULT 0;
DECLARE allReadRows,readRows,mySYL,blCount,recordCount,btjryCount,btjrqCount,yxblts,i INT(11) DEFAULT 0;
SELECT a5204,a5205 INTO yxblts,yxblsj FROM a52 WHERE a5218="xxxxxxx" LIMIT 0,1; SELECT username INTO RuserName FROM USER WHERE id=PuserId; SELECT COUNT(id) INTO btjryCount FROM a52 WHERE a5214=PuserId AND a5218='xxxxx';
IF btjryCount=0 THEN SELECT GROUP_CONCAT(a5215) INTO btjrqs FROM a52 WHERE a5218='btjrq';
SET tmpdate=CURDATE();
SET nowdate=DATE_SUB(CURDATE(),INTERVAL yxblts DAY); REPEAT
SELECT MAX(a2104) INTO maxRiZhiDate FROM a21 WHERE issubmit='y' AND a2104<tmpdate AND FIND_IN_SET(a2104,bldate)=0;
SET tmpdate=maxRiZhiDate;
IF tmpdate>=nowdate THEN
IF F_isWorkDay(maxRiZhiDate)=1 OR ISNULL(maxRiZhiDate) THEN
IF NOT ISNULL(maxRiZhiDate) AND LENGTH(maxRiZhiDate)>0 THEN
IF FIND_IN_SET(maxRiZhiDate,btjrqs)=0 THEN
SELECT COUNT(id) INTO recordCount FROM a21 WHERE a2104=maxRiZhiDate AND creatorid=PuserId;
IF recordCount=0 THEN
IF DAYOFWEEK(maxRiZhiDate)=6 THEN
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+2+yxblts)," ",yxblsj);
ELSEIF DAYOFWEEK(maxRiZhiDate)=7 THEN
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+1+yxblts)," ",yxblsj);
ELSE
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+yxblts)," ",yxblsj);
END IF; IF(CURRENT_TIMESTAMP()<buluDateTime) THEN
SET buluDate=maxRiZhiDate;
END IF;
END IF;
END IF;
END IF; END IF;
IF CHAR_LENGTH(buluDate)>0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(buluDate,",",bldate);
ELSE
SET bldate=buluDate;
END IF;
END IF;
END IF; SET buluDate='';
UNTIL tmpdate<=nowdate
END REPEAT; SELECT COUNT(id) INTO gqcount FROM a52 WHERE a5216=CURDATE() AND a5218='xxxxxx';
IF gqcount>0 THEN
SELECT a5215 INTO blyqrq FROM a52 WHERE a5216=CURDATE() AND a5218='xxxxxx';
SELECT COUNT(id) INTO a21Count FROM a21 WHERE a2104=blyqrq AND creatorid=PuserId;
IF a21Count=0 THEN
IF FIND_IN_SET(blyqrq,bldate)=0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(blyqrq,",",bldate);
ELSE
SET bldate=blyqrq;
END IF;
END IF;
END IF;
END IF; IF ISNULL(btjrqs) THEN
SET btjrqs="";
END IF; SELECT GROUP_CONCAT(a5108) INTO LiWaibuluDates FROM a51 WHERE a5110='y' AND F_ifInSet(a5108,btjrqs)=0 AND a5106 LIKE RuserName AND a5112='否' AND CONCAT(a5108," ",a5113)<CURRENT_TIMESTAMP() AND CURRENT_TIMESTAMP()<CONCAT(a5114," ",a5113);
IF NOT ISNULL(LiWaibuluDates) AND LENGTH(TRIM(LiWaibuluDates))>0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(LiWaibuluDates,",",bldate);
ELSE
SET bldate=LiWaibuluDates;
END IF;
END IF; SELECT COUNT(id) INTO RA51count FROM a51 WHERE a5110='n' AND F_getid1value2(a5105,1)=PuserId;
IF RA51count>0 THEN
SET u=0;
WHILE u< RA51count DO
SELECT a5108,a5109 INTO RbeginDate,RendDate FROM a51 WHERE a5110='n' AND F_getid1value2(a5105,1)=PuserId ORDER BY id LIMIT u,1;
SET RtmpDate=RbeginDate;
WHILE RtmpDate<=RendDate DO IF FIND_IN_SET(RtmpDate,bldate)>0 THEN
SET bldate=F_removeFromSet(RtmpDate,bldate);
END IF;
SET RtmpDate=DATE_ADD(RtmpDate, INTERVAL 1 DAY);
END WHILE;
SET u=u+1;
END WHILE;
END IF; END IF; RETURN bldate; END$$ DELIMITER ;

在老系统中该函数调用一次需要话20多秒到30秒左右。

拿到sql之后,首先要确定思路。不能着急这下手。

1. 首先查看各个表的数据量:

select count(*) from xxx;

发现只有 a21 的数据量达到了十几万,其他表数据量都比较小。所以重点是 a21表,仔细阅读了一遍函数的定义,发现涉及 到 a21 的有一处循环:

REPEAT
SELECT MAX(a2104) INTO maxRiZhiDate FROM a21 WHERE issubmit='y' AND a2104<tmpdate AND FIND_IN_SET(a2104,bldate)=0;

很显然使用了 find_in_set 函数,所以改语句无法使用索引。所以想要简单的通过增加索引来解决问题,应该是行不通的。

这样就定位到了函数运行慢的问题。结合业务理解改函数的含义。

为了使用索引,我们需要去掉 find_in_set 函数,理解了业务和原函数的功能之后,对改函数的 repeat 循环部分进行了重写:

DELIMITER $$

USE `oa`$$

DROP FUNCTION IF EXISTS `F_getBuluDates_inner`$$

CREATE DEFINER=`root`@`localhost` FUNCTION `F_getBuluDates_inner`(`PuserId` INT(11), tmpdate VARCHAR(32), nowdate VARCHAR(32)) RETURNS VARCHAR(3000) CHARSET gbk
BEGIN
DECLARE buluDate,buluDateTime,LiWaibuluDates,bldate VARCHAR(3000) DEFAULT "";
DECLARE rtMsg,RuserName,maxRiZhiDate,btjrqs,blyqrq,yxblsj VARCHAR(255) DEFAULT "";
DECLARE Ra51count INT(30);
DECLARE RbeginDate,RendDate,RtmpDate VARCHAR(2550);
DECLARE u,gqcount,a21Count INT(30) DEFAULT 0;
DECLARE allReadRows,readRows,mySYL,blCount,recordCount,btjryCount,btjrqCount,yxblts,i INT(11) DEFAULT 0;
DECLARE no_more_data INT DEFAULT 0; DECLARE my_cursor CURSOR FOR SELECT DISTINCT a2104 FROM a21 WHERE issubmit='y' AND a2104 < tmpdate AND a2104 >= nowdate ORDER BY a2104 DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_more_data = 1; SELECT a5204,a5205 INTO yxblts,yxblsj FROM a52 WHERE a5218="gzrz" LIMIT 0,1; SELECT username INTO RuserName FROM USER WHERE id=PuserId; SELECT COUNT(id) INTO btjryCount FROM a52 WHERE a5214=PuserId AND a5218='xxxxx';
IF btjryCount=0 THEN SELECT GROUP_CONCAT(a5215) INTO btjrqs FROM a52 WHERE a5218='xxx'; OPEN my_cursor;
FETCH my_cursor INTO maxRiZhiDate; REPEAT
IF F_isWorkDay(maxRiZhiDate)=1 OR ISNULL(maxRiZhiDate) THEN
IF NOT ISNULL(maxRiZhiDate) AND LENGTH(maxRiZhiDate)>0 THEN
IF FIND_IN_SET(maxRiZhiDate,btjrqs)=0 THEN
SELECT COUNT(id) INTO recordCount FROM a21 WHERE a2104=maxRiZhiDate AND creatorid=PuserId;
IF recordCount=0 THEN
IF DAYOFWEEK(maxRiZhiDate)=6 THEN
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+2+yxblts)," ",yxblsj);
ELSEIF DAYOFWEEK(maxRiZhiDate)=7 THEN
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+1+yxblts)," ",yxblsj);
ELSE
SET buluDateTime=CONCAT(FROM_DAYS(TO_DAYS(maxRiZhiDate)+yxblts)," ",yxblsj);
END IF; IF(CURRENT_TIMESTAMP()<buluDateTime) THEN
SET buluDate=maxRiZhiDate;
END IF;
END IF;
END IF;
END IF; END IF;
IF CHAR_LENGTH(buluDate)>0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(buluDate,",",bldate);
ELSE
SET bldate=buluDate;
END IF;
END IF; FETCH my_cursor INTO maxRiZhiDate;
SET buluDate='';
UNTIL no_more_data = 1
END REPEAT; SELECT COUNT(id) INTO gqcount FROM a52 WHERE a5216=CURDATE() AND a5218='xxx';
IF gqcount>0 THEN
SELECT a5215 INTO blyqrq FROM a52 WHERE a5216=CURDATE() AND a5218='xxxxxxxxxx';
SELECT COUNT(id) INTO a21Count FROM a21 WHERE a2104=blyqrq AND creatorid=PuserId;
IF a21Count=0 THEN
IF FIND_IN_SET(blyqrq,bldate)=0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(blyqrq,",",bldate);
ELSE
SET bldate=blyqrq;
END IF;
END IF;
END IF;
END IF; IF ISNULL(btjrqs) THEN
SET btjrqs="";
END IF; SELECT GROUP_CONCAT(a5108) INTO LiWaibuluDates FROM a51 WHERE a5110='y' AND F_ifInSet(a5108,btjrqs)=0 AND a5106 LIKE RuserName AND a5112='否' AND CONCAT(a5108," ",a5113)<CURRENT_TIMESTAMP() AND CURRENT_TIMESTAMP()<CONCAT(a5114," ",a5113);
IF NOT ISNULL(LiWaibuluDates) AND LENGTH(TRIM(LiWaibuluDates))>0 THEN
IF CHAR_LENGTH(bldate)>0 THEN
SET bldate=CONCAT(LiWaibuluDates,",",bldate);
ELSE
SET bldate=LiWaibuluDates;
END IF;
END IF; SELECT COUNT(id) INTO RA51count FROM a51 WHERE a5110='n' AND F_getid1value2(a5105,1)=PuserId;
IF RA51count>0 THEN
SET u=0;
WHILE u< RA51count DO
SELECT a5108,a5109 INTO RbeginDate,RendDate FROM a51 WHERE a5110='n' AND F_getid1value2(a5105,1)=PuserId ORDER BY id LIMIT u,1;
SET RtmpDate=RbeginDate;
WHILE RtmpDate<=RendDate DO IF FIND_IN_SET(RtmpDate,bldate)>0 THEN
SET bldate=F_removeFromSet(RtmpDate,bldate);
END IF;
SET RtmpDate=DATE_ADD(RtmpDate, INTERVAL 1 DAY);
END WHILE;
SET u=u+1;
END WHILE;
END IF; END IF; RETURN bldate; END$$ DELIMITER ;

然后为了让新的 sql 使用索引:

DECLARE my_cursor CURSOR FOR

SELECT DISTINCT a2104 FROM a21 WHERE issubmit='y' AND a2104 < tmpdate AND a2104 >= nowdate ORDER BY a2104 DESC;

新增了索引:

add index issubmit_a2104(issubmit,a2104);

最终的提供给外部的调用函数:

DELIMITER $$
CREATE FUNCTION F_getBuluDates_new(`PuserId` INT(11)) RETURNS VARCHAR(3000) CHARSET gbk
BEGIN
DECLARE bldate VARCHAR(3000) DEFAULT "";
DECLARE tmpdate,nowdate VARCHAR(32) DEFAULT "";
DECLARE yxblts INT;
SELECT a5204 INTO yxblts FROM a52 WHERE a5218="gzrz" LIMIT 0,1;
SET tmpdate = CURDATE();
SET nowdate = DATE_SUB(tmpdate,INTERVAL yxblts DAY);
SET bldate = F_getBuluDates_inner(PuserId,tmpdate,nowdate);
RETURN bldate;
END
$$

测试效果:

mysql> select F_getBuluDates_new(10687);
+---------------------------------------------------------------------------------------------------------------+
| F_getBuluDates_new(10687) |
+---------------------------------------------------------------------------------------------------------------+
| 2016-08-04,2016-08-05,2016-08-08,2016-08-09,2016-08-10,2016-08-11,2016-08-12,2016-08-15,2016-08-16,2016-08-17 |
+---------------------------------------------------------------------------------------------------------------+
1 row in set (1.10 sec) mysql> select F_getBuluDates(10687);
+---------------------------------------------------------------------------------------------------------------+
| F_getBuluDates(10687) |
+---------------------------------------------------------------------------------------------------------------+
| 2016-08-04,2016-08-05,2016-08-08,2016-08-09,2016-08-10,2016-08-11,2016-08-12,2016-08-15,2016-08-16,2016-08-17 |
+---------------------------------------------------------------------------------------------------------------+
1 row in set (13.10 sec)

性能提升了 11 倍多。客户对效果很满意。

新的执行计划:

mysql> explain SELECT DISTINCT a2104 FROM a21 WHERE issubmit='y' AND a2104 < '2016-08-16' AND a2104 >= '2016-08-10' ORDER BY a2104 DESC;
+----+-------------+-------+-------+----------------------+----------------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+----------------------+----------------+---------+------+------+--------------------------+
| 1 | SIMPLE | a21 | range | a2104,issubmit_a2104 | issubmit_a2104 | 516 | NULL | 834 | Using where; Using index |
+----+-------------+-------+-------+----------------------+----------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

总结:

从原函数实现来看,在数量小时,还是可以的。但是一旦数据量大了,就会出现性能问题。主要问题是在 where 条件中使用了太多的自定义函数,而且这个函数的参数还是表的数据列。
导致无法使用索引。无法使用索引就会进行全表扫描,所以数据量大时会导致性能问题。

  

MySQL优化一例的更多相关文章

  1. MySQL派生表(derived)优化一例

    1.什么是派生表derived 关键字:子查询–>在From后where前的子查询 mysql; +----+-------------+------------+------+-------- ...

  2. MySQL优化聊两句

    原文地址:http://www.cnblogs.com/verrion/p/mysql_optimised.html MySQL优化聊两句 MySQL不多介绍,今天聊两句该如何优化以及从哪些方面入手, ...

  3. MySQL优化实例

    这周就要从泰笛离职了,在公司内部的wiki上,根据公司实际的项目,写了一些mysql的优化方法,供小组里的小伙伴参考下,没想到大家的热情很高,还专门搞了个ppt讲解了一下. 举了三个大家很容易犯错的地 ...

  4. [转] MySql 优化 大数据优化

    一.我们可以且应该优化什么? 硬件 操作系统/软件库 SQL服务器(设置和查询) 应用编程接口(API) 应用程序 ------------------------------------------ ...

  5. (转)MySQL优化实例

    在Apache, PHP,MySQL的体系架构中,MySQL对于性能的影响最大,也是关键的核心部分.对于Discuz!论坛程序也是如此,MySQL的设置是否合理优化,直接影响到论坛的速度和承载量!同时 ...

  6. MYSQL 优化建议

    转自 http://coolshell.cn/articles/1846.html MYSQL 优化建议20条 1. 为查询缓存优化你的查询 大多数的MySQL服务器都开启了查询缓存.这是提高性最有效 ...

  7. MySQL优化四(优化表结构)

    body { font-family: Helvetica, arial, sans-serif; font-size: 14px; line-height: 1.6; padding-top: 10 ...

  8. MySQL优化面试

    原则:尽量使用整型表示字符串 存储IP INET_ATON(str),address to number INET_NTOA(number),number to address MySQL内部的枚举类 ...

  9. MySQL优化之my.conf配置详解

    最近项目不太忙,所以有时间静心来研究下mysql的优化,对于MySQL的设置是否合理优化,直接影响到网站的速度和承载量!同时,MySQL也是优化难度最大的一个部分,不但需要理解一些MySQL专业知识, ...

随机推荐

  1. [翻译]AKKA笔记 - 有限状态机 -1

    原文地址:http://rerun.me/2016/05/21/akka-notes-finite-state-machines-1/ 我最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子 ...

  2. GroupData群数据库的还原与优化

    一.背景 这个数据库的数据文件mdf大概有83G左右,当还原数据库之后感觉可以做很多性能方面上的调优,合并数据后mdf数据文件大概有59G左右,行压缩后mdf数据文件大概有39G左右,页压缩后mdf数 ...

  3. 大三作品:不需要售货员的超市? Easy-Shopping超市导购系统

    本来么,逛超市是一件很爽的事情,拉上父母孩子,推个大推车,一边聊一边买,然后开开心心的回家去. 可到了旺季,逛超市可就麻烦了,买东西人挤人,到结算的地方人山人海,一刷卡,我去,怎么这个卫生纸这么贵!这 ...

  4. Encountered an unexpected error when attempting to resolve tag helper directive '@addTagHelper' with value '"*, Microsoft.AspNet.Mvc.TagHelpers"'

    project.json 配置: { "version": "1.0.0-*", "compilationOptions": { " ...

  5. 构建自己的PHP框架--构建模版引擎(1)

    前段时间太忙,导致好久都没有更新博客了,今天抽出点时间来写一篇. 其实这个系列的博客很久没有更新了,之前想好好规划一下,再继续写,然后就放下了,今天再捡起来继续更新. 今天我们来说一下,如何构建自己的 ...

  6. position:sticky的兼容性尝试

    开篇 笔者刚刚结束淘宝的工作,现在加入了一家有青春活力的垂直电商公司,正对着阿里巴巴的西溪园区,最近一直在熟悉新的工作环境和规范,因此博客有好些时间没有更新了,在此抱歉! 在忙碌完公司的发布系统之后, ...

  7. Windows Programming ---- Beginning Visual C#

    span.kw { color: #007020; font-weight: bold; } code > span.dt { color: #902000; } code > span. ...

  8. 理解CSS前景色和透明度

    前面的话 颜色的出现让网页不再只是黑白,运用好颜色设计,能让网页增色不少.一个网页给人们留下的第一印象实际上就是它的整体颜色.关于如何设置颜色,请移步CSS的6种颜色模式.实际上,颜色的应用主要分为前 ...

  9. 一款批量修改AE模板的工具

    一.需求分析 对于视频后期剪辑及相关从业人员来说,AE(After Effects)模板效果是一个不错的开始点.在模板效果的基础上,可以很快的做出各种炫酷的后期效果.但是在网上下载的模板工程中,往往包 ...

  10. 微信企业号开发(1)WebAPI在回调模式中的URL验证

    微信回调模式的官方文档. 开发语言:C#(微信相关功能代码可以从官网下载) 首先,必须要明确几个参数,这几个参数在微信企业号中,每次调用都会使用到. 1.msg_signature:签名(已加密,加密 ...