博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MySQL的一次优化记录 (IN子查询和索引优化)
阅读量:5365 次
发布时间:2019-06-15

本文共 3809 字,大约阅读时间需要 12 分钟。

这两天实习项目遇到一个网页加载巨慢的问题(10多秒),然后定位到是一个MySQL查询特别慢的语句引起的:

SELECT *FROM (    SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala        , t.en_usablebala, t1.vc_nameinbank, date_format(t.D_IMPORTTIME, '%Y-%m-%d %H:%i:%S') AS D_IMPORTTIME        , t.vc_fundcode, t.c_datamode, t.vc_taskid, t.id, t.vc_projectname        , t.vc_projectcode, t1.c_accotype        , (            SELECT IF(vc_occurtime IS NULL, DATE_FORMAT(vc_occurdate, '%Y-%m-%d'), DATE_FORMAT(CONCAT(vc_occurdate, vc_occurtime), '%Y-%m-%d %H:%i:%S')) AS tradeTime            FROM tbanktradedetail_view            WHERE vc_bankacco = t.vc_bankacco        ) AS d_tradetime, t3.vc_entry_caption AS C_ACCOTYPE_STR, t5.vc_entry_caption AS VC_BANKNAME, t4.vc_entry_caption AS VC_MONEYTYPE_STR    FROM tbankaccobala t        INNER JOIN tbankaccoinfo t1 ON t.vc_bankacco = t1.vc_bankacco        INNER JOIN (            SELECT vc_entry_value, vc_entry_caption            FROM ot_dic_tdictionaryentry            WHERE vc_entry_no = '5087'        ) t3        ON t1.c_accotype = t3.vc_entry_value        INNER JOIN (            SELECT vc_entry_value, vc_entry_caption            FROM ot_dic_tdictionaryentry            WHERE vc_entry_no = '1004'        ) t4        ON t1.VC_MONEYTYPE = t4.vc_entry_value        INNER JOIN (            SELECT vc_entry_value, vc_entry_caption            FROM ot_dic_tdictionaryentry            WHERE vc_entry_no = '1014'        ) t5        ON t.c_bankno = t5.vc_entry_value    WHERE 1 = 1        AND t.id IN (            --   this query will take 4.6s:            -- SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC), ',', 1)            -- FROM tbankaccobala            -- GROUP BY vc_bankacco            -- but the following query only takes 1.1s:            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的子查询做二次查询        )) tWHERE 1 = 1ORDER BY t.D_IMPORTTIME DESC

抽出查询慢关键部分:

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 未优化的语句:

1489132-20190820011238586-1628249026.png
(相关子查询是使用外部查询中的值的子查询)

EXPLAIN 优化的语句:

1489132-20190820010630160-1180185191.png

我的理解:优化前,子查询是相关子查询,对于外部产生的每个值,都要执行一次子查询;优化后,子查询不再是相关子查询,只需要执行一次子查询并缓存中间结果,外部查到的每个值去缓存的中间结果里比对一下就行了。

(有人说是能不能用索引的原因——这么说应该是不对的)

完整查询的后端响应速度对比:

前:
1489132-20190823144901194-1755557082.png

后:

1489132-20190823144939547-500392417.png

索引优化

对于这么小的数据规模,时间还是太长了… 看前面explain执行计划的截图,嗯,没有索引…

给t1的vc_bankacco加上索引之后
1489132-20190823141012848-1896575639.png
1489132-20190823140833660-2022794083.png
解释执行计划:
1489132-20190823141228729-983443843.png
查询和网页响应用时大幅缩短:
1489132-20190823143953078-1180321516.png

再看sql里还有三个join:

1489132-20190823141826278-1827615179.png
用的都是ot_dic_tdictionaryentry这张表的t4.vc_entry_value字段,那么试着给这个字段也加上索引吧,然后用时如下:
1489132-20190823142146608-670966833.png
是的,时间反而变长了

explain执行计划:

1489132-20190823142442863-1315804179.png

所以变慢原因是:

没加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

转载于:https://www.cnblogs.com/dylanchu/p/11380723.html

你可能感兴趣的文章
mongodb索引
查看>>
nginx源码学习资源(不断更新)
查看>>
【bzoj2882】工艺 后缀自动机+STL-map
查看>>
[redis] redis
查看>>
Linux的加密认证功能以及openssl详解
查看>>
[Tools] 使用XP远程登录Win8系统
查看>>
【RL-TCPnet网络教程】第38章 TFTP简单文件传输基础知识
查看>>
HDU- 2265 Encoding The Diary
查看>>
socket基本概念
查看>>
[第三方]SCNetworkReachability 获取网络状态控件使用方法
查看>>
在Windows上使用putty连接一台Linux主机
查看>>
Socket常见错误
查看>>
百度地图2.0API和3.0API。你想要的百度地图的这都有
查看>>
专业词汇
查看>>
星期五的收获
查看>>
proxmox 去除订阅提示
查看>>
使用Html.EditorFor()为文本框加上maxlength,placeholder等属性
查看>>
[转]后缀数组求最长重复子串
查看>>
设计模式——外观模式详解
查看>>
MVC3 控件
查看>>