浅谈MySQL中的子查询优化技巧
mysql的子查询的优化一直不是很友好,一直有受业界批评比较多,也是我在sql优化中遇到过最多的问题之一,你可以点击这里,这里来获得一些信息,mysql在处理子查询的时候,会将子查询改写,通常情况下,我们希望由内到外,也就是先完成子查询的结果,然后在用子查询来驱动外查询的表,完成查询,但是恰恰相反,子查询不会先被执行;今天希望通过介绍一些实际的案例来加深对mysql子查询的理解:
案例:用户反馈数据库响应较慢,许多业务动更新被卡住;登录到数据库中观察,发现长时间执行的sql;
|10437|usr0321t9m9|10.242.232.50:51201|oms|Execute|1179|Sending
Sql为:
selecttradedto0_.*froma1tradedto0_wheretradedto0_.tradestatus='1' and(tradedto0_.tradeoidin(selectorderdto1_.tradeoidfroma2orderdto1_where orderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%'))andtradedto0_.undefine4='1' andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15;
2.其他表的更新被阻塞:
updatea1settradesign='DAB67634-795C-4EAC-B4A0-78F0D531D62F', markColor='#CD5555',memotime='2012-09-22',markPerson='??'wheretradeoidin('gy2012092204495100032');
为了尽快恢复应用,将其长时间执行的sqlkill掉后,应用恢复正常;
3.分析执行计划:
db@3306:explainselecttradedto0_.*froma1tradedto0_wheretradedto0_.tradestatus='1'and(tradedto0_.tradeoidin(selectorderdto1_.tradeoid froma2orderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%'))andtradedto0_.undefine4='1'andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15; +----+--------------------+------------+------+---------------+------+---------+------+-------+----- |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+--------------------+------------+------+---------------+------+---------+------+-------+----- |1|PRIMARY|tradedto0_|ALL|NULL|NULL|NULL|NULL|27454|Usingwhere;Usingfilesort| |2|DEPENDENTSUBQUERY|orderdto1_|ALL|NULL|NULL|NULL|NULL|40998|Usingwhere| +----+--------------------+------------+------+---------------+------+---------+------+-------+-----
从执行计划上,我们开始一步一步地进行优化:
首先,我们看看执行计划的第二行,也就是子查询的那部分,orderdto1_进行了全表的扫描,我们看看能不能添加适当的索引:
A.使用覆盖索引:
db@3306:altertablea2addindexind_a2(proname,procode,tradeoid); ERROR1071(42000):Specifiedkeywastoolong;maxkeylengthis1000bytes
添加组合索引超过了最大keylength限制:
B.查看该表的字段定义:
db@3306:DESCa2; +---------------------+---------------+------+-----+---------+-------+ |FIELD|TYPE|NULL|KEY|DEFAULT|Extra| +---------------------+---------------+------+-----+---------+-------+ |OID|VARCHAR(50)|NO|PRI|NULL|| |TRADEOID|VARCHAR(50)|YES||NULL|| |PROCODE|VARCHAR(50)|YES||NULL|| |PRONAME|VARCHAR(1000)|YES||NULL|| |SPCTNCODE|VARCHAR(200)|YES||NULL||
C.查看表字段的平均长度:
db@3306:SELECTMAX(LENGTH(PRONAME)),avg(LENGTH(PRONAME))FROMa2; +----------------------+----------------------+ |MAX(LENGTH(PRONAME))|avg(LENGTH(PRONAME))| +----------------------+----------------------+ |95|24.5588|
D.缩小字段长度
ALTERTABLEMODIFYCOLUMNPRONAMEVARCHAR(156);
再进行执行计划分析:
db@3306:explainselecttradedto0_.*froma1tradedto0_wheretradedto0_.tradestatus='1'and(tradedto0_.tradeoidin(selectorderdto1_.tradeoidfroma2orderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%'))andtradedto0_.undefine4='1'andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15; +----+--------------------+------------+-------+-----------------+----------------------+---------+ |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+--------------------+------------+-------+-----------------+----------------------+---------+ |1|PRIMARY|tradedto0_|ref|ind_tradestatus|ind_tradestatus|345|const,const,const,const|8962|Usingwhere;Usingfilesort| |2|DEPENDENTSUBQUERY|orderdto1_|index|NULL|ind_a2|777|NULL|41005|Usingwhere;Usingindex| +----+--------------------+------------+-------+-----------------+----------------------+---------+
发现性能还是上不去,关键在两个表扫描的行数并没有减小(8962*41005),上面添加的索引没有太大的效果,现在查看t表的执行结果:
db@3306:selectorderdto1_.tradeoidfromtorderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%'; Emptyset(0.05sec)
结果集为空,所以需要将t表的结果集做作为驱动表;
4.通过上面测试验证,普通的mysql子查询写法性能上是很差的,为mysql的子查询天然的弱点,需要将sql进行改写为关联的写法:
selecttradedto0_.*froma1tradedto0_,(selectorderdto1_.tradeoidfroma2orderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%')t2wheretradedto0_.tradestatus='1'and(tradedto0_.tradeoid=t2.tradeoid)andtradedto0_.undefine4='1'andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15;
5.查看执行计划:
db@3306:explainselecttradedto0_.*froma1tradedto0_,(selectorderdto1_.tradeoidfroma2orderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%')t2wheretradedto0_.tradestatus='1'and(tradedto0_.tradeoid=t2.tradeoid)andtradedto0_.undefine4='1'andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15; +----+-------------+------------+-------+---------------+----------------------+---------+------+ |id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra| +----+-------------+------------+-------+---------------+----------------------+---------+------+ |1|PRIMARY|NULL|NULL|NULL|NULL|NULL|NULL|NULL|ImpossibleWHEREnoticedafterreadingconsttables| |2|DERIVED|orderdto1_|index|NULL|ind_a2|777|NULL|41005|Usingwhere;Usingindex| +----+-------------+------------+-------+---------------+----------------------+---------+------+
6.执行时间:
db@3306:selecttradedto0_.*froma1tradedto0_,(selectorderdto1_.tradeoidfroma2orderdto1_whereorderdto1_.pronamelike'%??%'ororderdto1_.procodelike'%??%')t2wheretradedto0_.tradestatus='1'and(tradedto0_.tradeoid=t2.tradeoid)andtradedto0_.undefine4='1'andtradedto0_.invoicetype='1'andtradedto0_.tradestep='0'and(tradedto0_.orderCompanylike'0002%')orderbytradedto0_.tradesignASC,tradedto0_.makertimedesclimit15; Emptyset(0.03sec)
缩短到了毫秒;