欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【工作记录】记一次有意思的SQL优化

程序员文章站 2022-04-17 17:33:53
...

      昨日,组长给了一个临时的任务,因为DBA反应,这个批量查询的sql有问题:(Addition:因为公司隐私问题,我将表名做了简化,关键字段做了“*”处理)

SELECT
	ptc.d id,
	pi.* *Id,
	pi.prod_no prodNo,
	pi.prod_name prodName,
	aaa@qq.com f#@e,
	pi.prod_attr isRenewal,
	pi.*bill *Bill,
	ps.id *Id,
	ps.s$e s$e,
	ps.*_period *Period,
	aaa@qq.com aaa@qq.com,
	ps.c!m c&&e,
	ptm.id *Id,
	ptm.term term,
	ptc.m&*_at m*&t,
	ptc.z8_amt z8t,
	ptc.*_amt *Amt,
	prs.renewal_bill_period renewalBillPeriod,
	ptc.irr irr,
	pi.prod_type prodType,
	pi.*attr *Attr,
	pi.prod_type_id prodTypeId,
	ptc.*_id *Id,
	ptc.c#_id #Id,
	ptc.*%¥ %¥,
	ptc.balance_payment_rate balancePaymentRate,
	prs.id *Id,
	ptc.$ $e,
	ptc.min_rate_real minRateReal,
	ptc.* *,
	prs.renewal_way renewalWay,
	ptc.o$rate o#ate,
	ptc.*_term *Term,
	ptc.penalty_rate penaltyRate,
	aaa@qq.com a!it,
	ptc.zero_repay_irr zeroRepayIrr,
	ptc.*_rate *Rate,
	ptc.b^^te b^^e,
	ps.start_time schemeStartTime,
	ps.end_time schemeEndTime
FROM
	t_p_scheme ps
LEFT JOIN t__info pi ON pi.id = ps.prod_id
LEFT JOIN t_*_term_conf_free ptc ON ptc.scheme_id = ps.id
LEFT JOIN t_*_renewal_scheme prs ON prs.id = ptc.renewal_scheme_id
LEFT JOIN t_*_term ptm ON ptm.id = ptc.term_id
WHERE
	pi.prod_type_id = 3
AND pi. STATUS = 2
AND #currentTime# BETWEEN ps.start_time
AND ps.end_time
ORDER BY
	pi.id DESC,
	ps.id DESC,
	ptc.id DESC
     乍一看,没有问题,我以为是查询的逻辑写的不对,或者关联表有问题,经过确认,组长说“ #currentTime# BETWEEN ps.start_timeAND ps.end_time ” 这句有问题,没有见过这样写的。。。。。。


      确实,一般的写法会是这样: tb.currentTime BETWEEN #start_time# AND #end_time#”,这倒过来,就有点点蒙了。上网查了一下,对于如下这种:

<isNotEmpty prepend="AND" property="currentTime" >
  #currentTime# BETWEEN ps.start_time AND ps.end_time
</isNotEmpty>

      最好优化为:

<isNotEmpty prepend="AND" property="currentTime">  
	<![CDATA[  
		 #currentTime# >= ps.start_time and #currentTime# <= ps.end_time        
	]]>  
</isNotEmpty>

      对于ibatis中针对时间、日期的动态查询,使用between A and B写法,一旦A和B有一个不存在,结果就不对了,另外,一般变量放到左侧,虽然此处#currentTime#是传进来的变量,但是他不是字段的属性啊!!!so,改成下面这样,让表的字段,在运算符的左侧,查询性能会更高!!!

<isNotEmpty prepend="AND" property="currentTime">  
	<![CDATA[  
		 ps.start_time <= #currentTime# and ps.end_time >= #currentTime#    
	]]>  
</isNotEmpty>

      做完上述的修改之后,发现在测试3环境下的sql查询速度如下:

      【工作记录】记一次有意思的SQL优化

     我的哥啊!你怎么这么慢!!!

     分析:简单看了下,这次30s的查询,一共查了49571条数据,才不到5w啊,就这么慢!!!看了下,发现其left join的两张表“prs”以及“ptc”的数据量还是比较大的,几十万,而ON关联条件的字段,竟然没有索引,这就一定要加上啦

ALTER TABLE t_*_conf_free ADD INDEX idx_*_id (*_id);
      再去分析,如图,已经快了很多了,但是在线上还是不快。。。
【工作记录】记一次有意思的SQL优化


     因为业务需要,这个接口要查到这些字段不能精简,所以最后只能从业务的角度来思考了:

   【工作记录】记一次有意思的SQL优化

     如图所示,这个查询sql的入参只传入了2个值,status以及currentTime,导致其筛选的时候,需要到茫茫的Data海中进行筛选,组长观察了调用接口的入参,从数据的角度发现了规律

        【工作记录】记一次有意思的SQL优化

     如上,这里一个大的json数组,嵌套了上百个小的json对象,观察数据,发现:
     1.categoryCaseId,每次传入上百个jsonduixiang ,它都是一样的,而且入参的“categoryCaseId”含义本身是一个查询条件,纵使其中categoryCaseId不完全一致,只要虑重一下,就可以当中一个新的查询条件,加入进去,能大大缩小查询的范围啊。

     2.prodBaseAttr,这个字段,和categoryCaseId道理上一样,这么多json对象,都是基本一致的,于是将其作为入参去查询,即使不一样,做虑重。

Params**Form queryForm = new Params**Form();
queryForm.setStatus(Product**Enum.**.getIndex());
queryForm.setCurrentTime(DateUtils.formatDate(new Date()));

HashSet<Integer> categoryCaseIdScope = new HashSet<Integer>();
HashSet<Integer> prodBaseAttrScope = new HashSet<Integer>();

for(Product^^DTO product^^Params : product^^DTOs){ //product^^DTOS就是上图中的JSon数组
        //利用Set去重特性
	categoryCaseIdScope.add(product^^Params.getCategoryCaseId());
	prodBaseAttrScope.add(product^^Params.getProdBaseAttr());
}
queryForm.setCategoryCaseIdScope(StringUtils.join(categoryCaseIdScope.toArray(), ",")); //String类型
queryForm.setProdBaseAttrScope(StringUtils.join(prodBaseAttrScope.toArray(), ","));     //String类型
      这里利用了set集合元素不能相同的原理,做了虑重,queryForm从之前的2个变为4个,缩小查询范围:(多添加的这2个条件,并不会造成查询结果数据不准确的影响,而是之前没有用上)

      在ibais中,in()的写法,切记用“$”,不用“#”,否则只会匹配分隔符前第一个数,比如(3,4,5)只会匹配到3.

      就这样之后,再看查询:

      【工作记录】记一次有意思的SQL优化


      从30s变为了0.04s,这就是优化神奇的地方啊。