优化案例3:高频多union表关联穿插拼接完成计数
- 1. 引入
- 2. 解决过程
- 2.1 瓶颈定位思想
- 2.2 定位解决过程
- 2.2.1 子部分代码1
- 2.2.2 子部分代码2
- 2.3 优化结果
- 3. 优化心得
DM技术交流QQ群:940124259
1. 引入
已经很久没有发布文章,快沉底!赶紧拿近期优化的案例分享一下,本次分享的主要知识是快速定位SQL瓶颈点以及SQL分析函数的等价改写。
本例SQL代码比较长(几百行),特征是大量的多表外关联,穿插几处union all拼接结果集(集合操作),其中一些不重要的部分会一笔带过,着重讲解关键点。
2. 解决过程
2.1 瓶颈定位思想
遇到特别长的SQL,想快速找出瓶颈位置,就要充分掌握拆分思想,即一段一段拆分出符合语法语义的子SQL尝试执行,找出性能最差的子SQL,再花精力攻克它。
比如本案例的特征union all多处拼接,可以以union all作为划分界限,逐步跟踪子语句性能。
-- 经脱敏处理的完整SQL代码
select count(1)
from ( SELECT XMXX.IS_LOW_RISK,
XMXX.ID,
XMXX.REPORT_TYPE,
XMXX.TYPE,
XMXX.TYPE AS STAGE,
XMXX.STATUS,
XMXX.CODE,
XMXX.NAME,
XMXX.OLD_ID,
XMXX.CUR_DEPARTMENT_GUID,
XMXX.CUR_OFFICE_GUID,
XMXX.CUR_USER_ID,
XMXX.ATTEND_STATUS,
XMXX.APPLY_TIME,
XMXX.APPLY_TIME as update_time,
PRO_COUNTY.COUNTY,
PRO_JSXZ.JSXZ,
XMXX.SYS_FLAG,
XMXX.VERSION,
XMXX.TOTAL_INVEST,
DE.department_fullname AS approve_dept_name,
DE.parent_guid,
DE.store_level,
OWN.TYPE OWN_TYPE,
XMXX.LEGAL,
OWN.COMPANY_NAME COMPANY_NAME,
'0' as xmflag ,
XMXX.is_ppp_item
FROM SHB_CQCQ XMXX
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
LEFT JOIN( SELECT ITEM_FULL_VALUE COUNTY,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1008') PRO_COUNTY
ON PRO_COUNTY.ITEM_KEY = XMXX.COUNTY
LEFT JOIN( SELECT ITEM_FULL_VALUE JSXZ,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1009') PRO_JSXZ
ON PRO_JSXZ.ITEM_KEY = XMXX.JSXZ
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN (SELECT MAX(CUR_DEAL_TIME) AS CUR_DEAL_TIME,
RELATE_BILL_ID
FROM SHB_TO_DEAL_LOG
WHERE TYPE = 'SHB-OPT-08'
GROUP BY RELATE_BILL_ID) TD
ON TD.RELATE_BILL_ID = XMXX.ID
WHERE XMXX.IS_DEL = '0'
AND (XMXX.REPORT_TYPE NOT IN('3',
'2')
OR (XMXX.REPORT_TYPE IN('3',
'2')
and (XMXX.ATTEND_STATUS <>50
or XMXX.ATTEND_STATUS IS NULL)))
AND IS_SHOW = 0
AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002')
AND XMXX.status not in ('10')
AND ( XMXX.NAME LIKE '%重庆重庆%'
OR XMXX.LEGAL LIKE '%重庆重庆%'
OR XMXX.CODE LIKE '%重庆重庆%')
Union All
SELECT '0' as IS_LOW_RISK,
XMXX.ID,
XMXX.REPORT_TYPE,
XMXX.TYPE,
XMXX.TYPE AS STAGE,
XMXX.STATUS,
XMXX.CODE,
XMXX.NAME,
XMXX.OLD_ID,
XMXX.CUR_DEPARTMENT_GUID,
XMXX.CUR_OFFICE_GUID,
XMXX.CUR_USER_ID,
XMXX.ATTEND_STATUS,
XMXX.APPLY_TIME,
XMXX.APPLY_TIME as update_time,
PRO_COUNTY.COUNTY,
PRO_JSXZ.JSXZ,
XMXX.SYS_FLAG,
XMXX.VERSION,
XMXX.TOTAL_INVEST,
DE.department_fullname AS approve_dept_name,
DE.parent_guid,
DE.store_level,
OWN.TYPE OWN_TYPE,
XMXX.LEGAL,
OWN.COMPANY_NAME COMPANY_NAME,
'1' as xmflag,
'0' as is_ppp_item
FROM SHB_CQCQ_TAGGING XMXX
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
LEFT JOIN( SELECT ITEM_FULL_VALUE COUNTY,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1008') PRO_COUNTY
ON PRO_COUNTY.ITEM_KEY = XMXX.COUNTY
LEFT JOIN( SELECT ITEM_FULL_VALUE JSXZ,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1009') PRO_JSXZ
ON PRO_JSXZ.ITEM_KEY = XMXX.JSXZ
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN (SELECT MAX(CUR_DEAL_TIME) AS CUR_DEAL_TIME,
RELATE_BILL_ID
FROM SHB_TO_DEAL_LOG
WHERE TYPE = 'SHB-OPT-08'
GROUP BY RELATE_BILL_ID) TD
ON TD.RELATE_BILL_ID = XMXX.ID
WHERE XMXX.IS_DEL = '0'
AND (XMXX.REPORT_TYPE NOT IN('3',
'2')
OR (XMXX.REPORT_TYPE IN('3',
'2')
and (XMXX.ATTEND_STATUS <>50
or XMXX.ATTEND_STATUS IS NULL)))
AND IS_SHOW = 0
AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002')
AND XMXX.status not in ('10')
AND ( XMXX.NAME LIKE '%重庆重庆%'
OR XMXX.LEGAL LIKE '%重庆重庆%'
OR XMXX.CODE LIKE '%重庆重庆%')
Union All
SELECT XMXX.IS_LOW_RISK,
px.ID,
XMXX.REPORT_TYPE,
XMXX.TYPE,
XMXX.TYPE AS STAGE,
XMXX.STATUS,
XMXX.CODE,
XMXX.NAME,
px.SHB_CQCQ_id as OLD_ID,
XMXX.CUR_DEPARTMENT_GUID,
XMXX.CUR_OFFICE_GUID,
XMXX.CUR_USER_ID,
XMXX.ATTEND_STATUS,
XMXX.APPLY_TIME,
XMXX.update_time,
PRO_COUNTY.COUNTY,
PRO_JSXZ.JSXZ,
XMXX.SYS_FLAG,
XMXX.VERSION,
XMXX.TOTAL_INVEST,
DE.department_fullname AS approve_dept_name,
DE.parent_guid,
DE.store_level,
OWN.TYPE OWN_TYPE,
XMXX.LEGAL,
px.ppp_item_stage COMPANY_NAME,
'2' as xmflag ,
XMXX.is_ppp_item
from SHB_CQCQ_ppp px
JOIN SHB_CQCQ XMXX
ON px.SHB_CQCQ_id=XMXX.id
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
LEFT JOIN( SELECT ITEM_FULL_VALUE COUNTY,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1008') PRO_COUNTY
ON PRO_COUNTY.ITEM_KEY = XMXX.COUNTY
LEFT JOIN( SELECT ITEM_FULL_VALUE JSXZ,
ITEM_KEY
FROM DICTIONARY_ITEMS
WHERE GROUP_NO = '1009') PRO_JSXZ
ON PRO_JSXZ.ITEM_KEY = XMXX.JSXZ
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
where 1=1
AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002')
AND ( XMXX.NAME LIKE '%重庆重庆%'
OR XMXX.LEGAL LIKE '%重庆重庆%'
OR XMXX.CODE LIKE '%重庆重庆%')
AND px.pp_status in('33',
'50',
'40',
'12',
'41',
'71')
Union All
select E.*
from ( select distinct '0' AS IS_LOW_RISK,
F.id,
to_number('999') AS report_type,
F.pro_type as type,
F.apply_stage as stage,
to_number(F.status) as status,
F.gj_pro_code AS code,
F.gj_pro_name AS name,
'' as old_id,
'CHONGQI-3' as CUR_DEPARTMENT_GUID,
'CHONGQI-3' as CUR_OFFICE_GUID,
'' as CUR_USER_ID,
F.matters as attend_status,
F.update_time as apply_time,
F.update_time,
pro_county.county,
pro_jsxz.item_full_value AS jsxz,
to_number('999') AS sys_flag,
to_number('0') AS version,
F.gj_investment_total as TOTAL_INVEST,
'顶呱呱' as approve_dept_name,
'' as parent_guid,
to_number('000') as store_level,
own.type AS own_type,
F.gj_pro_org as legal,
own.company_name,
F.matters as xmflag,
'' as is_ppp_item
from ( select xmxx.total_invest as gj_investment_total,
pro.build_properties,
pro.gj_pro_org,
C.matters,
C.apply_stage,
C.pro_type,
C.id,
xmxx.code as gj_pro_code,
xmxx.name as gj_pro_name,
C.create_user_id,
pro.county,
C.apply_matters,
xmxx.status,
C.update_time
from ( select 'inout_project' as matters,
A.apply_stage,
A.pro_type,
A.id,
A.project_id,
A.update_time,
A.create_user_id,
A.apply_matters
from service_inout_project A
where A.is_del = '0'
and A.status in('01',
'02',
'11',
'13',
'21',
'23',
'12',
'22',
'15',
'16',
'17',
'18',
'25',
'26',
'27',
'28',
'14',
'24')
union all
select 'free_project' as matters,
B.apply_stage,
B.pro_type,
B.id,
B.project_id,
B.update_time,
B.create_user_id,
B.apply_matters
from service_free_project B
where B.is_del = '0'
and B.status in('01',
'02',
'11',
'13',
'21',
'23',
'12',
'22',
'15',
'16',
'17',
'18',
'25',
'26',
'27',
'28',
'14',
'24')) C
left join cq_base_pro pro
on pro.id = C.project_id
left join SHB_CQCQ xmxx
on xmxx.cq_base_pro_id = pro.id
and xmxx.is_del = '0'
union all
select info.total_money as gj_investment_total,
'' as build_properties,
'' as gj_pro_org,
E.matters,
E.apply_stage,
E.pro_type,
E.id,
info.project_code as gj_pro_code,
info.project_name as gj_pro_name,
E.create_user_id,
info.place_code as county,
E.apply_matters,
'' as status,
E.update_time
from ( select 'free_project' as matters,
D.apply_stage,
D.pro_type,
D.id,
D.project_id,
D.update_time,
D.create_user_id,
D.apply_matters,
D.status
from service_free_project D
where D.is_del = '0'
and D.status in('01',
'02',
'11',
'13',
'21',
'23',
'12',
'22',
'15',
'16',
'17',
'18',
'25',
'26',
'27',
'28',
'14',
'24')) E
left join region_apply_pro_info info
on E.project_id = info.id ) F
left join shb_user_info own
on own.user_id = F.create_user_id
left join (select item_full_value county,
item_key
from dictionary_items
where group_no = '1008') pro_county
on pro_county.item_key = F.county
left join ( select max(cur_deal_time) as cur_deal,
relate_bill_id
from shb_to_deal_log
where cur_department_guid = 'CHONGQI-31'
group by relate_bill_id ) td
on td.relate_bill_id = F.id
left join (select item_full_value,
item_key
from dictionary_items
where group_no = '1009') pro_jsxz
on pro_jsxz.item_key = F.build_properties
where F.gj_pro_code is not null
union all
select distinct '' as is_low_risk ,
ch.id ,
xmxx.report_type ,
'8' as type ,
ch.apply_stage as stage ,
ch.status ,
ch.pro_code as code ,
ch.pro_name as name ,
xmxx.old_id ,
XMXX.CUR_DEPARTMENT_GUID ,
XMXX.CUR_OFFICE_GUID ,
XMXX.CUR_USER_ID ,
XMXX.ATTEND_STATUS ,
XMXX.APPLY_TIME,
ch.update_time ,
pro_county.county ,
PRO_JSXZ.item_full_value as JSXZ ,
XMXX.SYS_FLAG ,
XMXX.VERSION ,
XMXX.TOTAL_INVEST,
de.department_fullname as approve_dept_name,
de.parent_guid ,
de.store_level ,
own.type as own_type ,
xmxx.legal ,
OWN.COMPANY_NAME COMPANY_NAME ,
'charter' as xmflag ,
xmxx.is_ppp_item
from charter_project ch
left join cq_base_pro b
on b.id = ch.cq_base_id
left join SHB_CQCQ xmxx
on xmxx.cq_base_pro_id = ch.cq_base_id
and xmxx.is_del = '0'
left join ( select item_full_value county,
item_key
from dictionary_items
where group_no = '1008' ) pro_county
on pro_county.item_key = xmxx.county
left join ( select item_full_value,
item_key
from dictionary_items
where group_no = '1009' ) pro_jsxz
on pro_jsxz.item_key = xmxx.jsxz
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
WHERE XMXX.COUNTY in ('CQ50011001',
'CQ50011002')
or XMXX.COUNTY IS NULL
union all
SELECT XMXX.IS_LOW_RISK ,
en.id ,
XMXX.REPORT_TYPE ,
XMXX.TYPE ,
en.apply_stage AS STAGE ,
en.STATUS ,
XMXX.CODE ,
XMXX.NAME ,
XMXX.OLD_ID ,
XMXX.CUR_DEPARTMENT_GUID ,
XMXX.CUR_OFFICE_GUID ,
XMXX.CUR_USER_ID ,
XMXX.ATTEND_STATUS ,
XMXX.APPLY_TIME ,
en.update_time ,
PRO_COUNTY.COUNTY ,
PRO_JSXZ.item_full_value as JSXZ ,
XMXX.SYS_FLAG ,
XMXX.VERSION ,
XMXX.TOTAL_INVEST ,
case when en.reply_level = '0' then '教室' else DE.department_fullname end AS approve_dept_name,
DE.parent_guid ,
DE.store_level ,
OWN.TYPE OWN_TYPE ,
XMXX.LEGAL ,
OWN.COMPANY_NAME COMPANY_NAME ,
'energy' as xmflag ,
XMXX.is_ppp_item
from energy_audit en
left join ( select x.*
from SHB_CQCQ x
inner join ( select code,
max(create_time) utime
from SHB_CQCQ
where (status = '40' or type = '5')
group by code ) xx
on xx.code = x.code and x.create_time = xx.utime ) xmxx
on xmxx.code = en.project_code
left join ( select item_full_value county,
item_key
from dictionary_items
where group_no = '1008' ) pro_county
on pro_county.item_key = xmxx.county
left join ( select item_full_value,
item_key
from dictionary_items
where group_no = '1009' ) pro_jsxz
on pro_jsxz.item_key = xmxx.jsxz
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
where 1=1 AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002') ) E
where 1=1 AND ( E.name LIKE '%重庆重庆%' OR E.code LIKE '%重庆重庆%') ) TOTAL
2.2 定位解决过程
针对union all分支一个一个从上到下尝试执行,记录耗时,很快地追踪到有两处位置性能特别差。
2.2.1 子部分代码1
-- 脱敏SQL
select xmxx.total_invest as gj_investment_total,
pro.build_properties,
pro.gj_pro_org,
C.matters,
C.apply_stage,
C.pro_type,
C.id,
xmxx.code as gj_pro_code,
xmxx.name as gj_pro_name,
C.create_user_id,
pro.county,
C.apply_matters,
xmxx.status,
C.update_time
from ( select 'inout_project' as matters,
A.apply_stage,
A.pro_type,
A.id,
A.project_id,
A.update_time,
A.create_user_id,
A.apply_matters
from service_inout_project A
where A.is_del = '0'
and A.status in('01',
'02',
'11',
'13',
'21',
'23',
'12',
'22',
'15',
'16',
'17',
'18',
'25',
'26',
'27',
'28',
'14',
'24')
union all
select 'free_project' as matters,
B.apply_stage,
B.pro_type,
B.id,
B.project_id,
B.update_time,
B.create_user_id,
B.apply_matters
from service_free_project B
where B.is_del = '0'
and B.status in('01',
'02',
'11',
'13',
'21',
'23',
'12',
'22',
'15',
'16',
'17',
'18',
'25',
'26',
'27',
'28',
'14',
'24')) C
left join cq_base_pro pro
on pro.id = C.project_id
left join SHB_CQCQ xmxx
on xmxx.cq_base_pro_id = pro.id
and xmxx.is_del = '0'
代码1执行计划:一看执行计划步骤20就怀疑有问题,SHB_CQCQ AS
XMXX表大小567MB,表记录1123476,整体查询结果集返回2条。检查步骤4-18真实返回少,步骤19-20应该补建索引,根据步骤3计划得知关联字段XMXX.CQ_BASE_PRO_ID,则对它建一个单列索引。
-- 优化前执行计划
1 #NSET2: [79, 2, 648]
2 #PRJT2: [79, 2, 648]; exp_num(14), is_atom(FALSE)
3 #HASH LEFT JOIN2: [79, 2, 648]; key_num(1), partition_keys_num(0) KEY(PRO.ID=XMXX.CQ_BASE_PRO_ID)
4 #INDEX JOIN LEFT JOIN2: [2, 2, 396]
5 #PRJT2: [1, 2, 396]; exp_num(8), is_atom(FALSE)
6 #UNION ALL: [1, 2, 396]
7 #PRJT2: [0, 1, 396]; exp_num(8), is_atom(FALSE)
8 #HASH LEFT SEMI JOIN2: [0, 1, 396]; KEY_NUM(1); KEY(A.STATUS=DMTEMPVIEW_17674615.colname) KEY_NULL_EQU(0)
9 #SLCT2: [0, 1, 396]; A.IS_DEL = '0'
10 #CSCN2: [0, 7, 396]; INDEX33559165(SERVICE_INOUT_PROJECT as A)
11 #CONST VALUE LIST: [0, 18, 48]; row_num(18), col_num(1),
12 #PRJT2: [0, 1, 396]; exp_num(8), is_atom(FALSE)
13 #HASH LEFT SEMI JOIN2: [0, 1, 396]; KEY_NUM(1); KEY(B.STATUS=DMTEMPVIEW_17674616.colname) KEY_NULL_EQU(0)
14 #SLCT2: [0, 1, 396]; B.IS_DEL = '0'
15 #CSCN2: [0, 1, 396]; INDEX33559166(SERVICE_FREE_PROJECT as B)
16 #CONST VALUE LIST: [0, 18, 48]; row_num(18), col_num(1),
17 #BLKUP2: [0, 1, 0]; INDEX33559875(PRO)
18 #SSEK2: [0, 1, 0]; scan_type(ASC), INDEX33559875(CQ_BASE_PRO as PRO), scan_range[C.PROJECT_ID,C.PROJECT_ID]
19 #SLCT2: [51, 256955, 252]; XMXX.IS_DEL = '0'
20 #CSCN2: [51, 311391, 252]; INDEX33559137(SHB_CQCQ as XMXX) ************ 性能瓶颈点 ************
处理方法 :补建索引
create index idxfs_shb_CQCQ_cq_base_pro_id on cq.shb_CQCQ(cq_base_pro_id);
-- 优化后执行计划
1 #NSET2: [2, 9, 396]
2 #PRJT2: [2, 9, 396]; exp_num(14), is_atom(FALSE)
3 #INDEX JOIN LEFT JOIN2: [2, 9, 396] join condition(XMXX.IS_DEL = '0')
4 #INDEX JOIN LEFT JOIN2: [2, 2, 396]
5 #PRJT2: [1, 2, 396]; exp_num(8), is_atom(FALSE)
6 #UNION ALL: [1, 2, 396]
7 #PRJT2: [0, 1, 396]; exp_num(8), is_atom(FALSE)
8 #HASH LEFT SEMI JOIN2: [0, 1, 396]; KEY_NUM(1); KEY(A.STATUS=DMTEMPVIEW_17681581.colname) KEY_NULL_EQU(0)
9 #SLCT2: [0, 1, 396]; A.IS_DEL = '0'
10 #CSCN2: [0, 7, 396]; INDEX33559165(SERVICE_INOUT_PROJECT as A)
11 #CONST VALUE LIST: [0, 18, 48]; row_num(18), col_num(1),
12 #PRJT2: [0, 1, 396]; exp_num(8), is_atom(FALSE)
13 #HASH LEFT SEMI JOIN2: [0, 1, 396]; KEY_NUM(1); KEY(B.STATUS=DMTEMPVIEW_17681582.colname) KEY_NULL_EQU(0)
14 #SLCT2: [0, 1, 396]; B.IS_DEL = '0'
15 #CSCN2: [0, 1, 396]; INDEX33559166(SERVICE_FREE_PROJECT as B)
16 #CONST VALUE LIST: [0, 18, 48]; row_num(18), col_num(1),
17 #BLKUP2: [0, 1, 0]; INDEX33559875(PRO)
18 #SSEK2: [0, 1, 0]; scan_type(ASC), INDEX33559875(CQ_BASE_PRO as PRO), scan_range[C.PROJECT_ID,C.PROJECT_ID]
19 #BLKUP2: [0, 1, 0]; IDXFS_SHB_CQCQ_CQ_BASE_PRO_ID(XMXX)
20 #SSEK2: [0, 1, 0]; scan_type(ASC), IDXFS_SHB_CQCQ_CQ_BASE_PRO_ID(SHB_CQCQ as XMXX), scan_range[PRO.ID,PRO.ID] ******* 优化效果点 *******
2.2.2 子部分代码2
-- 脱敏SQL
SELECT XMXX.IS_LOW_RISK ,
en.id ,
XMXX.REPORT_TYPE ,
XMXX.TYPE ,
en.apply_stage AS STAGE ,
en.STATUS ,
XMXX.CODE ,
XMXX.NAME ,
XMXX.OLD_ID ,
XMXX.CUR_DEPARTMENT_GUID ,
XMXX.CUR_OFFICE_GUID ,
XMXX.CUR_USER_ID ,
XMXX.ATTEND_STATUS ,
XMXX.APPLY_TIME ,
en.update_time ,
PRO_COUNTY.COUNTY ,
PRO_JSXZ.item_full_value as JSXZ ,
XMXX.SYS_FLAG ,
XMXX.VERSION ,
XMXX.TOTAL_INVEST ,
case when en.reply_level = '0' then '教室' else DE.department_fullname end AS approve_dept_name,
DE.parent_guid ,
DE.store_level ,
OWN.TYPE OWN_TYPE ,
XMXX.LEGAL ,
OWN.COMPANY_NAME COMPANY_NAME ,
'energy' as xmflag ,
XMXX.is_ppp_item
from energy_audit en
left join ( select x.*
from SHB_CQCQ x
inner join (select code,
max(create_time) utime
from SHB_CQCQ
where (status = '40' or type = '5')
group by code ) xx
on xx.code = x.code and x.create_time = xx.utime ) xmxx
on xmxx.code = en.project_code
left join ( select item_full_value county,
item_key
from dictionary_items
where group_no = '1008' ) pro_county
on pro_county.item_key = xmxx.county
left join ( select item_full_value,
item_key
from dictionary_items
where group_no = '1009' ) pro_jsxz
on pro_jsxz.item_key = xmxx.jsxz
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
where 1=1 AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002') /*耗时985毫秒*/
子代码2的执行计划:看来SHB_CQCQ表属于业务核心表,哪哪都有它,反复的引用,体积有点大。
迅速怀疑(status = ‘40’ or type = ‘5’) 过滤条件很差,而优化器默认union分治再合并处理(步骤21-25),赶紧过滤查查select count(*) from shb_xmxx XMXX where (status = ‘40’ or type = ‘5’) ; – 235 931,返回的结果集比较大啦。
显然步骤21-25执行计划是错误的,应该考虑过滤条件合体,而不是拆分再合并(搞出步骤22的错误索引回表和步骤25的二次全表扫描),这个可以用hint optimizer_or_nbexp(2)解决。
再继续针对XMXX内联视图的查看,SHB_CQCQ as X又被执行了一次自关联,执行计划步骤26体现,显然这种不科学。他的初衷大致取每个项目最近的处理时间,完全没必要反复自关联这么搞,弄成分析函数作辅助列再过滤即可,减少一次SHB_CQCQ as X表的访问,性能自然而然地提高。
-- 未优化前执行计划
1 #NSET2: [1196, 7949, 7886]
2 #PRJT2: [1196, 7949, 7886]; exp_num(28), is_atom(FALSE)
3 #INDEX JOIN LEFT JOIN2: [1196, 7949, 7886]
4 #INDEX JOIN LEFT JOIN2: [1187, 1295, 7886]
5 #HASH RIGHT JOIN2: [1178, 1295, 7886]; key_num(1), KEY(PRO_JSXZ.ITEM_KEY=XMXX.JSXZ)
6 #PRJT2: [1, 8, 144]; exp_num(2), is_atom(FALSE)
7 #SLCT2: [1, 8, 144]; DICTIONARY_ITEMS.GROUP_NO = '1009'
8 #CSCN2: [1, 9158, 144]; INDEX33559353(DICTIONARY_ITEMS)
9 #HASH RIGHT JOIN2: [1175, 1295, 7742]; key_num(1), KEY(PRO_COUNTY.ITEM_KEY=XMXX.COUNTY)
10 #PRJT2: [1, 72, 144]; exp_num(2), is_atom(FALSE)
11 #SLCT2: [1, 72, 144]; DICTIONARY_ITEMS.GROUP_NO = '1008'
12 #CSCN2: [1, 9158, 144]; INDEX33559353(DICTIONARY_ITEMS)
13 #HASH2 INNER JOIN: [1171, 1295, 7598]; KEY_NUM(1); KEY(EN.PROJECT_CODE=XMXX.CODE) KEY_NULL_EQU(0)
14 #CSCN2: [0, 1328, 252]; INDEX33560566(ENERGY_AUDIT as EN)
15 #HASH RIGHT SEMI JOIN2: [1162, 7727, 7346]; n_keys(1) KEY(DMTEMPVIEW_17697425.colname=XMXX.COUNTY) KEY_NULL_EQU(0)
16 #CONST VALUE LIST: [0, 2, 48]; row_num(2), col_num(1),
17 #PRJT2: [1157, 154543, 7346]; exp_num(20), is_atom(FALSE)
18 #HASH2 INNER JOIN: [1157, 154543, 7346]; KEY_NUM(2); KEY(XX.CODE=X.CODE AND XX.UTIME=X.CREATE_TIME) KEY_NULL_EQU(0, 0)
19 #PRJT2: [108, 227506, 146]; exp_num(2), is_atom(FALSE)
20 #HAGR2: [108, 227506, 146]; grp_num(1), sfun_num(1); slave_empty(0) keys(SHB_CQCQ.CODE)
21 #UNION FOR OR2: [86, 263623, 146]; key_num(1) ************ 性能瓶颈点 ************
22 #BLKUP2: [41, 36060, 146]; INDEX_NAME_TYPE(SHB_CQCQ)
23 #SSEK2: [41, 36060, 146]; scan_type(ASC), INDEX_NAME_TYPE(SHB_CQCQ), scan_range['5','5']
24 #SLCT2: [45, 227562, 146]; SHB_CQCQ.STATUS = var2
25 #CSCN2: [45, 311391, 146]; INDEX33559137(SHB_CQCQ)
26 #CSCN2: [712, 311391, 7200]; INDEX33559137(SHB_CQCQ as X) ************ 性能瓶颈点 ************
27 #BLKUP2: [8, 1, 0]; INDEX33559823(DE)
28 #SSEK2: [8, 1, 0]; scan_type(ASC), INDEX33559823(DEPARTMENT as DE), scan_range[XMXX.DUTY_DEPARTMENT_ID,XMXX.DUTY_DEPARTMENT_ID]
29 #BLKUP2: [9, 7, 0]; SHB_USER_INFO_USERID_INDEX(OWN)
30 #SSEK2: [9, 7, 0]; scan_type(ASC), SHB_USER_INFO_USERID_INDEX(SHB_USER_INFO as OWN), scan_range[XMXX.CREATE_USER_ID,XMXX.CREATE_USER_ID]
处理方法:分析函数等价改写和HINT处理OR不展开
-- 等价改写和HINT指定
SELECT XMXX.IS_LOW_RISK ,
en.id ,
XMXX.REPORT_TYPE ,
XMXX.TYPE ,
en.apply_stage AS STAGE ,
en.STATUS ,
XMXX.CODE ,
XMXX.NAME ,
XMXX.OLD_ID ,
XMXX.CUR_DEPARTMENT_GUID ,
XMXX.CUR_OFFICE_GUID ,
XMXX.CUR_USER_ID ,
XMXX.ATTEND_STATUS ,
XMXX.APPLY_TIME ,
en.update_time ,
PRO_COUNTY.COUNTY ,
PRO_JSXZ.item_full_value as JSXZ ,
XMXX.SYS_FLAG ,
XMXX.VERSION ,
XMXX.TOTAL_INVEST ,
case when en.reply_level = '0' then '教室' else DE.department_fullname end AS approve_dept_name,
DE.parent_guid ,
DE.store_level ,
OWN.TYPE OWN_TYPE ,
XMXX.LEGAL ,
OWN.COMPANY_NAME COMPANY_NAME ,
'energy' as xmflag ,
XMXX.is_ppp_item
from energy_audit en
left join ( select /*+ optimizer_or_nbexp(2)*/xx.*
from (
select x.*, max(create_time) over(partition by code ) utime
from SHB_CQCQ x
where (status = '40' or type = '5')
) xx
where xx.utime = create_time
) xmxx
on xmxx.code = en.project_code
left join ( select item_full_value county,
item_key
from dictionary_items
where group_no = '1008' ) pro_county
on pro_county.item_key = xmxx.county
left join ( select item_full_value,
item_key
from dictionary_items
where group_no = '1009' ) pro_jsxz
on pro_jsxz.item_key = xmxx.jsxz
LEFT JOIN DEPARTMENT DE
ON XMXX.duty_department_id = DE.DEPARTMENT_GUID
LEFT JOIN SHB_USER_INFO OWN
ON OWN.USER_ID = XMXX.CREATE_USER_ID
where 1=1 AND XMXX.COUNTY in ('CQ50011001',
'CQ50011002')
-- 优化后执行计划
1 #NSET2: [241, 400, 7740]
2 #PRJT2: [241, 400, 7740]; exp_num(28), is_atom(FALSE)
3 #INDEX JOIN LEFT JOIN2: [241, 400, 7740]
4 #INDEX JOIN LEFT JOIN2: [236, 65, 7740]
5 #HASH RIGHT JOIN2: [232, 65, 7740]; key_num(1), KEY(PRO_JSXZ.ITEM_KEY=XMXX.JSXZ)
6 #PRJT2: [0, 8, 144]; exp_num(2), is_atom(FALSE)
7 #BLKUP2: [0, 8, 144]; IDXFS_DICTIONARY_ITEMS_GROUP_NO(DICTIONARY_ITEMS)
8 #SSEK2: [0, 8, 144]; scan_type(ASC), IDXFS_DICTIONARY_ITEMS_GROUP_NO(DICTIONARY_ITEMS), scan_range['1009','1009']
9 #HASH LEFT JOIN2: [231, 65, 7596]; key_num(1), partition_keys_num(0) KEY(XMXX.COUNTY=PRO_COUNTY.ITEM_KEY)
10 #HASH RIGHT SEMI JOIN2: [230, 65, 7452]; n_keys(1) KEY(DMTEMPVIEW_17743948.colname=XMXX.COUNTY) KEY_NULL_EQU(0)
11 #CONST VALUE LIST: [0, 2, 48]; row_num(2), col_num(1),
12 #SLCT2: [230, 65, 7452]; XMXX.CODE = EN.PROJECT_CODE
13 #NEST LOOP INNER JOIN2: [230, 65, 7452]; [with var]
14 #CSCN2: [0, 1328, 252]; INDEX33560566(ENERGY_AUDIT as EN)
15 #PRJT2: [0, 1, 7200]; exp_num(20), is_atom(FALSE)
16 #SLCT2: [0, 1, 7200]; XX.UTIME = XX.CREATE_TIME ******* 优化效果点 *******
17 #PRJT2: [0, 1, 7200]; exp_num(22), is_atom(FALSE)
18 #AFUN: [0, 1, 7200]; afun_num(1); partition_num(1)[X.CODE]; order_num(0)
19 #SLCT2: [0, 1, 7200]; (X.TYPE = '5' OR X.STATUS = var3)
20 #BLKUP2: [0, 1, 7200]; SHB_CQCQ_CODE_INDEX(X)
21 #SSEK2: [0, 1, 7200]; scan_type(ASC), SHB_CQCQ_CODE_INDEX(SHB_CQCQ as X), scan_range[var2,var2] ******* 优化效果点 *******
22 #PRJT2: [0, 72, 144]; exp_num(2), is_atom(FALSE)
23 #BLKUP2: [0, 72, 144]; IDXFS_DICTIONARY_ITEMS_GROUP_NO(DICTIONARY_ITEMS)
24 #SSEK2: [0, 72, 144]; scan_type(ASC), IDXFS_DICTIONARY_ITEMS_GROUP_NO(DICTIONARY_ITEMS), scan_range['1008','1008']
25 #BLKUP2: [4, 1, 0]; INDEX33559823(DE)
26 #SSEK2: [4, 1, 0]; scan_type(ASC), INDEX33559823(DEPARTMENT as DE), scan_range[XMXX.DUTY_DEPARTMENT_ID,XMXX.DUTY_DEPARTMENT_ID]
27 #BLKUP2: [4, 7, 0]; SHB_USER_INFO_USERID_INDEX(OWN)
28 #SSEK2: [4, 7, 0]; scan_type(ASC), SHB_USER_INFO_USERID_INDEX(SHB_USER_INFO as OWN), scan_range[XMXX.CREATE_USER_ID,XMXX.CREATE_USER_ID]
2.3 优化结果
这条复杂SQL在客户项目中运行最为频繁,从原来8秒已经优化到200毫秒,极大缩短响应时间,减轻数据库服务器的压力。
优化工作适可而止,不要强迫搞到极致,白白浪费时间,其他项目还得忙。
3. 优化心得
- 1. 遇到巨长的SQL不要慌张,学会拆分,缩小范围。
- 2. 学会总结,有时根本不用ET工具,可以直接从执行计划看出来,或者从SQL中某部分能快速怀疑对位置。
- 3. SQL等价改写的前提是一定要读懂语义,进行同含义的改写(改写成简单的,不是绝对的,有时反而需要改成复杂的才能优化)。
- 4. 像我们这些DBA一般搞懂语义改写就不错了,能懂业务逻辑那才是最好的,少走很多弯路(遇到很多开发商可能SQL编写能力参差不齐,也有可能不是很熟悉业务,没办法的事)。