这两天实习项目遇到一个网页加载巨慢的问题(10多秒),然后定位到是一个MySQL查询特别慢的语句引起的:
SELECT * FROM ( SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala FROM tbankaccobala t WHERE 1 = 1 AND t.id IN ( -- 这个查询需要3s: SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1) FROM tbankaccobala GROUP BY vc_bankacco ) ) t
这个语句导致前端页面10多秒才有响应(但MySQL执行显示要4.6秒,phpMyAdmin也是10秒左右响应,为何?)
IN子查询语句优化
把IN语句里面的内容改成下面这样,只在外层再加一个select,就把3s的查询缩短为0.006s:
SELECT hhhh from( SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1) as hhhh FROM tbankaccobala GROUP BY vc_bankacco ) as sbstr -- 对IN的子查询做二次select,或者把IN改为JOIN都可以解决速度奇慢的问题
原语句空行处省略了一系列的其他表和INNER JOIN语句。一开始怀疑是多表的JOIN操作导致速度变慢,但删去JOIN变成上面这段注释掉的语句之后,速度依然非常慢,显示要3s,于是猜测 IN 才是导致速度变慢的主要因素,改后只要0.006s,啧…
EXPLAIN 未优化的语句:
(相关子查询是使用外部查询中的值的子查询)
EXPLAIN 优化的语句:
我的理解:优化前,子查询是相关子查询,对于外部产生的每个值,都要执行一次子查询;优化后,子查询不再是相关子查询,只需要执行一次子查询并缓存中间结果,外部查到的每个值去缓存的中间结果里比对一下就行了。
(有人说是能不能用索引的原因——这么说应该是不对的)
完整查询的后端响应速度对比:
前:
后:
索引优化
对于这么小的数据规模,时间还是太长了… 看前面explain执行计划的截图,嗯,没有索引…
给t1的vc_bankacco加上索引之后
解释执行计划:
查询和网页响应用时大幅缩短:
再看sql里还有三个join:
用的都是ot_dic_tdictionaryentry这张表的t4.vc_entry_value字段,那么试着给这个字段也加上索引吧,然后用时如下:
是的,时间反而变长了!
explain执行计划:
所以变慢原因是:
没加vc_entry_value的索引时,会先用vc_entry_no选出一个数量很小的表,再和t1做join,
而加了vc_entry_value的索引之后,MySQL就把这个索引用了起来,join语句被优化为先FirstMatch(ot_dic_tdictionaryentry),这产生了一个1713*1713=2934369行的中间结果(笛卡尔乘积),然后才使用vc_entry_no进行where过滤。
所以索引不能乱加啊,加错了反而会导致性能下降!这个示例里的查询要加索引只能在vc_entry_no上索引,而不能在vc_entry_value上!
这个示例中主要提升是IN子查询语句的优化。在使用索引的情况下,对IN子查询做优化前后的查询时间分别是3.1s和0.16s