一、背景
这个复杂的查询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 函数的调用。
![](https://i-blog.csdnimg.cn/blog_migrate/425d6e9c5794bbc11bc6f4a5ab1db906.png)
以下是最终优化后的查询计划,用时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的优化,对性能影响较小。
四、总结
- 执行计划,不能完整反应所有的细节,重视时长与数据块的超长增长,要将其视为病灶。
- 简单的表达式,具有最优的性能。
- 查询优化之路是没有止境,目标是平衡查询的性能与兼容性。