【屎山代码雕花日记】处理垃圾SQL,优化从100s+到 1.5s内。

首选要说明的一点,这个代码仅仅是处理数量为千级以上的数据就会造成严重超时,且这个代码非笔者本人所写。但是这个代码相关的一个模块因为该代码造成了严重的生产环境问题,亟需解决。

起初在生产环境调试,日志提示该问题为Open Feign调用异常,所以一直对feign做debug,而且日志也能正常输出该语句的执行记录。导致一直没法准确定位到问题,但是设置feign超时时间为60秒后,仍不能解决问题后,俺把真正的问题定位在SQL代码层面。

来欣赏这坨shit,新建查询跑起来之后,就能闻到代码有股“坏味道”。(代码有点长,不想看可以直接跳过,勿展开)

-- ps:部分mybatis语法已注释掉,以下是shit 代码
select
		     DISTINCT t.task_id,
		     t.task_name,
		     t.content,
		     t.province,
		     t.city,
		     t.area,
		     t.address,
		     t.latitude,
		     t.longitude,
		     t.task_status,
		     t.begin_time,
		     t.end_time,
			 t.dept_ids,
			 t.deptName,
			 t.is_consultation,
			 t.consultation_status,
		     t.consultation_id,
		     t.user_id,
		     t.create_time
		from (
				select
				      t.task_id,
				      t.task_name,
				      t.content,
		              t.province,
		              t.city,
				      t.area,
				      t.address,
				      t.latitude,
				      t.longitude,
				      t.task_status,
				      t.begin_time,
				      t.end_time,
					  t.dept_ids,
						(
						   select GROUP_CONCAT(`name` SEPARATOR '、') deptName
								from sys_dept
								where  FIND_IN_SET(dept_id,t.dept_ids)
						) deptName,
						CASE
						WHEN 1=1 THEN
							  1
						END is_consultation,
						CASE
						WHEN c.status is not null THEN
							  c.status
						else
						0
						END consultation_status,
		                IFNULL(c.consultation_id,0) consultation_id,
		                t.user_id,
		                t.create_time
				from sys_task t
				LEFT JOIN
				(
					select
					     c.consultation_id,
					     c.status,
					     c.task_id
					from sys_task t
					LEFT JOIN consultation c on t.task_id=c.task_id
					where t.del_flag=0
					and c.del_flag=0
					and t.task_type=1
					and t.user_id= 1  #{userId}
				)c on t.task_id=c.task_id
				where t.del_flag=0
				and t.task_type=1
				and t.user_id=  1 #{userId}
				UNION
				SELECT
				      t.task_id,
				      t.task_name,
				      t.content,
		              t.province,
		              t.city,
				      t.area,
				      t.address,
				      t.latitude,
				      t.longitude,
				      t.task_status,
				      t.begin_time,
				      t.end_time,
				      t.dept_ids,
					  (
				       select GROUP_CONCAT(`name` SEPARATOR '、') deptName
					   from sys_dept
					   where  FIND_IN_SET(dept_id,t.dept_ids)
				       ) deptName,
						CASE
						WHEN 1=1 THEN
								2
						END is_consultation,
						CASE
						WHEN c.status is not null THEN
							  c.status
						else
						0
						END consultation_status,
		                IFNULL(c.consultation_id,0) consultation_id,
		                t.user_id,
		                t.create_time
					FROM sys_task AS t
					JOIN sys_user_task AS ut ON t.task_id = ut.task_id
					JOIN sys_user AS u ON ut.user_id = u.user_id
					LEFT JOIN
					(
						select
						     c.consultation_id,
						     c.status,
						     c.task_id
						from sys_task t
						LEFT JOIN consultation c on t.task_id=c.task_id
						LEFT JOIN consultation_user cu on c.consultation_id=cu.consultation_id
						where t.del_flag=0
						and c.del_flag=0
						and cu.del_flag=0
						and t.task_type=1
						and cu.user_id= 1 #{userId}
					)c on t.task_id=c.task_id
					where t.del_flag=0
					and ut.del_flag=0
					and u.del_flag=0
					and t.task_type=1
		            and ut.user_id= 1 #{userId}
				-- <if test="deptList!=null and deptList.size>0">
				UNION
				select
				     t.task_id,
				     t.task_name,
				     t.content,
					 t.province,
					 t.city,
				     t.area,
				     t.address,
				     t.latitude,
				     t.longitude,
				     t.task_status,
				     t.begin_time,
				     t.end_time,
				     t.dept_ids,
					(
					   select GROUP_CONCAT(`name` SEPARATOR '、') deptName
							from sys_dept
							where  FIND_IN_SET(dept_id,t.dept_ids)
					) deptName,
					CASE
					 WHEN tt.task_id is not null THEN
							 2
					 else
					0
					END is_consultation,
							CASE
					WHEN c.status is not null THEN
						  c.status
					else
					0
					END consultation_status,
					IFNULL(c.consultation_id,0) consultation_id,
					t.user_id,
					t.create_time
				from sys_task t
				LEFT JOIN sys_user u on t.user_id=u.user_id
				LEFT JOIN
				(
					select
					      c.consultation_id,
					      c.status,
					      c.task_id
					from sys_task t
					LEFT JOIN consultation c on t.task_id=c.task_id
					where t.del_flag=0
					and c.del_flag=0
					and t.task_type=1
					and t.user_id= 1 #{userId}
				)c on t.task_id=c.task_id
				LEFT JOIN
				(
					SELECT
						 tt.task_id,
						 tt.task_name,
						 u.username,
						 ut.user_task_id
					FROM sys_task AS tt
					JOIN sys_user_task AS ut ON tt.task_id = ut.task_id
					JOIN sys_user AS u ON ut.user_id = u.user_id
					WHERE  tt.del_flag=0
					and ut.del_flag=0
					and u.del_flag=0
					and tt.task_type=1
					and u.user_id = 1 #{userId}
				) tt on t.task_id=tt.task_id
				where t.del_flag=0
				and u.del_flag=0
				and t.task_type=1
				and u.dept_id in
-- 				<foreach collection="deptList" open="(" separator="," close=")" item="item">
-- 						#{item}
-- 				</foreach>
				(...)   -- 这里是传入一大堆参数,数据量级一旦扩大势必会导致性能低下,要注意
				and t.task_id not in
				(
					select
					      t.task_id
					from sys_task t
					LEFT JOIN
					(
						select
						      c.consultation_id,
						      c.status,
						      c.task_id
						from sys_task t
						LEFT JOIN consultation c on t.task_id=c.task_id
						where t.del_flag=0
						and c.del_flag=0
						and t.task_type=1
						and t.user_id=1 #{userId}
					)c on t.task_id=c.task_id
					where t.del_flag=0
					and t.task_type=1
					and t.user_id= 1 #{userId}
					UNION
					SELECT
					      t.task_id
					FROM sys_task AS t
					JOIN sys_user_task AS ut ON t.task_id = ut.task_id
					JOIN sys_user AS u ON ut.user_id = u.user_id
					LEFT JOIN
					(
						select
							c.consultation_id,
							c.status,
							c.task_id
						from sys_task t
						LEFT JOIN consultation c on t.task_id=c.task_id
						LEFT JOIN consultation_user cu on c.consultation_id=cu.consultation_id
						where t.del_flag=0
						and c.del_flag=0
						and cu.del_flag=0
						and t.task_type=1
						and cu.user_id= 1 #{userId}
					)c on t.task_id=c.task_id
					where t.del_flag=0
					and ut.del_flag=0
					and u.del_flag=0
					and t.task_type=1
					and ut.user_id= 1 #{userId}
				)
				-- </if>
		   )t
		where 1=1

