问题语句
SELECT * FROM a WHERE `type` = 'appointment' AND `event` = 14 AND EXISTS ( SELECT * FROM b WHERE a.`sheet_id` = b.`id` AND `company_id` = 8 AND b.`deleted_at` IS NULL ) ORDER BY a.id DESC LIMIT 6;
解读执行计划
在exists类型的子查询的执行计划中,select_type一栏分别是PRIMARY和DEPENDENT SUBQUERY
DEPENDENT SUBQUERY的意思是:子查询,依赖于外层的查询;DEPEND SUBQUERY是依赖于SQL的主体部分,它的执行次数最大可能和SQL主体部分结果的行数一样多(这里因为有limit6,所以看起来主表扫描行数是6,如果去掉这个limit6的话,这个值是1500W)上面这句话解释得通俗一点就是外连接先执行查询,然后把查询的结果集放入子查询内进行匹配;外查询每执行一次查询,就要来子查询匹配一次 join的执行计划中,select_type一栏都是simple
join的第一行的就是外表 从上面的对比可以看出,无论是 exists类型的子查询 还是join,都基本可以看作遵循了第一行就是驱动表的规则(注意不是所有子查询都遵循这个规则,本篇只针对exists类型的dependent subquery)
分析
图一PRIMARY对应的表就是图二中的a表,DEPENDENT SUBQUERY表就是图二中的b表;a表有1500W行数据,b表有2W行数据 所以图一的SQL执行效率如此低下的原因就是大表驱动小表
优化
exists改写为join
SELECT a.* FROM a join b on a.`sheet_id` = b.`id` WHERE a.`type` = 'appointment' AND a.`event` = 14 AND b.`company_id` = 8 AND b.`deleted_at` IS NULL ORDER BY a.`id` DESC LIMIT 6;
由于a表作为内表,因此在a.`sheet_id`,a.`type`,a.`event`上创建联合索引;语句中出现了b表的本地谓词,所以b表的b.`company_id`,b.`deleted_at`上也要创建联合索引 优化结果,执行时间:117s→0.36s,性能提升了2000倍这个语句有一个更极端的取值,在b.`company_id` = 2的时候,小表不会搜出任何满足条件的结果,在这种情况下,原语句执行时间在350s以上,而新语句仅需要0.03s,性能提升万倍
优化案例
今天优化的这批语句中,大多数是exists子查询的问题,可以看出这个研发小哥非常的喜欢用exists这种写法;前面的那个exists语句是泛用型,后面的exists语句加了些新花样eg.
SELECT SUM(`xxxx`) AS ag FROM a WHERE EXISTS ( SELECT * FROM b WHERE a.`delivery_sheet_id` = b.`id` AND (`status` = 4 OR `is_rejected` = '1') AND `company_id` = 8 AND b.`deleted_at` IS NULL ) AND `status` IN (0, 4) AND `collection_type` IN (2, 3) AND a.`deleted_at` IS NULL;
or的优化通常改写union,但这里是求sum不能这么改,需要改写成2个语句然后求和;对应的列要建好索引
select c.ag+d.ag as ag from (SELECT SUM(a.`xxxx`) AS ag FROM a join b on a.`delivery_sheet_id` = b.`id` where b.`status` = 4 ANDb.`company_id` = 8 AND b.`deleted_at` IS NULL AND a.`status` IN (0, 4) AND a.`collection_type` IN (2, 3) AND a.`deleted_at` IS NULL) c, ( SELECT SUM(a.`xxxx`) AS ag FROM a join b on a.`delivery_sheet_id` = b.`id` where b.`is_rejected` = '1' ANDb.`company_id` = 8 AND b.`deleted_at` IS NULL AND a.`status` IN (0, 4) AND a.`collection_type` IN (2, 3) AND a.`deleted_at` IS NULL) d;
优化结果,执行时间:18s→0.2s
in改写join的思路和exists差不多
这里没有现成的例子,粘贴一篇郑松华老师公众号的分析过来 原语句
SELECT COUNT( * ) AS totalNum, sum( CASE WHEN F.ALARM_LEVEL = 1 THEN 1 ELSE 0 END ) AS LEVELS1, sum( CASE WHEN F.ALARM_LEVEL = 2 THEN 1 ELSE 0 END ) AS LEVELS2, sum( CASE WHEN F.ALARM_LEVEL = 3 THEN 1 ELSE 0 END ) AS LEVELS3, sum( CASE WHEN F.DEAL_STATE = 0 THEN 1 ELSE 0 END ) AS DESTS FROM F LEFT JOIN DC ON DC.ID = F.CONST_ID LEFT JOIN V ON V.ID = F.VEHICLE_ID LEFT JOIN AREA ON AREA.ID = V.SYS_DIVISION_ID WHERE DC.ID IS NOT NULL AND V.ID IS NOT NULL AND F.DEAL_STATE = 0 AND ALARM_LEVEL IN ( 1, 2, 3 ) AND F.VEHICLE_ID IN ( SELECT VEHICLE_ID FROM GVLK WHERE GROUP_ID IN ( SELECT GROUP_ID FROM GULK WHERE USER_ID = 'ff8080816091b09c0161f9b825750a9a' ) UNION SELECT VEHICLE_ID FROM UVLK WHERE USER_ID = 'ff8080816091b09c0161f9b825750a9a' ) AND date( F.ALARM_TIME ) BETWEEN '2000-01-01' AND '2018-08-14' AND AREA.PATH LIKE CONCAT( ( SELECT ARE.PATH FROM ARE WHERE ARE.ID = '0' ), '%' )
执行计划如下
改写如下(in改join)
explain extended SELECT COUNT( * ) AS totalNum, sum( CASE WHEN F.ALARM_LEVEL = 1 THEN 1 ELSE 0 END ) AS LEVELS1, sum( CASE WHEN F.ALARM_LEVEL = 2 THEN 1 ELSE 0 END ) AS LEVELS2, sum( CASE WHEN F.ALARM_LEVEL = 3 THEN 1 ELSE 0 END ) AS LEVELS3, sum( CASE WHEN F.DEAL_STATE = 0 THEN 1 ELSE 0 END ) AS DESTS FROM F straight_join ( SELECT VEHICLE_ID FROM GVLK WHERE GROUP_ID IN ( SELECT GROUP_ID FROM GULK WHERE USER_ID = 'ff8080816091b09c0161f9b825750a9a' ) UNION SELECT VEHICLE_ID FROM UVLK WHERE USER_ID = 'ff8080816091b09c0161f9b825750a9a' ) s on F.VEHICLE_ID = s.VEHICLE_ID straight_join DC ON DC.ID = F.CONST_ID straight_join V ON V.ID = F.VEHICLE_ID straight_join AREA ON AREA.ID = V.SYS_DIVISION_ID WHERE DC.ID IS NOT NULL AND V.ID IS NOT NULL AND F.DEAL_STATE = 0 AND ALARM_LEVEL IN ( 1, 2, 3 ) AND date( F.ALARM_TIME ) BETWEEN '2000-01-01' AND '2018-08-14' AND AREA.PATH LIKE CONCAT( ( SELECT ARE.PATH FROM ARE WHERE ARE.ID = '0' ), '%' )

