今天一个同学给我教学系统 sql 优化 , 未优化之前用了1.2 秒出结果, 15000转硬盘
表结构:
CREATE TABLE `t_s_students` ( `id` int(11) NOT NULL AUTO_INCREMENT, `CLASSCODE` varchar(32) DEFAULT NULL, `STUDENTCODE` varchar(32) DEFAULT NULL, `STUDENTNAME` varchar(20) DEFAULT NULL, `CARDTYPE` varchar(20) DEFAULT NULL, `CARDNUMBER` varchar(30) DEFAULT NULL, `SEX` varchar(5) DEFAULT NULL, `AGE` varchar(5) DEFAULT NULL, `ZENGYUANCODE` varchar(20) DEFAULT NULL, `ZENGYUANNAME` varchar(20) DEFAULT NULL, `TRAINPASS` varchar(5) DEFAULT '0', `INCOMPANY` varchar(1) DEFAULT '0', `method` varchar(1) DEFAULT NULL, `agentcom` varchar(8) DEFAULT NULL, `repeatcount` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_tss_classcode` (`CLASSCODE`), KEY `idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE` (`repeatcount`,`agentcom`,`CARDNUMBER`,`CLASSCODE`), KEY `idx_repeatcount_agentcom_CARDNUMBER_agentcom` (`agentcom`), KEY `idx_agentcom_CARDNUMBER_CLASSCODE` (`agentcom`,`CARDNUMBER`,`CLASSCODE`) ) ENGINE=InnoDB AUTO_INCREMENT=276898 DEFAULT CHARSET=utf8; CREATE TABLE `t_s_students` ( `id` int(11) NOT NULL AUTO_INCREMENT, `CLASSCODE` varchar(32) DEFAULT NULL, `STUDENTCODE` varchar(32) DEFAULT NULL, `STUDENTNAME` varchar(20) DEFAULT NULL, `CARDTYPE` varchar(20) DEFAULT NULL, `CARDNUMBER` varchar(30) DEFAULT NULL, `SEX` varchar(5) DEFAULT NULL, `AGE` varchar(5) DEFAULT NULL, `ZENGYUANCODE` varchar(20) DEFAULT NULL, `ZENGYUANNAME` varchar(20) DEFAULT NULL, `TRAINPASS` varchar(5) DEFAULT '0', `INCOMPANY` varchar(1) DEFAULT '0', `method` varchar(1) DEFAULT NULL, `agentcom` varchar(8) DEFAULT NULL, `repeatcount` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_tss_classcode` (`CLASSCODE`), KEY `idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE` (`repeatcount`,`agentcom`,`CARDNUMBER`,`CLASSCODE`), KEY `idx_repeatcount_agentcom_CARDNUMBER_agentcom` (`agentcom`), KEY `idx_agentcom_CARDNUMBER_CLASSCODE` (`agentcom`,`CARDNUMBER`,`CLASSCODE`) ) ENGINE=InnoDB AUTO_INCREMENT=276898 DEFAULT CHARSET=utf8;
SQL语句如下:
SELECT d.noRepeatStudentNumber, d.noRepeatTrainPassNumber, concat(round(100 * ifnull(d.noRepeatTrainPassNumber / d.noRepeatStudentNumber, 0), 2), '%') AS noRepeatTrainPassRate, d.resultStudentNumber, d.trainPassNumber , concat(round(100 * ifnull(d.trainPassNumber / d.resultStudentNumber, 0), 2), '%') AS trainPassRate, d.incompanyNumber, concat(round(100 * ifnull(d.incompanyNumber / d.trainPassNumber, 0), 2), '%') AS incompanyRate FROM (SELECT ( SELECT COUNT(DISTINCT s1.cardnumber) FROM t_s_classes c, t_s_students s1 WHERE c.classcode = s1.classcode AND s1.repeatcount = '0' AND s1.agentcom LIKE concat('8007', '%') ) AS noRepeatStudentNumber, (SELECT COUNT(DISTINCT s2.cardnumber) FROM t_s_classes c, t_s_students s2 WHERE c.classcode = s2.classcode AND s2.trainpass = '1' AND c.classstatus = '2' AND s2.repeatcount = '0' AND s2.agentcom LIKE concat('8007', '%') ) AS noRepeatTrainPassNumber, (SELECT COUNT(DISTINCT s3.cardnumber) FROM t_s_classes c, t_s_students s3 WHERE c.classcode = s3.classcode AND s3.agentcom LIKE concat('8007', '%') ) AS resultStudentNumber, (SELECT COUNT(DISTINCT s4.cardnumber) FROM t_s_classes c, t_s_students s4 WHERE c.classcode = s4.classcode AND s4.trainpass = '1' AND c.classstatus = '2' AND s4.agentcom LIKE concat('8007', '%') ) AS trainPassNumber, (SELECT COUNT(DISTINCT s5.cardnumber) FROM t_s_classes c, t_s_students s5 WHERE c.classcode = s5.classcode AND s5.trainpass = '1' AND c.classstatus = '2' AND s5.incompany = '1' AND s5.agentcom LIKE concat('8007', '%') ) AS incompanyNumber FROM DUAL ) d;
执行计划: +--------------+-----------------------+-----------------+----------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+-------------------+-----------------------+----------------+--------------------+------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +--------------+-----------------------+-----------------+----------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+-------------------+-----------------------+----------------+--------------------+------------------------------------+ | 1 | PRIMARY | <derived2> | | system | | | | | 1 | 100 | | | 2 | DERIVED | | | | | | | | | | No tables used | | 7 | SUBQUERY | c | | ALL | PRIMARY | | | | 3821 | 10 | Using where | | 7 | SUBQUERY | s5 | | ref | idx_tss_classcode,idx_repeatcount_agentcom_CARDNUMBER_agentcom,idx_agentcom_CARDNUMBER_CLASSCODE | idx_tss_classcode | 99 | pro_oedu.c.CLASSCODE | 86 | 0.42 | Using where | | 6 | SUBQUERY | c | | ALL | PRIMARY | | | | 3821 | 10 | Using where | | 6 | SUBQUERY | s4 | | ref | idx_tss_classcode,idx_repeatcount_agentcom_CARDNUMBER_agentcom,idx_agentcom_CARDNUMBER_CLASSCODE | idx_tss_classcode | 99 | pro_oedu.c.CLASSCODE | 86 | 4.23 | Using where | | 5 | SUBQUERY | s3 | | range | idx_tss_classcode,idx_repeatcount_agentcom_CARDNUMBER_agentcom,idx_agentcom_CARDNUMBER_CLASSCODE | idx_agentcom_CARDNUMBER_CLASSCODE | 27 | | 90044 | 100 | Using where; Using index | | 5 | SUBQUERY | c | | eq_ref | PRIMARY | PRIMARY | 98 | pro_oedu.s3.CLASSCODE | 1 | 100 | Using index | | 4 | SUBQUERY | s2 | | range | idx_tss_classcode,idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE,idx_repeatcount_agentcom_CARDNUMBER_agentcom,idx_agentcom_CARDNUMBER_CLASSCODE | idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE | 60 | | 40148 | 10 | Using index condition; Using where | | 4 | SUBQUERY | c | | eq_ref | PRIMARY | PRIMARY | 98 | pro_oedu.s2.CLASSCODE | 1 | 10 | Using where | | 3 | SUBQUERY | s1 | | range | idx_tss_classcode,idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE,idx_repeatcount_agentcom_CARDNUMBER_agentcom,idx_agentcom_CARDNUMBER_CLASSCODE | idx_repeatcount_agentcom_CARDNUMBER_CLASSCODE | 60 | | 40148 | 100 | Using where; Using index | | 3 | SUBQUERY | c | | eq_ref | PRIMARY | PRIMARY | 98 | pro_oedu.s1.CLASSCODE | 1 | 100 | Using index | +--------------+-----------------------+-----------------+----------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------+-------------------+-----------------------+----------------+--------------------+------------------------------------+
总共执行了12个步骤 , 执行了1.2秒 ,其实相当快了,但是以后数据量上来后很难保证,这位同学要更快,要求真高阿
1. 这个语句的谓词有个特点:
第1相同部分: agentcom LIKE concat
第2相同部分: classstatus
第3相同部分: repeatcount
不相同部分: incompany
2. 每一次统计count 都要访问表一次 , 总共访问5次 , 要想办法去重 , 只访问一次表 , 使用case 语句做判断而做出统计数量出来 , 就是说case 一次匹配就加1 , 然后进行sum 代替count , 修改sql语句如下:
SELECT d.noRepeatStudentNumber, d.noRepeatTrainPassNumber, CONCAT ( round( 100 * ifnull( d.noRepeatTrainPassNumber / d.noRepeatStudentNumber, 0 ), 2 ), '%' ) AS noRepeatTrainPassRate, d.resultStudentNumber, d.trainPassNumber, CONCAT ( round( 100 * ifnull( d.trainPassNumber / d.resultStudentNumber, 0 ), 2 ), '%' ) AS trainPassRate, d.incompanyNumber, CONCAT ( round( 100 * ifnull( d.incompanyNumber / d.trainPassNumber, 0 ), 2 ), '%' ) AS incompanyRate FROM ( SELECT sum( d1.noRepeatStudentNumber ) AS noRepeatStudentNumber, sum( d1.noRepeatTrainPassNumber ) AS noRepeatTrainPassNumber, sum( d1.resultStudentNumber ) AS resultStudentNumber, sum( d1.trainPassNumber ) AS trainPassNumber, sum( d1.incompanyNumber ) AS incompanyNumber FROM ( SELECT max( CASE WHEN s.repeatcount = '0' AND s.agentcom LIKE concat ( '8007', '%' ) THEN 1 ELSE 0 END ) AS noRepeatStudentNumber, max( CASE WHEN s.repeatcount = '0' AND s.trainpass = '1' AND c.classstatus = '2' AND s.agentcom LIKE concat ( '8007', '%' ) THEN 1 ELSE 0 END ) AS noRepeatTrainPassNumber, max( CASE WHEN s.agentcom LIKE concat ( '8007', '%' ) THEN 1 ELSE 0 END ) AS resultStudentNumber, max( CASE WHEN s.trainpass = '1' AND c.classstatus = '2' AND s.agentcom LIKE concat ( '8007', '%' ) THEN 1 ELSE 0 END ) AS trainPassNumber, max( CASE WHEN s.trainpass = '1' AND c.classstatus = '2' AND s.incompany = '1' AND s.agentcom LIKE concat ( '8007', '%' ) THEN 1 ELSE 0 END ) AS incompanyNumber FROM t_s_classes t INNER JOIN t_s_students s ON t.classcode = s.classcode GROUP BY s.cardnumber ORDER BY NULL ) d1 ) d;
3. 性能瞬间0.5秒
总结:
这是一个案例参考而已 , 优化sql 要观察其特点 .