整个SQL查询执行结果耗费时间居然要达到一百多秒甚至不止:
在这里插入图片描述

我们再 56 - 71行这里看到有以下代码:

LEFT JOIN
(
    select
         c.consultation_id,
         c.status,
         c.task_id
    from sys_task t
    LEFT JOIN consultation c on t.task_id=c.task_id
    where t.del_flag=0
    and c.del_flag=0
    and t.task_type=1
    and t.user_id= 1
)c on t.task_id=c.task_id
where t.del_flag=0 and t.task_type=1 and t.user_id=  1

这里在前面已经通过嵌套查询了一层情况下,还要进行一次联表查询,再嵌套一层联表去查。这里完全就是多此一举的操作,观察后完全可以把以上这个代码简化成一个联表查询而不需要去嵌套。为什么呢?注意看这里只是联表consultation,而上方的sys_task已经做过查询,完全可以做联表。

所以直接修改成以下:

LEFT JOIN consultation c on t.task_id=c.task_id and c.del_flag=0 and t.task_type=1 and t.user_id= 1 

然后我们执行一遍SQL (23 - 71行)。可以看到这个查询就需要花费70多秒的时间:

在这里插入图片描述

我们再执行以下优化好的代码:

select
				      t.task_id,
				      t.task_name,
				      ... -- 省略
						 ( select GROUP_CONCAT(`name` SEPARATOR '、') deptName from sys_dept where  FIND_IN_SET(dept_id,t.dept_ids) ) deptName,
						CASE WHEN 1=1 THEN 1 END is_consultation,
						CASE WHEN c.status is not null THEN c.status else 0 END consultation_status,
						IFNULL(c.consultation_id,0) consultation_id,
						t.user_id,
						t.create_time
				from sys_task t
				-- 优化后
				LEFT JOIN consultation c on t.task_id=c.task_id and c.del_flag=0 and t.task_type=1 and t.user_id= 1 
				where t.del_flag=0
				and t.task_type=1
				and t.user_id= 1 

在这里插入图片描述

可以看到直接把运行速度干到 秒内,证明我们的思路是对的,就是排查掉SQL内的多层嵌套联表,顺着这个思路把SQL内存在同样操作的代码优化掉。 还有很重要的一点就是 注意优化后的语句结果需要注意是否和原本结果吻合,屎山代码难以改动其中之一的一点在于改完之后结果也需要对得上。

-- 108 到 127 行原来的代码
LEFT JOIN
(
    select
         c.consultation_id,
         c.status,
         c.task_id
    from sys_task t
    LEFT JOIN consultation c on t.task_id=c.task_id
    LEFT JOIN consultation_user cu on c.consultation_id=cu.consultation_id
    where t.del_flag=0
    and c.del_flag=0
    and cu.del_flag=0
    and t.task_type=1
    and cu.user_id= 1
)c on t.task_id=c.task_id
where t.del_flag=0and ut.del_flag=0 and u.del_flag=0 and t.task_type=1 and ut.user_id= 1

-- 优化之后的代码
LEFT JOIN  consultation c on t.task_id = c.task_id and c.del_flag=0 
LEFT JOIN  consultation_user cu on c.consultation_id = cu.consultation_id and cu.del_flag=0 and cu.user_id= 1
where t.del_flag=0 and ut.del_flag=0 and u.del_flag=0 and t.task_type=1 and ut.user_id= 1 

总结:

1、不要进行过多的联表嵌套查询;

2、在有过多业务筛选条件的情况之下,不要把条件塞在一条SQL内运行;

3、很多代码当下不出问题可能是因为数据库数据样本不够多,这点需要重视起来;

4、遵守编程规范,保持良好的编程习惯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值