By Franck Pachot
.
By a strange coincidence, I’ve encountered several cases of long parse time recently. That was the occasion to give a closer look at a CBO trace enhancement that came with April 2016 PSU: Bug 16923858 Diagnostic enhancement to annotate 10053 trace with accumulated parse time
I have a query that takes several seconds to parse and here is the new parse time information when you trace SQL_Compiler (the 10053 trace):
$ grep TIMER db003_ora_22231_orig.trc
TIMER: costing additional plans cpu: 1.807 sec elapsed: 2.019 sec
TIMER: costing general plans cpu: 26.131 sec elapsed: 27.747 sec
TIMER: costing additional plans cpu: 8.995 sec elapsed: 9.329 sec
TIMER: SU: costing SEL$A2089C65 cpu: 38.313 sec elapsed: 40.506 sec
TIMER: costing additional plans cpu: 1.523 sec elapsed: 1.850 sec
TIMER: SU: costing Interleaved CVM SEL$488B5694 cpu: 4.255 sec elapsed: 4.807 sec
TIMER: SU: Interleaved CVM SEL$A2089C65 cpu: 4.293 sec elapsed: 4.846 sec
TIMER: i costing additional plans cpu: 1.742 sec elapsed: 1.884 sec
TIMER: i costing general plans cpu: 21.343 sec elapsed: 21.843 sec
TIMER: i costing additional plans cpu: 8.072 sec elapsed: 8.375 sec
TIMER: i JPPD: costing SEL$A2089C65 cpu: 32.798 sec elapsed: 33.838 sec
TIMER: i JPPD: iteration (#1) SEL$A2089C65 cpu: 32.822 sec elapsed: 33.863 sec
TIMER: i costing general plans cpu: 22.088 sec elapsed: 22.751 sec
TIMER: i costing additional plans cpu: 5.484 sec elapsed: 5.745 sec
TIMER: i JPPD: costing SEL$A2089C65 cpu: 30.181 sec elapsed: 31.113 sec
TIMER: i JPPD: iteration (#2) SEL$A2089C65 cpu: 30.197 sec elapsed: 31.129 sec
TIMER: SU: Interleaved JPPD SEL$A2089C65 cpu: 63.026 sec elapsed: 64.999 sec
TIMER: SU: iteration (#1) SEL$A2089C65 cpu: 105.656 sec elapsed: 110.375 sec
TIMER: SU: costing SEL$EB7B6E47 cpu: 3.749 sec elapsed: 4.025 sec
TIMER: SU: iteration (#2) SEL$EB7B6E47 cpu: 3.762 sec elapsed: 4.038 sec
TIMER: CBQT SU and CVM SEL$1 cpu: 109.512 sec elapsed: 114.507 sec
TIMER: Cost-Based Transformations (Overall) SEL$62E0E936 cpu: 110.118 sec elapsed: 115.112 sec
TIMER: costing additional plans cpu: 1.247 sec elapsed: 1.485 sec
TIMER: Access Path Analysis (Final) SEL$62E0E936 cpu: 2.227 sec elapsed: 2.227 sec
TIMER: SQL Optimization (Overall) SEL$62E0E936 cpu: 114.674 sec elapsed: 119.906 sec
The default is to have a line when an operation takes more than 1 second. You can change that with the fix control:
This will trace all times > 10 microseconds:
alter session set "_fix_control"='16923858:1';
This will trace all times > 10 seconds:
alter session set "_fix_control"='16923858:7';
You get it: each level increases the threshold to the next order of magnitude, the default is 6 (1 second)
I’ve a small awk script to display the time in a better format:
cpu(ms) ela(ms)
42138.000 42159.000 costing general plans 12.1.0.2 640468
6416.000 6418.000 costing additional plans 12.1.0.2 719893
1509.000 1509.000 costing general plans 12.1.0.2 753749
2377.000 2377.000 or expansion check 12.1.0.2 763107
52698.000 52721.000 SU: costing SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10) 12.1.0.2 763120
2035.000 2035.000 costing general plans 12.1.0.2 842348
2642.000 2642.000 or expansion check 12.1.0.2 849927
4920.000 4921.000 SU: costing Interleaved CVM SEL$959A6A10 (VIEW MERGE SEL$5061678E; SEL$CAA3F13B) 12.1.0.2 849940
4941.000 4942.000 SU: Interleaved CVM SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10) 12.1.0.2 849942
42556.000 42568.000 i costing general plans 12.1.0.2 1482291
6348.000 6348.000 i costing additional plans 12.1.0.2 1561716
1653.000 1653.000 i costing general plans 12.1.0.2 1595574
2487.000 2488.000 i or expansion check 12.1.0.2 1604932
52924.000 52938.000 i JPPD: costing SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 1604945
52933.000 52947.000 i JPPD: iteration (#1) SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 1604956
1116.000 1116.000 i costing additional plans 12.1.0.2 1630694
33570.000 33576.000 i costing general plans 12.1.0.2 2098660
10614.000 10622.000 i costing additional plans 12.1.0.2 2211397
2092.000 2093.000 i costing general plans 12.1.0.2 2246743
1396.000 1397.000 i costing additional plans 12.1.0.2 2264252
3778.000 3780.000 i or expansion check 12.1.0.2 2265816
50741.000 50759.000 i JPPD: costing SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 2265829
50752.000 50769.000 i JPPD: iteration (#2) SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 2265839
103688.000 103719.000 SU: Interleaved JPPD SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 2265844
161339.000 161394.000 SU: iteration (#1) SEL$5061678E (SUBQUERY UNNEST SEL$93416A64; SEL$10)(COPY SEL$5061678E) 12.1.0.2 2265846
3250.000 3252.000 SU: costing SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2307338
3262.000 3264.000 SU: iteration (#2) SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2307349
164632.000 164689.000 CBQT SU and CVM SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2307498
164827.000 164884.000 Cost-Based Transformations (Overall) SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2307946
2577.000 2578.000 costing general plans 12.1.0.2 2387060
3204.000 3204.000 or expansion check 12.1.0.2 2393273
4597.000 4598.000 Access Path Analysis (Final) SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2393703
170872.000 170931.000 SQL Optimization (Overall) SEL$1 (PARSER)(COPY SEL$1) 12.1.0.2 2393730
Note that I’m more interested by the CPU time because the elapsed time includes the time to write to the trace.
I’m reading a 2 million line 10053 trace file here. The CBO takes time but is doing lot of work here.
My awk script also records the maximum time spend on each query block and then displays the ‘Query Block Registry’ with those times. This is a good way to understand which CBO transformation is responsible for most of the parse time:
cpu(ms) ela(ms) Query Block Registry:
0.000 0.000 SEL$10 0x897e9b8 (PARSER) [FINAL]
161339.000 161394.000 SEL$5061678E 0x0 (SUBQUERY UNNEST SEL$93416A64; SEL$10)
0.000 0.000 SEL$6F0423A9 0x0 (PUSHED PREDICATE SEL$CAA3F13B; SEL$5061678E; "VW_SQ_3"@"SEL$93416A64" 28)
0.000 0.000 SEL$959A6A10 0x0 (VIEW MERGE SEL$5061678E; SEL$CAA3F13B)
0.000 0.000 SEL$1DF60DFD 0x0 (QUERY BLOCK TABLES CHANGED SEL$10)
0.000 0.000 SEL$CAA3F13B 0x0 (SUBQ INTO VIEW FOR COMPLEX UNNEST SEL$1DF60DFD)
0.000 0.000 SEL$6F0423A9 0x0 (PUSHED PREDICATE SEL$CAA3F13B; SEL$5061678E; "VW_SQ_3"@"SEL$93416A64" 28)
0.000 0.000 SEL$959A6A10 0x0 (VIEW MERGE SEL$5061678E; SEL$CAA3F13B)
0.000 0.000 SEL$9 0x8987df0 (PARSER) [FINAL]
0.000 0.000 SEL$8 0x8988ee8 (PARSER) [FINAL]
0.000 0.000 SEL$7 0x898a4f8 (PARSER) [FINAL]
0.000 0.000 SEL$6 0x898c028 (PARSER) [FINAL]
0.000 0.000 SEL$5 0x898d268 (PARSER) [FINAL]
0.000 0.000 SEL$4 0x898eaf8 (PARSER) [FINAL]
0.000 0.000 SEL$3 0x89912b8 (PARSER) [FINAL]
0.000 0.000 SEL$2 0x89923c0 (PARSER) [FINAL]
170872.000 170931.000 SEL$1 0x89a3ef0 (PARSER) [FINAL]
0.000 0.000 SEL$93416A64 0x0 (VIEW ADDED SEL$1)
161339.000 161394.000 SEL$5061678E 0x0 (SUBQUERY UNNEST SEL$93416A64; SEL$10)
0.000 0.000 ...
The interesting point here is that the [FINAL] is the transformation that is chosen by the optimizer, so we know that we have spent time on a query block that has been finally chosen for the best plan.
Before going further, here is my ugly awk script that gives the above output from a 10053 trace file:
BEGIN{
print ; print " cpu(ms) ela(ms)"
}
/^ *optimizer_features_enable += +/{
ofe=$3 ; sub(/^[8-9][.]/,"0&",ofe)
delete qb
}
/TIMER/{
l=$0 ; sub(/ cpu: .*$/,"",l) ; sub(/TIMER: /,"",l)
sub(/^.* cpu: /,"") # --> $1 is cpu $4 is ela
q=gensub(/^.* /,"","g",l);
if ( qb[q] == "" ) { q="" } else { sub(/ [^ ]*$/,"",l) }
if ( q!="" && $1*1e3 > cpu[q] ) cpu[q]=$1*1e3
if ( q!="" && $4*1e3 > ela[q] ) ela[q]=$4*1e3
printf "%10.3f %10.3ft%-40s %20s %-60st%6s %sn",$1*1e3,$4*1e3,l,q,qb[q],ofe,NR
if ( q != "") versions[l" "q]=versions[l" "q] ofe"="($1*1e3)"msn"
}
/Query Block Registry/,/^:/{
if ( $0 ~/Query Block Registry/ ) { ; print "" ; print " cpu(ms) ela(ms) "$0 ; print "" }
else { printf "%10.3f %10.3ft%sn",cpu[$1],ela[$1],$0 }
}
So, I know that the unnesting of subquery SEL$10 is responsible for my long parse time. The ‘+alias’ format of the explain plan is an easy way to find which subquery it is comes from, :
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
64 - SEL$10
65 - SEL$10 / TABLE_WITH_DATES@SEL$10
66 - SEL$10 / TABLE_WITH_DATES@SEL$10
Here is the subselect (I redacted the table names).
AND begin_date =
(SELECT MAX (begin_date)
FROM table_with_dates
WHERE some_id = some_id
AND begin_date <= ...
)
So what can I do from now? I can avoid the subquery unnesting by adding the NO_UNNEST hin in the subquery, or add it to the main query as /*+ NO_UNNEST( @SEL$10) */
This really reduces the parse time, but I have to check that the plan is still acceptable. Actually we cannot blame the CBO for the time it takes here, because the goal of subquery unnesting (SU) is exactly that: open the way to lot of new access path, that can be cost based and may give a better execution plan. And an application should not parse too often, so spending a few seconds in parsing is not bad if it helps to execute the query quickly all over the day.
So don’t forget to have the latest PSU and trace with:
alter session set events 'trace [SQL_Compiler.*]';
explain plan for ...
alter session set events 'trace [SQL_Compiler.*] off';
get timer information with:
column value new_value tracefile
select value from v$diag_info where name='Default Trace File';
column value new_value clear
host grep TIMER &tracefile
And find which transformation is responsible for most of the parse time.