回顾一下最近做的一个查询优化 ,该sql是一个页面查询,历史遗留问题就不多说。。当我接手的时候(去掉搜索条件)的sql为
MYSQL 5.7.23
original版本
select a.*, m.fitment_contract_amount
from (select t.id,
t.task_no,
t.apply_time,
t.urgency_level,
t.product_id,
t.operator_id auditUserId,
b.customer_name,
b.customer_id_no,
b.customer_phone,
ifnull(f.merchant_code,cmi.merchant_code) merchant_code,
ifnull(f.merchant_name,cmi.merchant_name) merchant_name,
ifnull(f.store_code,cmi.store_code) store_code,
ifnull(f.store_name,cmi.store_name) store_name,
a.apply_amount,
a.pre_amount,
s.loan_amount,
s.term,
s.loan_time,
(select result from customer_audit_phone_result where task_no=t.task_no and delete_flag=0 order by id desc limit 1) phoneResult,
(select id from customer_audit_result where task_no = a.task_no order by id desc limit 1) audit_result_id
from customer_audit_task t
left join customer_base_info b on t.task_no = b.task_no and b.delete_flag=0
left join customer_fitment_info f on t.task_no = f.task_no and f.delete_flag=0
left join customer_apply_info a on t.task_no = a.task_no and a.delete_flag=0
left join customer_loan_success_info s on t.task_no = s.task_no and s.delete_flag=0
left join customer_merchant_info cmi on t.task_no=cmi.task_no and cmi.delete_flag=0
where t.delete_flag=0) a
left join customer_material_info m on m.customer_audit_result_id = a.audit_result_id and m.delete_flag=0
where find_in_set(a.product_id,"1,6")
order by a.apply_time desc
limit 0,10
可以看出该sql原来的写法是将customer_audit_task 的全表查询出 然后再排序分页(耗时很长,页面接口请求大约5万条记录需要60S),第一步先想到将子查询去掉,省去全表查出的过程,同时关注到audit_result_id的逻辑是取一条,将其通过group by的方式获取
VERSION 1.0
select t.id,
t.task_no,
t.apply_time,
t.urgency_level,
t.product_id,
t.operator_id auditUserId,
b.customer_name,
b.customer_id_no,
b.customer_phone,
ifnull(f.merchant_code,cmi.merchant_code) merchant_code,
ifnull(f.merchant_name,cmi.merchant_name) merchant_name,
ifnull(f.store_code,cmi.store_code) store_code,
ifnull(f.store_name,cmi.store_name) store_name,
a.apply_amount,
a.pre_amount,
s.loan_amount,
s.term,
s.loan_time,
(select result from customer_audit_phone_result where task_no=t.task_no and delete_flag=0 order by id desc limit 1) phoneResult,
car.id audit_result_id,
m.fitment_contract_amount
from customer_audit_task t
left join customer_base_info b on t.task_no = b.task_no and b.delete_flag=0
left join customer_fitment_info f on t.task_no = f.task_no and f.delete_flag=0
left join customer_apply_info a on t.task_no = a.task_no and a.delete_flag=0
left join customer_loan_success_info s on t.task_no = s.task_no and s.delete_flag=0
left join customer_merchant_info cmi on t.task_no=cmi.task_no and cmi.delete_flag=0
left join(select task_no, max(id) id from customer_audit_result where delete_flag = 0 group by task_no )car on car.task_no = t.task_no
left join customer_material_info m on m.customer_audit_result_id = car.id and m.delete_flag=0
where find_in_set(t.product_id,"1,6") and t.delete_flag=0
order by t.apply_time desc
limit 0,10
结果虽然有所改善,但是依然很慢(10S左右),可以看到主要还是查询customer_audit_task表,在关联查询中可以通过延迟关联进行优化
举个例子
正常写法
select id, apply_code, request_str from manage_inner_api_log order by id desc limit 97000,10;
延迟关联优化(通过子查询覆盖索引)
select id, apply_code, request_str from manage_inner_api_log INNER JOIN (select id from manage_inner_api_log order by id desc limit 97000, 10)as limt USING(id);
即在INNER JOIN中通过覆盖索引将符合条件的主键筛选出提高查询效率
VERSION 2.0
select t.id,
t.task_no,
t.apply_time,
t.urgency_level,
t.product_id,
t.operator_id auditUserId,
b.customer_name,
b.customer_id_no,
b.customer_phone,
ifnull(f.merchant_code,cmi.merchant_code) merchant_code,
ifnull(f.merchant_name,cmi.merchant_name) merchant_name,
ifnull(f.store_code,cmi.store_code) store_code,
ifnull(f.store_name,cmi.store_name) store_name,
a.apply_amount,
a.pre_amount,
s.loan_amount,
s.term,
s.loan_time,
(select result from customer_audit_phone_result where task_no=t.task_no and delete_flag=0 order by id desc limit 1) phoneResult,
t.real_status audit_status,
car.id audit_result_id,
m.fitment_contract_amount
from customer_audit_task t
inner join (select id from customer_audit_task
where
find_in_set(product_id,"1,6")
and audit_status not in(100, 103, 1001, 1002)
and delete_flag=0
order by apply_time desc limit 0,10
)a on t.id = a.id
left join customer_base_info b on t.task_no = b.task_no and b.delete_flag=0
left join customer_fitment_info f on t.task_no = f.task_no and f.delete_flag=0
left join customer_apply_info a on t.task_no = a.task_no and a.delete_flag=0
left join customer_loan_success_info s on t.task_no = s.task_no and s.delete_flag=0
left join customer_merchant_info cmi on t.task_no=cmi.task_no and cmi.delete_flag=0
left join(select task_no, max(id) id from customer_audit_result where delete_flag = 0 group by task_no )car on car.task_no = t.task_no
left join customer_material_info m on m.customer_audit_result_id = car.id and m.delete_flag=0
order by t.apply_time desc
优化后的查询时间大概在1S,但是之前都未曾考虑到搜索条件的问题(有关联表的字段需要作为搜索条件),所以将从表的搜索条件放在 INNER JOIN查询中
VERSION 3.0
select t.id, t.task_no, t.apply_time, t.urgency_level, t.product_id, t.operator_id auditUserId, b.customer_name, b.customer_id_no, b.customer_phone, ifnull(f.merchant_code,cmi.merchant_code) merchant_code, ifnull(f.merchant_name,cmi.merchant_name) merchant_name, ifnull(f.store_code,cmi.store_code) store_code, ifnull(f.store_name,cmi.store_name) store_name, a.apply_amount, a.pre_amount, s.loan_amount, s.term, s.loan_time, (select result from customer_audit_phone_result where task_no=t.task_no and delete_flag=0 order by id desc limit 1) phoneResult, t.real_status audit_status, car.id audit_result_id, m.fitment_contract_amount from customer_audit_task t inner join (select si.id from customer_audit_task si left join customer_base_info b on si.task_no = b.task_no and b.delete_flag=0 left join customer_apply_info a on si.task_no = a.task_no and a.delete_flag=0 left join customer_loan_success_info s on s.task_no = si.task_no and s.delete_flag=0 left join (select ifnull(f.merchant_code,cmi.merchant_code) merchant_code, ifnull(f.merchant_name,cmi.merchant_name) merchant_name, ifnull(f.store_code,cmi.store_code) store_code, f.task_no, ifnull(f.store_name,cmi.store_name) store_name from customer_fitment_info f left join customer_merchant_info cmi on f.task_no = cmi.task_no and f.delete_flag=0 and cmi.delete_flag=0 )ms on ms.task_no = si.task_no where find_in_set(si.product_id,#{productCodes}) and si.delete_flag=0 and si.real_status not in(100, 103, 1001, 1002) <if test="custNameLike !=null and custNameLike != ''"> and b.customer_name = #{custNameLike} </if> <if test="mobileLike !=null and mobileLike != ''"> and b.customer_phone = #{mobileLike} </if> <if test="productLike !=null and productLike != ''"> and si.product_id = #{productLike} </if> <!-- 资料审核中 --> <if test="auditStatusLike == 11"> and (si.real_status = 101 or si.real_status = 104) </if> <!-- 审批拒绝:资料审核拒绝或电核拒绝 --> <if test="auditStatusLike == 16"> and (si.real_status = 106 or si.real_status = 203) </if> <if test="auditStatusLike != 11 and auditStatusLike != 16 and auditStatusLike !=null and auditStatusLike != ''"> and si.real_status = #{auditStatusLike} </if> <if test="applyTimeStartLike !=null and applyTimeStartLike != ''"> <![CDATA[ and DATE_FORMAT(si.apply_time, '%Y-%m-%d') >= #{applyTimeStartLike} ]]> </if> <if test="applyTimeEndLike !=null and applyTimeEndLike != ''"> <![CDATA[ and DATE_FORMAT(si.apply_time, '%Y-%m-%d') <= #{applyTimeEndLike} ]]> </if> <if test="userId !=null and userId != ''"> and si.operator_id = #{userId} </if> <if test="loanTimeStartLike !=null and loanTimeStartLike != ''"> <![CDATA[ and DATE_FORMAT(s.loan_time, '%Y-%m-%d') >= #{loanTimeStartLike} ]]> </if> <if test="loanTimeEndLike !=null and loanTimeEndLike != ''"> <![CDATA[ and DATE_FORMAT(s.loan_time, '%Y-%m-%d') <= #{loanTimeEndLike} ]]> </if> <if test="merchantLike !=null and merchantLike != ''"> and ms.merchant_code = #{merchantLike} </if> <if test="storeLike !=null and storeLike != ''"> and ms.store_code = #{storeLike} </if> order by si.apply_time desc limit 0,10 )a on t.id = a.id left join customer_base_info b on t.task_no = b.task_no and b.delete_flag=0 left join customer_apply_info a on t.task_no = a.task_no and a.delete_flag=0 left join customer_loan_success_info s on t.task_no = s.task_no and s.delete_flag=0 left join(select task_no, max(id) id from customer_audit_result where delete_flag = 0 group by task_no )car on car.task_no = t.task_no left join customer_material_info m on m.customer_audit_result_id = car.id and m.delete_flag=0 left join customer_fitment_info f on t.task_no = f.task_no and f.delete_flag=0 left join customer_merchant_info cmi on t.task_no=cmi.task_no and cmi.delete_flag=0 order by t.apply_time desc
通过EXPLAIN发现查询主要耗时集中在INNER JOIN子查询之中,主要耗时在ORDER BY 操作上(如果去掉耗时在0.01s左右得出),所以通过索引来优化LEFT JOIN中的排序(去除USING filesort部分)
SELECT
si.id
FROM
customer_audit_task si
LEFT JOIN customer_base_info b ON si.task_no = b.task_no
AND b.delete_flag = 0
LEFT JOIN customer_apply_info a ON si.task_no = a.task_no
AND a.delete_flag = 0
LEFT JOIN customer_loan_success_info s ON s.task_no = si.task_no
AND s.delete_flag = 0
LEFT JOIN (
SELECT
ifnull(
f.merchant_code,
cmi.merchant_code
) merchant_code,
ifnull(
f.merchant_name,
cmi.merchant_name
) merchant_name,
ifnull(
f.store_code,
cmi.store_code
) store_code,
f.task_no,
ifnull(
f.store_name,
cmi.store_name
) store_name
FROM
customer_fitment_info f
LEFT JOIN customer_merchant_info cmi ON f.task_no = cmi.task_no
AND f.delete_flag = 0
AND cmi.delete_flag = 0
) ms ON ms.task_no = si.task_no
WHERE
find_in_set(si.product_id, '1,6')
and real_status not in(100, 103, 1001, 1002)
AND si.delete_flag = 0
ORDER BY
si.apply_time DESC
LIMIT 0,10;
对于主表增加查询条件和排序字段索引
alter table customer_audit_task add index IDX_AUDIT_TASK_DELETEFLAG_APPLYTIME (delete_flag, apply_time) ;
对于从表增加关于task_no字段的索引(只列出一个从表,其他从表同理)
alter table customer_loan_success_info add index IDX_LOAN_SUCCESS_INFO_TASKNO (task_no) ;
这次sql优化大概就是这样,从原来5万条记录的50S变成最后的0.01S,应该还有优化的空间(例如INNER JOIN中关联从表查过一次,外部查询时又left join了从表,这部分应该还有优化空间,大概还可以提高10%-20%(粗略统计)),主要的花费时间在如何在LEFT JOIN查询中建议合适的索引消除USING filesort,希望可以给看到的同学一点帮助。