一次复杂查询的优化过程 - 函数稳定性引发的性能问题
一、背景
这个复杂的查询SQL语句,已经进行语法优化,写入应用程序中,且不希望修改代码,实现执行速度大幅度提升。
查询语句中,使用了用户自定义函数,多个视图嵌套,代码逻辑复杂,运行时长过长。
分析方向,基于查询计划,定位耗时较多的节点,通过改变调用对象,实现优化查询性能。
二、查询语句,优化前后的计划
SQL语句如下:
analyse; explain (analyse ,buffers ,verbose ,costs ,timing ) with t as (select d.*, nvl(getfinanceamount(d.keyid), 0) useMoney from (select t.realId as keyId, t.bg_type, t.bg_year, t.bg_deptname, t.bg_deptId, t.bg_functiongname, t.bg_functiongcode, t.bg_projectname, t.bg_projectcode, t.bg_enconame, t.bg_encocode, sum(t.bg_budgetmoney) as bgBudgetMoney, sum(t.bg_budgetdeptmoney) as bgBudgetDeptMoney, t.bg_budgetdeptpp, sum(t.bg_detailmoney) as bgDetailMoney, t.bg_detailpp, t.bg_source, t.bg_bid, t.bg_memo, t.budgetsourcetype, t.paytype from (select d.*, nvl(s.paytype, '其他') as paytype, d.keyid as realId from budget_t_distinfo d left join busi_t_budgetdetail s on s.keyid = d.bg_bid where 1 = 1 and d.bg_detailmoney > 0 and d.bg_source in ('1', '3') union all select d.*, nvl(s.paytype, '其他') as paytype, nvl(a.keyid, d.keyid) as realId from budget_t_distinfo d left join busi_t_budgetdetail s on s.keyid = d.bg_bid left join budget_t_distinfo a on a.bg_year = d.bg_year and a.bg_type = d.bg_type and a.bg_deptid = d.bg_deptid and a.bg_functiongcode = d.bg_functiongcode and a.bg_projectcode = d.bg_projectcode and a.bg_encocode = d.bg_encocode and a.bg_source in ('1', '3') where 1 = 1 and d.bg_detailmoney > 0 and d.bg_source in ('2', '6') ) t group by t.realId, t.bg_type, t.bg_year, t.bg_deptname, t.bg_deptId, t.bg_functiongname, t.bg_functiongcode, t.bg_projectname, t.bg_projectcode, t.bg_enconame, t.bg_encocode, t.bg_budgetdeptpp, t.bg_detailpp, t.bg_source, t.bg_bid, t.bg_memo, t.budgetsourcetype, t.paytype) d ), b as (select v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname, sum(v.debitamount) as usedMoney from view_bd_acc v where 1 = 1 and v.unitsid = 825 and v.year = 2022 group by v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname) select t.*, nvl(b.usedMoney, 0) as usedMoney from t left join b on b.f1 = t.bg_functiongname and b.f2 = t.bg_enconame and nvl(b.f3, 0) = nvl(decode(t.bg_projectname, '请选择', '', t.bg_projectname), 0) and b.f7 = decode(t.bg_source, 1, '本年预算', 2, '本年预算', 3, '结转资金') and b.btype = decode(t.bg_type, 1, '基本支出', '项目支出') and b.bmname = t.bg_deptname where 1 = 1 and t.bg_year = 2022 ;
优化前的查询计划,用时57秒
Nested Loop Left Join (cost=40763.36..40768.61 rows=1 width=2284) (actual time=1102.648..58109.460 rows=73 loops=1) Join Filter: ((b.f1 = (t.bg_functiongname)::text) AND (b.f2 = (t.bg_enconame)::text) AND ((b.bmname)::text = (t.bg_deptname)::text) AND ((NVL((b.f3)::character varying, '0'::character varying))::text = (NVL(DECODE(t.bg_projectname, '请选择'::text, NULL::character varying, t.bg_projectname), '0'::character varying))::text) AND (b.btype = (DECODE(text_numeric(t.bg_type), '1'::numeric, '基本支出'::character varying, '项目支出'::character varying))::text) AND ((b.f7)::text = (DECODE(text_numeric(t.bg_source), '1'::numeric, '本年预算'::character varying, '2'::numeric, '本年预算'::character varying, '3'::numeric, '结转资金'::character varying, NULL::character varying))::text)) Rows Removed by Join Filter: 1157 Buffers: shared hit=10447414 read=7332 I/O Timings: read=1110.574 CTE t -> Subquery Scan on d (cost=1086.55..1142.05 rows=200 width=344) (actual time=213.277..57656.258 rows=1287 loops=1) Buffers: shared hit=10142442 read=6945 I/O Timings: read=1063.118 -> HashAggregate (cost=1086.55..1090.05 rows=200 width=312) (actual time=17.982..22.077 rows=1287 loops=1) Group Key: *SELECT* 1.realid, *SELECT* 1.bg_type, *SELECT* 1.bg_year, *SELECT* 1.bg_deptname, *SELECT* 1.bg_deptid, *SELECT* 1.bg_functiongname, *SELECT* 1.bg_functiongcode, *SELECT* 1.bg_projectname, *SELECT* 1.bg_projectcode, *SELECT* 1.bg_enconame, *SELECT* 1.bg_encocode, *SELECT* 1.bg_budgetdeptpp, *SELECT* 1.bg_detailpp, *SELECT* 1.bg_source, *SELECT* 1.bg_bid, *SELECT* 1.bg_memo, *SELECT* 1.budgetsourcetype, *SELECT* 1.paytype Buffers: shared hit=210 read=381 I/O Timings: read=8.926 -> Append (cost=103.47..1018.98 rows=1287 width=233) (actual time=11.277..16.589 rows=1287 loops=1) Buffers: shared hit=210 read=381 I/O Timings: read=8.926 -> Subquery Scan on *SELECT* 1 (cost=103.47..708.85 rows=1280 width=233) (actual time=11.276..14.317 rows=1276 loops=1) Buffers: shared hit=65 read=381 I/O Timings: read=8.926 -> Hash Right Join (cost=103.47..696.05 rows=1280 width=247) (actual time=11.274..14.141 rows=1276 loops=1) Hash Cond: (s.keyid = d_1.bg_bid) Buffers: shared hit=65 read=381 I/O Timings: read=8.926 -> Seq Scan on busi_t_budgetdetail s (cost=0.00..528.02 rows=13802 width=19) (actual time=0.685..9.994 rows=13802 loops=1) Buffers: shared hit=9 read=381 I/O Timings: read=8.926 -> Hash (cost=87.47..87.47 rows=1280 width=201) (actual time=1.613..1.614 rows=1276 loops=1) Buckets: 2048 Batches: 1 Memory Usage: 291kB Buffers: shared hit=56 -> Seq Scan on budget_t_distinfo d_1 (cost=0.00..87.47 rows=1280 width=201) (actual time=0.014..0.983 rows=1276 loops=1) Filter: ((bg_detailmoney > '0'::numeric) AND ((bg_source)::text = ANY ('{1,3}'::text[]))) Rows Removed by Filter: 822 Buffers: shared hit=56 -> Subquery Scan on *SELECT* 2 (cost=134.69..290.83 rows=7 width=242) (actual time=2.123..2.189 rows=11 loops=1) Buffers: shared hit=145 -> Hash Left Join (cost=134.69..290.76 rows=7 width=256) (actual time=2.120..2.184 rows=11 loops=1) Hash Cond: (((d_2.bg_year)::text = (a.bg_year)::text) AND ((d_2.bg_type)::text = (a.bg_type)::text) AND ((d_2.bg_deptid)::text = (a.bg_deptid)::text) AND ((d_2.bg_functiongcode)::text = (a.bg_functiongcode)::text) AND ((d_2.bg_projectcode)::text = (a.bg_projectcode)::text) AND ((d_2.bg_encocode)::text = (a.bg_encocode)::text)) Buffers: shared hit=145 -> Nested Loop Left Join (cost=0.29..145.59 rows=7 width=214) (actual time=1.010..1.069 rows=11 loops=1) Buffers: shared hit=89 -> Seq Scan on budget_t_distinfo d_2 (cost=0.00..87.47 rows=7 width=201) (actual time=0.986..1.016 rows=11 loops=1) Filter: ((bg_detailmoney > '0'::numeric) AND ((bg_source)::text = ANY ('{2,6}'::text[]))) Rows Removed by Filter: 2087 Buffers: shared hit=56 -> Index Scan using busi_t_budgetdetail_busi_pk_budgetdetail on busi_t_budgetdetail s_1 (cost=0.29..8.30 rows=1 width=19) (actual time=0.003..0.003 rows=1 loops=11) Index Cond: (keyid = d_2.bg_bid) Buffers: shared hit=33 -> Hash (cost=82.22..82.22 rows=2087 width=46) (actual time=1.091..1.092 rows=2085 loops=1) Buckets: 4096 Batches: 1 Memory Usage: 196kB Buffers: shared hit=56 -> Seq Scan on budget_t_distinfo a (cost=0.00..82.22 rows=2087 width=46) (actual time=0.016..0.568 rows=2087 loops=1) Filter: ((bg_source)::text = ANY ('{1,3}'::text[])) Rows Removed by Filter: 11 Buffers: shared hit=56 CTE b -> GroupAggregate (cost=39621.18..39621.31 rows=4 width=224) (actual time=447.585..447.609 rows=16 loops=1) Group Key: v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> Sort (cost=39621.18..39621.19 rows=4 width=224) (actual time=447.580..447.593 rows=22 loops=1) Sort Key: v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname Sort Method: quicksort Memory: 28kB Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> Subquery Scan on v (cost=39620.83..39621.14 rows=4 width=224) (actual time=447.507..447.576 rows=22 loops=1) Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> GroupAggregate (cost=39620.83..39621.10 rows=4 width=452) (actual time=447.506..447.572 rows=22 loops=1) Group Key: t_1.btype, t_1.f1, t_1.f2, t_1.f3, t_1.f7, t_1.bmname, t_1.paytype, t_1.unitsid, t_1.accountperiod, t_1.year, p.departcode Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> Sort (cost=39620.83..39620.84 rows=4 width=356) (actual time=447.495..447.507 rows=65 loops=1) Sort Key: t_1.btype, t_1.f1, t_1.f2, t_1.f3, t_1.f7, t_1.bmname, t_1.paytype, t_1.accountperiod, p.departcode Sort Method: quicksort Memory: 41kB Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> Hash Right Join (cost=39497.72..39620.79 rows=4 width=356) (actual time=447.105..447.370 rows=65 loops=1) Hash Cond: ((p.unitsid = t_1.unitsid) AND ((p.departname)::text = (t_1.bmname)::text)) Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 -> Seq Scan on sys_t_department p (cost=0.00..122.62 rows=58 width=21) (actual time=1.787..2.017 rows=58 loops=1) Filter: ((unitsid = '825'::numeric) AND ((useflag)::text = '1'::text)) Rows Removed by Filter: 4717 Buffers: shared hit=3 read=48 I/O Timings: read=1.472 -> Hash (cost=39497.66..39497.66 rows=4 width=352) (actual time=445.308..445.317 rows=65 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 18kB Buffers: shared hit=304969 read=339 I/O Timings: read=45.984 -> Subquery Scan on t_1 (cost=39410.84..39497.66 rows=4 width=352) (actual time=436.419..445.254 rows=65 loops=1) Buffers: shared hit=304969 read=339 I/O Timings: read=45.984 -> Append (cost=39410.84..39497.62 rows=4 width=352) (actual time=436.418..445.236 rows=65 loops=1) Buffers: shared hit=304969 read=339 I/O Timings: read=45.984 -> Nested Loop Left Join (cost=39410.84..39420.59 rows=1 width=325) (actual time=436.417..444.912 rows=47 loops=1) Filter: (NVL(s_2.keyid, '825'::numeric) = '825'::numeric) Buffers: shared hit=304890 read=339 I/O Timings: read=45.984 -> GroupAggregate (cost=39410.27..39410.31 rows=1 width=1949) (actual time=436.073..436.232 rows=47 loops=1) Group Key: j0.keyid, v_1.accountid, v_1.year, v_1.accountperiod, v_1.transitroleid, j.subject, v_1.status Buffers: shared hit=302942 read=339 I/O Timings: read=45.984 -> Sort (cost=39410.27..39410.28 rows=1 width=1793) (actual time=436.057..436.068 rows=47 loops=1) Sort Key: j0.keyid, v_1.accountid, v_1.accountperiod, v_1.transitroleid, j.subject, v_1.status Sort Method: quicksort Memory: 45kB Buffers: shared hit=302942 read=339 I/O Timings: read=45.984 -> Nested Loop (cost=27587.45..39410.26 rows=1 width=1793) (actual time=280.458..436.004 rows=47 loops=1) Buffers: shared hit=302942 read=339 I/O Timings: read=45.984 -> Hash Join (cost=27587.03..34466.00 rows=1255 width=1783) (actual time=134.776..229.053 rows=70418 loops=1) Hash Cond: (j0.journalid = j.keyid) Buffers: shared hit=21623 read=42 I/O Timings: read=1.045 -> Seq Scan on accounting_journalassist j0 (cost=0.00..6548.60 rows=125857 width=1657) (actual time=0.006..31.338 rows=167439 loops=1) Filter: ((f9 IS NOT NULL) AND ((f6 IS NULL) OR ((f6)::text = '001'::text))) Rows Removed by Filter: 117649 Buffers: shared hit=2985 -> Hash (cost=27531.84..27531.84 rows=4415 width=138) (actual time=134.752..134.753 rows=70505 loops=1) Buckets: 131072 (originally 8192) Batches: 1 (originally 1) Memory Usage: 20392kB Buffers: shared hit=18638 read=42 I/O Timings: read=1.045 -> Seq Scan on accounting_journal j (cost=0.00..27531.84 rows=4415 width=138) (actual time=0.020..116.354 rows=70505 loops=1) Filter: ((split_part((subject)::text, ' '::text, 1) ~~ '71010101%'::text) OR (split_part((subject)::text, ' '::text, 1) ~~ '71010102%'::text)) Rows Removed by Filter: 372087 Buffers: shared hit=18638 read=42 I/O Timings: read=1.045 -> Index Scan using accounting_voucher_pk_voucher on accounting_voucher v_1 (cost=0.42..3.94 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=70418) Index Cond: (keyid = j.voucherid) Filter: ((DECODE(sn, NULL::boolean, '0'::numeric, sn) > '0'::numeric) AND ((status)::text <> '4'::text) AND ((status)::text <> '4'::text) AND (year = '2022'::numeric) AND (DECODE((to_char((withto)::text))::character varying, NULL::boolean, '0'::numeric, text_numeric(to_char((withto)::text))) = '0'::numeric)) Rows Removed by Filter: 1 Buffers: shared hit=281319 read=297 I/O Timings: read=44.939 -> Nested Loop Left Join (cost=0.57..8.75 rows=1 width=10) (actual time=0.005..0.005 rows=1 loops=47) Buffers: shared hit=329 -> Nested Loop Left Join (cost=0.42..8.49 rows=1 width=14) (actual time=0.003..0.003 rows=1 loops=47) Buffers: shared hit=235 -> Index Scan using accounting_account_pk_accounting_account on accounting_account b_1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: (keyid = v_1.accountid) Buffers: shared hit=141 -> Index Scan using accounting_unit_pk_accounting_unit on accounting_unit u (cost=0.14..0.20 rows=1 width=14) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: (keyid = b_1.unitid) Buffers: shared hit=94 -> Index Scan using sys_t_units_sys_ix_units_unitscode on sys_t_units s_2 (cost=0.14..0.25 rows=1 width=15) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: ((unitscode)::text = (u.code)::text) Buffers: shared hit=94 -> Subquery Scan on *SELECT* 2_1 (cost=13.52..77.00 rows=3 width=298) (actual time=0.195..0.313 rows=18 loops=1) Buffers: shared hit=79 -> Hash Right Join (cost=13.52..76.97 rows=3 width=298) (actual time=0.193..0.308 rows=18 loops=1) Hash Cond: (s_3.borrowid = b_2.keyid) Buffers: shared hit=79 -> Seq Scan on busi_t_borrow_subject s_3 (cost=0.00..32.28 rows=628 width=126) (actual time=0.003..0.077 rows=628 loops=1) Buffers: shared hit=26 -> Hash (cost=13.49..13.49 rows=3 width=24) (actual time=0.042..0.042 rows=18 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB Buffers: shared hit=15 -> Index Scan using busi_t_borrow_busi_ix_borrow_year on busi_t_borrow b_2 (cost=0.28..13.49 rows=3 width=24) (actual time=0.013..0.038 rows=18 loops=1) Index Cond: (year_ = '2022'::numeric) Filter: ((unitsid = '825'::numeric) AND ((historystatus)::integer < 3) AND ((accountstatus)::integer >= 3)) Rows Removed by Filter: 10 Buffers: shared hit=15 SubPlan 2 -> Aggregate (cost=8.31..8.32 rows=1 width=32) (actual time=0.002..0.002 rows=1 loops=18) Buffers: shared hit=38 -> Index Scan using busi_t_repay_idx_borrowid on busi_t_repay r (cost=0.28..8.30 rows=1 width=9) (actual time=0.001..0.001 rows=0 loops=18) Index Cond: (borrowid = b_2.keyid) Filter: (((historystatus)::integer < 3) AND ((accountstatus)::integer >= 3)) Rows Removed by Filter: 0 Buffers: shared hit=38 -> CTE Scan on t (cost=0.00..5.00 rows=1 width=2252) (actual time=655.036..57661.296 rows=73 loops=1) Filter: ((bg_year)::integer = 2022) Rows Removed by Filter: 1214 Buffers: shared hit=10142442 read=6945 I/O Timings: read=1063.118 -> CTE Scan on b (cost=0.00..0.08 rows=4 width=224) (actual time=6.132..6.134 rows=16 loops=73) Buffers: shared hit=304972 read=387 I/O Timings: read=47.456 Planning Time: 3.436 ms Execution Time: 58109.920 ms
执行计划耗时分析:主要耗时在subquery scan,而不是在join and aggregate 部分。
为什么 " subquery scan on d " 会如此耗时?结合语句,可以确定耗时点是在于 getfinanceamount 函数的调用。
以下是最终优化后的查询计划,用时0.15秒。可以看到,CTE t 进行了扁平化,而不是 CTE t 的结果先执行出来,再进行连接。
Hash Right Join (cost=7866.26..7868.22 rows=7 width=377) (actual time=54.484..156.509 rows=73 loops=1) Hash Cond: ((b.f1 = (*SELECT* 1.bg_functiongname)::text) AND (b.f2 = (*SELECT* 1.bg_enconame)::text) AND ((NVL((b.f3)::character varying, '0'::character varying))::text = (NVL(DECODE(*SELECT* 1.bg_projectname, '请选择'::text, NULL::character varying, *SELECT* 1.bg_projectname), '0'::character varying))::text) AND ((b.f7)::text = (DECODE(text_numeric(*SELECT* 1.bg_source), '1'::numeric, '本年预算'::character varying, '2'::numeric, '本年预算'::character varying, '3'::numeric, '结转资金'::character varying, NULL::character varying))::text) AND (b.btype = (DECODE(text_numeric(*SELECT* 1.bg_type), '1'::numeric, '基本支出'::character varying, '项目支出'::character varying))::text) AND ((b.bmname)::text = (*SELECT* 1.bg_deptname)::text)) Buffers: shared hit=23216 CTE b -> GroupAggregate (cost=7481.84..7481.97 rows=4 width=224) (actual time=49.840..49.882 rows=16 loops=1) Group Key: v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname Buffers: shared hit=6925 -> Sort (cost=7481.84..7481.85 rows=4 width=224) (actual time=49.837..49.851 rows=22 loops=1) Sort Key: v.f1, v.f2, v.f3, v.f7, v.btype, v.bmname Sort Method: quicksort Memory: 28kB Buffers: shared hit=6925 -> Subquery Scan on v (cost=7481.49..7481.80 rows=4 width=224) (actual time=49.762..49.831 rows=22 loops=1) Buffers: shared hit=6925 -> GroupAggregate (cost=7481.49..7481.76 rows=4 width=452) (actual time=49.762..49.828 rows=22 loops=1) Group Key: t.btype, t.f1, t.f2, t.f3, t.f7, t.bmname, t.paytype, t.unitsid, t.accountperiod, t.year, p.departcode Buffers: shared hit=6925 -> Sort (cost=7481.49..7481.50 rows=4 width=356) (actual time=49.753..49.766 rows=65 loops=1) Sort Key: t.btype, t.f1, t.f2, t.f3, t.f7, t.bmname, t.paytype, t.accountperiod, p.departcode Sort Method: quicksort Memory: 41kB Buffers: shared hit=6925 -> Hash Right Join (cost=7426.68..7481.45 rows=4 width=356) (actual time=49.588..49.630 rows=65 loops=1) Hash Cond: ((p.unitsid = t.unitsid) AND ((p.departname)::text = (t.bmname)::text)) Buffers: shared hit=6925 -> Bitmap Heap Scan on sys_t_department p (cost=4.73..59.06 rows=58 width=21) (actual time=0.017..0.027 rows=58 loops=1) Recheck Cond: (unitsid = '825'::numeric) Filter: ((useflag)::text = '1'::text) Heap Blocks: exact=3 Buffers: shared hit=6 -> Bitmap Index Scan on sys_t_department_unitsid (cost=0.00..4.72 rows=58 width=0) (actual time=0.009..0.009 rows=58 loops=1) Index Cond: (unitsid = '825'::numeric) Buffers: shared hit=3 -> Hash (cost=7421.89..7421.89 rows=4 width=352) (actual time=49.563..49.572 rows=65 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 18kB Buffers: shared hit=6919 -> Subquery Scan on t (cost=7335.07..7421.89 rows=4 width=352) (actual time=40.958..49.531 rows=65 loops=1) Buffers: shared hit=6919 -> Append (cost=7335.07..7421.85 rows=4 width=352) (actual time=40.957..49.516 rows=65 loops=1) Buffers: shared hit=6919 -> Nested Loop Left Join (cost=7335.07..7344.82 rows=1 width=325) (actual time=40.956..49.252 rows=47 loops=1) Filter: (NVL(s_2.keyid, '825'::numeric) = '825'::numeric) Buffers: shared hit=6840 -> GroupAggregate (cost=7334.50..7334.55 rows=1 width=1949) (actual time=40.628..40.767 rows=47 loops=1) Group Key: j0.keyid, v_1.accountid, v_1.year, v_1.accountperiod, v_1.transitroleid, j.subject, v_1.status Buffers: shared hit=4892 -> Sort (cost=7334.50..7334.51 rows=1 width=1793) (actual time=40.611..40.618 rows=47 loops=1) Sort Key: j0.keyid, v_1.accountid, v_1.accountperiod, v_1.transitroleid, j.subject, v_1.status Sort Method: quicksort Memory: 45kB Buffers: shared hit=4892 -> Nested Loop (cost=0.84..7334.49 rows=1 width=1793) (actual time=33.344..40.578 rows=47 loops=1) Buffers: shared hit=4892 -> Nested Loop (cost=0.42..7331.13 rows=5 width=148) (actual time=33.334..40.435 rows=47 loops=1) Buffers: shared hit=4704 -> Seq Scan on accounting_voucher v_1 (cost=0.00..7105.20 rows=10 width=22) (actual time=32.981..39.962 rows=123 loops=1) Filter: ((sn > '0'::numeric) AND ((status)::text <> '4'::text) AND (NVL(withto, '0'::numeric) = '0'::numeric) AND (year = '2022'::numeric)) Rows Removed by Filter: 149187 Buffers: shared hit=4119 -> Index Scan using accounting_journal_index_voucherid on accounting_journal j (cost=0.42..22.58 rows=1 width=138) (actual time=0.003..0.004 rows=0 loops=123) Index Cond: (voucherid = v_1.keyid) Filter: ((split_part((subject)::text, ' '::text, 1) ~~ '71010101%'::text) OR (split_part((subject)::text, ' '::text, 1) ~~ '71010102%'::text)) Rows Removed by Filter: 4 Buffers: shared hit=585 -> Index Scan using accounting_journalassist_index_journalid on accounting_journalassist j0 (cost=0.42..0.66 rows=1 width=1657) (actual time=0.002..0.003 rows=1 loops=47) Index Cond: (journalid = j.keyid) Filter: ((f9 IS NOT NULL) AND ((f6 IS NULL) OR ((f6)::text = '001'::text))) Buffers: shared hit=188 -> Nested Loop Left Join (cost=0.57..8.75 rows=1 width=10) (actual time=0.004..0.005 rows=1 loops=47) Buffers: shared hit=329 -> Nested Loop Left Join (cost=0.42..8.49 rows=1 width=14) (actual time=0.003..0.003 rows=1 loops=47) Buffers: shared hit=235 -> Index Scan using accounting_account_pk_accounting_account on accounting_account b_1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: (keyid = v_1.accountid) Buffers: shared hit=141 -> Index Scan using accounting_unit_pk_accounting_unit on accounting_unit u (cost=0.14..0.20 rows=1 width=14) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: (keyid = b_1.unitid) Buffers: shared hit=94 -> Index Scan using sys_t_units_sys_ix_units_unitscode on sys_t_units s_2 (cost=0.14..0.25 rows=1 width=15) (actual time=0.001..0.001 rows=1 loops=47) Index Cond: ((unitscode)::text = (u.code)::text) Buffers: shared hit=94 -> Subquery Scan on *SELECT* 2_1 (cost=13.52..77.00 rows=3 width=298) (actual time=0.154..0.254 rows=18 loops=1) Buffers: shared hit=79 -> Hash Right Join (cost=13.52..76.97 rows=3 width=298) (actual time=0.153..0.251 rows=18 loops=1) Hash Cond: (s_3.borrowid = b_2.keyid) Buffers: shared hit=79 -> Seq Scan on busi_t_borrow_subject s_3 (cost=0.00..32.28 rows=628 width=126) (actual time=0.003..0.059 rows=628 loops=1) Buffers: shared hit=26 -> Hash (cost=13.49..13.49 rows=3 width=24) (actual time=0.036..0.036 rows=18 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB Buffers: shared hit=15 -> Index Scan using busi_t_borrow_busi_ix_borrow_year on busi_t_borrow b_2 (cost=0.28..13.49 rows=3 width=24) (actual time=0.008..0.032 rows=18 loops=1) Index Cond: (year_ = '2022'::numeric) Filter: ((unitsid = '825'::numeric) AND ((historystatus)::integer < 3) AND ((accountstatus)::integer >= 3)) Rows Removed by Filter: 10 Buffers: shared hit=15 SubPlan 1 -> Aggregate (cost=8.31..8.32 rows=1 width=32) (actual time=0.001..0.001 rows=1 loops=18) Buffers: shared hit=38 -> Index Scan using busi_t_repay_idx_borrowid on busi_t_repay r (cost=0.28..8.30 rows=1 width=9) (actual time=0.001..0.001 rows=0 loops=18) Index Cond: (borrowid = b_2.keyid) Filter: (((historystatus)::integer < 3) AND ((accountstatus)::integer >= 3)) Rows Removed by Filter: 0 Buffers: shared hit=38 -> CTE Scan on b (cost=0.00..0.08 rows=4 width=224) (actual time=49.842..49.882 rows=16 loops=1) Buffers: shared hit=6925 -> Hash (cost=384.11..384.11 rows=7 width=313) (actual time=2.153..2.159 rows=73 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 24kB Buffers: shared hit=387 -> GroupAggregate (cost=383.53..384.04 rows=7 width=313) (actual time=1.998..2.092 rows=73 loops=1) Group Key: *SELECT* 1.realid, *SELECT* 1.bg_type, *SELECT* 1.bg_year, *SELECT* 1.bg_deptname, *SELECT* 1.bg_deptid, *SELECT* 1.bg_functiongname, *SELECT* 1.bg_functiongcode, *SELECT* 1.bg_projectname, *SELECT* 1.bg_projectcode, *SELECT* 1.bg_enconame, *SELECT* 1.bg_encocode, *SELECT* 1.bg_budgetdeptpp, *SELECT* 1.bg_detailpp, *SELECT* 1.bg_source, *SELECT* 1.bg_bid, *SELECT* 1.bg_memo, *SELECT* 1.budgetsourcetype, *SELECT* 1.paytype Buffers: shared hit=387 -> Sort (cost=383.53..383.55 rows=7 width=234) (actual time=1.984..1.991 rows=73 loops=1) Sort Key: *SELECT* 1.realid, *SELECT* 1.bg_type, *SELECT* 1.bg_year, *SELECT* 1.bg_deptname, *SELECT* 1.bg_deptid, *SELECT* 1.bg_functiongname, *SELECT* 1.bg_functiongcode, *SELECT* 1.bg_projectname, *SELECT* 1.bg_projectcode, *SELECT* 1.bg_enconame, *SELECT* 1.bg_encocode, *SELECT* 1.bg_budgetdeptpp, *SELECT* 1.bg_detailpp, *SELECT* 1.bg_source, *SELECT* 1.bg_bid, *SELECT* 1.bg_memo, *SELECT* 1.budgetsourcetype, *SELECT* 1.paytype Sort Method: quicksort Memory: 45kB Buffers: shared hit=387 -> Result (cost=0.29..383.43 rows=7 width=234) (actual time=0.600..1.944 rows=73 loops=1) Buffers: shared hit=387 -> Append (cost=0.29..383.36 rows=7 width=234) (actual time=0.599..1.934 rows=73 loops=1) Buffers: shared hit=387 -> Subquery Scan on *SELECT* 1 (cost=0.29..147.84 rows=6 width=233) (actual time=0.598..0.805 rows=72 loops=1) Buffers: shared hit=272 -> Nested Loop Left Join (cost=0.29..147.78 rows=6 width=247) (actual time=0.597..0.795 rows=72 loops=1) Buffers: shared hit=272 -> Seq Scan on budget_t_distinfo d (cost=0.00..97.96 rows=6 width=201) (actual time=0.586..0.671 rows=72 loops=1) Filter: ((bg_detailmoney > '0'::numeric) AND ((bg_source)::text = ANY ('{1,3}'::text[])) AND ((bg_year)::integer = 2022)) Rows Removed by Filter: 2026 Buffers: shared hit=56 -> Index Scan using busi_t_budgetdetail_busi_pk_budgetdetail on busi_t_budgetdetail s (cost=0.29..8.30 rows=1 width=19) (actual time=0.001..0.001 rows=1 loops=72) Index Cond: (keyid = d.bg_bid) Buffers: shared hit=216 -> Subquery Scan on *SELECT* 2 (cost=98.27..235.49 rows=1 width=242) (actual time=1.120..1.123 rows=1 loops=1) Buffers: shared hit=115 -> Nested Loop Left Join (cost=98.27..235.48 rows=1 width=256) (actual time=1.119..1.122 rows=1 loops=1) Buffers: shared hit=115 -> Hash Right Join (cost=97.99..227.18 rows=1 width=206) (actual time=1.113..1.115 rows=1 loops=1) Hash Cond: (((a.bg_year)::text = (d_1.bg_year)::text) AND ((a.bg_type)::text = (d_1.bg_type)::text) AND ((a.bg_deptid)::text = (d_1.bg_deptid)::text) AND ((a.bg_functiongcode)::text = (d_1.bg_functiongcode)::text) AND ((a.bg_projectcode)::text = (d_1.bg_projectcode)::text) AND ((a.bg_encocode)::text = (d_1.bg_encocode)::text)) Buffers: shared hit=112 -> Seq Scan on budget_t_distinfo a (cost=0.00..82.22 rows=2087 width=46) (actual time=0.004..0.384 rows=2087 loops=1) Filter: ((bg_source)::text = ANY ('{1,3}'::text[])) Rows Removed by Filter: 11 Buffers: shared hit=56 -> Hash (cost=97.96..97.96 rows=1 width=201) (actual time=0.457..0.458 rows=1 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 9kB Buffers: shared hit=56 -> Seq Scan on budget_t_distinfo d_1 (cost=0.00..97.96 rows=1 width=201) (actual time=0.451..0.453 rows=1 loops=1) Filter: ((bg_detailmoney > '0'::numeric) AND ((bg_source)::text = ANY ('{2,6}'::text[])) AND ((bg_year)::integer = 2022)) Rows Removed by Filter: 2097 Buffers: shared hit=56 -> Index Scan using busi_t_budgetdetail_busi_pk_budgetdetail on busi_t_budgetdetail s_1 (cost=0.29..8.30 rows=1 width=19) (actual time=0.004..0.004 rows=1 loops=1) Index Cond: (keyid = d_1.bg_bid) Buffers: shared hit=3 Planning Time: 2.245 ms Execution Time: 156.745 ms
三、优化过程
1、子查询平面化
子查询平面化是指优化器将把子查询融合到上层查询。
- 分析查询计划
CTE t -> Subquery Scan on d (cost=1086.55..1142.05 rows=200 width=344) (actual time=49.561..57423.904 rows=1287 loops=1) ... -> CTE Scan on t (cost=0.00..5.00 rows=1 width=2252) (actual time=396.500..57429.582 rows=73 loops=1)
从计划中得知,CTE t生成数据1287行,最终过滤后得到数据73行。这里不仅有1200行数据无效,而且CTE包含的用户函数,被无效执行1200次,造成性能的主要问题。
造成这种现象的原因,就是子查询没有平面化。限制子查询平面化的,是用户函数属性,查询得知此函数属性是不稳定。
- 修改用户函数属性
select proname, CASE WHEN p.provolatile = 'i' THEN 'immutable' WHEN p.provolatile = 's' THEN 'stable' WHEN p.provolatile = 'v' THEN 'volatile' END as Volatility from zgf.pg_catalog.pg_proc p where proname = 'getfinanceamount';
proname | Volatility ------------------+------------ getfinanceamount | volatile (1 行记录)
alter function getfinanceamount stable;
- 优化后的计划
查询计划中,没有创建CTE t,已经与上层查询融合。
用户函数的执行次数是73次,节省时长 = (57423.904-49.561)/1287*(1287-73)=54120ms 。
2、查询所需的索引
如果没有适合的索引,查询就会读取全表
- 视图带来time累计计算的黑盒,在计划中,最终用时突然增长。
Hash Right Join (cost=40002.45..40004.42 rows=7 width=377) (actual time=427.929..3568.261 rows=73 loops=1)
这是视图用时造成的。独立执行视图代码,得知对大表使用了Seq Scan,根据过滤条件,建立适当的索引。
create index busi_t_reimburse_subject_i1 on busi_t_reimburse_subject (f9, economicsubjectname, nvl(projectsubjectname, 0));
优化后的查询计划,用时节省了3.0秒
Hash Right Join (cost=40002.45..40004.42 rows=7 width=377) (actual time=370.715..470.194 rows=73 loops=1)
- B-tree索引不支持字符模糊过滤
计划中,仍有较大的Seq Scan
Seq Scan on zgf.accounting_journal j (cost=0.00..27531.84 rows=4415 width=137) (actual time=0.022..104.008 rows=70505 loops=1) Filter: ((split_part((j.subject)::text, ' '::text, 1) ~~ '71010101%'::text) OR (split_part((j.subject)::text, ' '::text, 1) ~~ '71010102%'::text)) Rows Removed by Filter: 372087
这里使用了字符模糊匹配,需要建立gin型索引
create extension sys_trgm ; create index accounting_journal_subjectpre on accounting_journal USING gin (split_part((subject), ' ', 1) gin_trgm_ops);
优化后的查询计划,用时节省了100ms
BitmapOr (cost=806.92..806.92 rows=71038 width=0) (actual time=23.473..23.474 rows=0 loops=1) -> Bitmap Index Scan on accounting_journal_subjectpre (cost=0.00..329.41 rows=29522 width=0) (actual time=11.421..11.421 rows=70525 loops=1) Index Cond: (split_part((j.subject)::text, ' '::text, 1) ~~ '71010101%'::text)" -> Bitmap Index Scan on accounting_journal_subjectpre (cost=0.00..443.37 rows=41516 width=0) (actual time=12.051..12.051 rows=41362 loops=1) Index Cond: (split_part((j.subject)::text, ' '::text, 1) ~~ '71010102%'::text)"
- 增加索引
计划中,有较大的Seq Scan
Seq Scan on zgf.sys_t_department p (cost=0.00..122.62 rows=58 width=21) (actual time=0.481..0.521 rows=58 loops=1) Filter: ((p.unitsid = '825'::numeric) AND ((p.useflag)::text = '1'::text)) Rows Removed by Filter: 4717
创建B-tree索引
create index sys_t_department_unitsid on sys_t_department (unitsid);
优化后,用时节省0.1ms
Bitmap Index Scan on sys_t_department_unitsid (cost=0.00..4.72 rows=58 width=0) (actual time=0.011..0.011 rows=58 loops=1) Index Cond: (p.unitsid = '825'::numeric)
3、用时较长的node
计划中还有几处node,用时较长,可以尝试优化一处用时较长的node
Nested Loop (cost=21761.45..52337.05 rows=1 width=1793) (actual time=161.838..293.567 rows=47 loops=1) join : accounting_journalassist , accounting_journal , accounting_voucher 等
分析结果,因为视图的过滤条件,语法繁琐,浪费了CPU时间。
--原视图代码 CREATE FORCE VIEW view_reportquery AS SELECT ... FROM ... WHERE ... AND (DECODE((to_char((v.withto)::text))::character varying, NULL::character varying, "numeric"(0), text_numeric(to_char((v.withto)::text))) = (0)::numeric) AND (DECODE(v.sn, text_numeric(NULL::character varying), "numeric"(0), v.sn) > (0)::numeric) AND ((v.status)::text <> '4'::text)) GROUP BY ...; --新视图代码 CREATE OR REPLACE VIEW view_reportquery AS SELECT ... FROM ... WHERE ... AND nvl(v.withto, 0) = 0 AND v.sn > 0 AND ... GROUP BY ... ;
优化后,用时节省150ms
Nested Loop (cost=0.84..7334.49 rows=1 width=1793) (actual time=32.992..40.572 rows=47 loops=1) join : accounting_journalassist , accounting_journal , accounting_voucher 等
其他node的优化,对性能影响较小。
四、总结
- 执行计划,不能完整反应所有的细节,重视时长与数据块的超长增长,要将其视为病灶。
- 简单的表达式,具有最优的性能。
- 查询优化之路是没有止境,目标是平衡查询的性能与兼容性。