首选要说明的一点,这个代码仅仅是处理数量为千级以上的数据就会造成严重超时,且这个代码非笔者本人所写。但是这个代码相关的一个模块因为该代码造成了严重的生产环境问题,亟需解决。
起初在生产环境调试,日志提示该问题为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、遵守编程规范,保持良好的编程习惯。