更多内容请关注微信公众号:数据与人
编辑推荐:
- mysql优化 exists/in改写join03-01
- MySQL 关于Table cache设置03-01
- MySQL索引分类,90%的开发都不知道03-01
- 同一台机器上配置两个MySQL实例03-01
- mysql 学习笔记之主从复制 Replication03-01
- MySQL的索引03-01
- 忘记mysql root管理员帐号密码处理方法03-01
- mysqldump同步数据到生产注意事项03-01
相关推荐
-
雷神推出 MIX PRO II 迷你主机:基于 Ultra 200H,玻璃上盖 + ARGB 灯效
2 月 9 日消息,雷神 (THUNDEROBOT) 现已宣布推出基于英
-
制造商 Musnap 推出彩色墨水屏电纸书 Ocean C:支持手写笔、第三方安卓应用
2 月 10 日消息,制造商 Musnap 现已在海外推出一款 Oce
热文推荐
- mysql优化 exists/in改写join
mysql优化 exists/in改写join
26-03-01 - MySQL 关于Table cache设置
MySQL 关于Table cache设置
26-03-01 - MySQL索引分类,90%的开发都不知道
MySQL索引分类,90%的开发都不知道
26-03-01 - mysql 学习笔记之主从复制 Replication
mysql 学习笔记之主从复制 Replication
26-03-01 - MySQL 不同版本默认字符集
MySQL 不同版本默认字符集
26-03-01 - MYSQL_审计日志查看
MYSQL_审计日志查看
26-03-01 - MYSQL_备份恢复手段
MYSQL_备份恢复手段
26-03-01 - MGR8.0支持savepoint
MGR8.0支持savepoint
26-03-01 - Docker二所镜像制作
Docker二所镜像制作
26-03-01 - Mysql MHA部署-02主从复制
Mysql MHA部署-02主从复制
26-03-01
