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

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

SELECT
	ptc.d id,
	pi.* *Id,
	pi.prod_no prodNo,
	pi.prod_name prodName,
	pi.f!@e f#@e,
	pi.prod_attr isRenewal,
	pi.*bill *Bill,
	ps.id *Id,
	ps.s$e s$e,
	ps.*_period *Period,
	ps.c@d c@d,
	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,
	ptc.a@it 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查询速度如下:

      

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

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

ALTER TABLE t_*_conf_free ADD INDEX idx_*_id (*_id);
      再去分析,如图,已经快了很多了,但是在线上还是不快。。。


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

   

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

        

     如上,这里一个大的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.

      就这样之后,再看查询:

      


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

猜你喜欢

转载自blog.csdn.net/u013047584/article/details/80502454