exist优化 in mysql_mysql优化 exists/in改写join

本文分析了一个存在性能问题的MySQL查询语句,该语句使用了EXISTS子查询。通过将EXISTS改写为JOIN,显著提高了查询效率,性能提升了2000倍。此外,还分享了其他类似的优化案例,如OR条件优化和IN子查询的JOIN改写,展示了优化查询结构对于提升数据库性能的重要性。
摘要由CSDN通过智能技术生成

问题语句

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' ), '%' )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值