Oracle still chosen CHOOSE even no stats
Mike, April 28, 2002 - 2:06 am UTC
I creates a brand new table tt, no stats, But Oracle still chooses the CBO (CHOOSE).
Why? Unless RULE hint applied.
sql> create table tt as select * from dba_objects;
Table created.
sql> create index tt_oi_idx on tt(object_id);
Index created.
sql> /* there is no statistics collected */
sql> show parameter optimizer_mode
NAME TYPE VALUE
------------------------------------ ------- ------------------------------
optimizer_mode string CHOOSE
sql> select count(*) from tt where object_id>12000;
COUNT(*)
---------
8886
sql> select /*+ RULE */ count(*) from tt where object_id>12000;
COUNT(*)
---------
8886
TKPROF output:
select count(*)
from
tt where object_id>12000
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.02 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.10 21 22 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.02 0.12 21 22 0 1
Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 22
Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
8886 INDEX RANGE SCAN (object id 21570)
********************************************************************************
select /*+ RULE */ count(*)
from
tt where object_id>12000
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.01 0.01 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.01 0.01 0 22 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.02 0.02 0 22 0 1
Misses in library cache during parse: 1
Optimizer goal: RULE
Parsing user id: 22
Rows Row Source Operation
------- ---------------------------------------------------
1 SORT AGGREGATE
8886 INDEX RANGE SCAN (object id 21570)
********************************************************************************
April 28, 2002 - 9:58 am UTC
CHOOSE means choose -- that means "i will use the RBO if no stats are present and no constructs like partitioning are used -- some constructs force the use of CBO regardless".
In the above -- CHOOSE *is in fact rule* since you have a simple table with no statistics. The second query with RULE shows RULE for the simple reason that you "forced" rule to be used.
You can see this using AUTOTRACE better perhaps:
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t ( x int primary key );
Table created.
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t select rownum from all_users;
34 rows created.
ops$tkyte@ORA817DEV.US.ORACLE.COM> commit;
Commit complete.
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace traceonly explain;
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x < 5;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)
<b>there I can tell RBO was used simply by the lack of CARD/COST/BYTES.. see below for example of CBO with same query..</b>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ RULE */ * from t where x < 5;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=HINT: RULE
1 0 INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)
<b>Again, RBO, not CBO. Now a tkprof would say the first was run with choose (and it WAS, that is not at all inaccurate) and the second with RBO -- that again is correct, neither contradicts the other!</b>
ops$tkyte@ORA817DEV.US.ORACLE.COM> analyze table t compute statistics;
Table analyzed.
ops$tkyte@ORA817DEV.US.ORACLE.COM> select * from t where x < 5;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE <b>(Cost=1 Card=5 Bytes=10)</b>
1 0 INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE) (Cost=1 Card=5 Bytes=10)
<b>that additional info will appear ONLY when using the CBO. That shows that in this case, choose means CBO, not RBO. The tkprof would still show choose and it would be CORRECT.</b>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select /*+ RULE */ * from t where x < 5;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=HINT: RULE
1 0 INDEX (RANGE SCAN) OF 'SYS_C006757' (UNIQUE)
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> set autotrace off
<b>and back to rule again, nothing is amiss here, what you see is both normal and correct</b>
How to find out RBO or CBO
A reader, March 04, 2003 - 11:51 pm UTC
Hi Tom,
Your explanation on this topic has impressed me a lot. Now I have a scenario like this.
My database has the optimizer mode as CHOOSE.
Out of 700+ tables, only 10 major tables are analyzed. Now I would like to analyze all the tables.
My queries are:
1. Now I want to know with the current setup, how many sqls are choosen the RBO or CBO. In the V$sql view, we have a column optimizer_cost, can I assume that, at any particular point of time, the number of records in this view where the optimizer_cost=0 will give me the information that these many sqls are choosen for RBO. The same exercise I would like to do after analyzing all the tables, so that I will come to know that all my sqls are using CBO.
2.There are 3 application users in my database. First I would like to analyze all the objects for one user. So When I have tried this procedure
dbms_stats.gather_schema_stats('username')
in my development server, I found that only the tables are analysed and not the indexes( This info i have seen from the user_tables and user_indexes). So please let me know which procedure I have to use inorder to analyze one schema.
Thanks in Advance
SUBBARAO.
March 05, 2003 - 7:45 am UTC
1) well, you need look at optimizer_mode as well, a cost of zero is a valid cost for a CBO query. Need to make sure optimizer mode was rule or choose. But it seems like a useless statistic to have at hand. Why does it matter if you have 5032 queries using RBO and 5254 using CBO -- what can you do based on that fact?
I would not rely on this count 100%, even under choose -- a cost of zero is valid for a CBO query. In 9i v$sql_plan will unambigously help us out here (the cost/card/bytes are only present for cbo queries)
2) add cascade=>true (you can read the supplied packages guide to discover all of the options for dbms_stats)
Measuring the performance after analyze
A reader, March 06, 2003 - 2:43 am UTC
Hi Tom,
My Intention is not to collect the count, My main intention is this, Now that i didnt anlayze 99% of my tables in my database, Now I want to analyze all the objects. There is some way to measure out the performance after analyzing the table right?. My boss is not understanding the advantage of analyzing the tables. So I should prove him that after anlayzing this is the status.
I am not asking this mainly to show it to my boss, whereas even I would like to measure the performance difference.
Thanks in advance
SUBBARAO.
March 06, 2003 - 7:40 am UTC
you are not doing this in production right -- this is a test system, where you have scripts setup to benchmark your application, report back that things work correctly, how fast or slow things went right?
cause if this is a production box and you are just going to do this -- expect to have a very very very long week ahead of you.
But anyway -- on you TEST system, collect some statspacks during a normal test load, analyze, collect statspack again and compare the outputs.
Clarifications.
A reader, March 06, 2003 - 8:39 pm UTC
Hi Tom,
I am planning to do this in the productions system. Right now I have already collected the statspack for a weeks time, after anlayzing also I will collect the statspack and compare the outputs.
If it is a production system why you are telling this
"cause if this is a production box and you are just going to do this -- expect to have a very very very long week ahead of you."
What kind of problem you are expecting in a general OLTP system. And mainly what figures we need to look at in the statspack. What I thought is to see only the expensive SQLS in the statspack. is it enough?
Thanks in Advance
SUBBARAO
March 07, 2003 - 7:38 am UTC
the problems you would expect doing this to a production system are not a concern if you do this to a test system
anytime you do something untested to production, anything at all, expect a very very long week.
Is RBO the same as hint /*+RULE*/?
A Reader, March 18, 2003 - 10:29 am UTC
I learn that the RBO will be eliminated in future Oracle release and we are trying to use CBO as much as possible. The optimizer_mode set to choose in our database. We have statistics on every table and we update the statistics to keep them update. I assume that would lead to the use of CBO. However, there are some SQL queries, we cannot make them run faster unless we use the /*+RULE*/ hint (even I just analyzed all the tables involved). Using other CBO hint like /*+FIRST_ROWS*/ etc. will make the performance worse. My question: is the /*+RULE*/ hint going to go away in future release? Is the hint he same as using RBO and should we use it?
Thank you!
RBO *gone* ? Rumor?
Jeff Evans, March 18, 2003 - 11:47 am UTC
You wrote:
You need to find out *why* the queries are not functioning well with CBO. Using
the RULE hint uses the RBO and the RBO is *gone* in the next release.
Does this mean that the next major release of Oracle will not have the RBO? I've heard rumors of this but I haven't ever got 100% confirmation.
March 18, 2003 - 12:53 pm UTC
RBO is gone. Metalink announced this a while ago.
CBO=Put common sense on the side?
A reader, July 06, 2004 - 5:56 pm UTC
Some of our DW/BI-type queries/apps, etc have had dramatic performance benefits from using the Oracle 9i/CBO. So much so, that many developers are now keeping common sense aside when constructing their reports/queries. Join everything in sight, aggregate up the wazoo, use DISTINCT unnecessarily, etc. Given the volume of our data so far, CBO is indeed able to handle everything we throw at it.
My question is: Given all the smarts built into CBO, is it really safe to say that common-sense/intelligence is now "obsolete"? Just write queries any way you want and the CBO will just take care of it?
Thanks for your comments
July 06, 2004 - 7:54 pm UTC
common sense (which isn't so common) is still the most important attribute one can have.
I would not say "just write queries any way you want" will every be true.
Selt-tuning SQL
A reader, July 06, 2004 - 9:51 pm UTC
Well, I was thinking about the new dbms_sqltune package in 10g when I wrote that.
If the database can recognize bad sql, offer suggestions to improve it and rank those suggestions, all things which dbms_sqltune is designed to do, well, then we are just one step away from doing
alter session set implement_sqltune=true
and voila! we have a sentient database that replaces us humans!
:-)
July 07, 2004 - 7:49 am UTC
Here is the analogy I have for that (not really an analogy, a true story -- ok, not an analogy at all i guess).....
doing a benchmark. given this code:
insert into t ( c1, c2, .... )
select c1, c2, ....
from t1, t2, t3, t4, ....
where ....;
loop
delete from t
where (c1,c2) in ( select c1, min(c2)
from t
group by c1
having count(1) > 1 );
exit when sql%rowcount = 0;
end loop;
Now, a "self tuning database" can take the insert as select and make it "as good as it gets". It can take the delete and do the same...
But -- it won't recognize that the logic done by the client (remember, client could be java, pro*c, whatever -- not just plsql -- the server sees only discrete SQL, not algorithms) can be summed up as:
o take the query in the insert and
o remove the rows with the older C2 have the same C1
o additionally, if all of the rows for a given C1 have the same C2,
remove these rows as well.
In the benchmark, the insert as select took a while. The delete however took hours. worse yet, if the delete found a single row to actually remove -- it had to run all over again.
So, by analyzing the algorithm here and listing the rules out in plain english - i developed:
insert into t ( c1, c2, .... )
select c1, c2, ......
from
( select c1, c2, .... ,
max(c2) OVER ( partition by c1 ) max_c2
count(c2) OVER ( partition by c1, c2 ) cnt
from t1, t2, t3, t4, ....
where .... )
where c2 = max_c2
and cnt = 1;
using analytics to find the max(c2) by c1 (we wanted the max) and count the number of rows by c1,c2 -- we were able to take the entire set of procedural logic and reduce it to the original insert into as select.
It ran in about the same amount of time the original insert did. Meaning, we cut off at least hours of processing time.
How? by tuning "a query"
No, not close. By tuning "the approach"
Is good design dead? I hope not, I don't have that much time to waste, do you?
Brilliant!
A reader, July 07, 2004 - 9:39 am UTC
Tom, you are the best. Brilliant example, as usual.
You hit the nail on the head. Of course, the server can see only discrete SQL, not algorithms. Clearly, the database cannot rewrite "the approach", that would be like the AI-type computers in Star Trek!
However, that is not what I was referring to.
When faced with discrete SQL (which is what dbms_sqltune works with anyway), all I was saying (albeit slightly tongue-in-cheek), is that the database could take its own suggestions and implement them. I mean all the typical newbie-type SQL errors when using IN/EXISTS, correlated subqueries, etc. In all these cases, dbms_sqltune could rewrite the query to provide the same answer with much lesser LIO
Again, the reason I bring all this up is that 10g has made great strides as a "self managing" database from a DBA perspective but stupid (badly written) queries can still bring the database to its knees!
The future of CBO
Gary, July 07, 2004 - 7:54 pm UTC
"typical newbie-type SQL errors when using IN/EXISTS"
I'm uncomfortable with calling those 'errors'. It's not doing anything incorrectly, even though it may be doing it inefficiently. And if it is doing it inefficiently, that is as more because of the way the query was interpreted than the way it was written.
There is no logical difference between "select col_a from table_b where col_c in (select col_d from table_e)" and "select col_a from table_b where exists (select 1 from table_e where table_e.col_d = table_b.col_c)"
The RBO treated them differently so as to give the developer the opportunity to tell the optimizer how best to get to the data needed. Sort of like using hints with the CBO. As the CBO gets brighter, it needs less direction.
Ideally, the optimizer should give the same plan (the quickest) to a query and any query that is logically identical (ie would give the same result set for all potential table contents).
As the optimizer should have more up-to-date information about the table contents when it creates the plan, in theory, it should be capable of producing a better plan than the writer of the SQL code might anticipate.
In practice, for complex queries, the optimizer will only be able to see a subset of all possible plans, so I think there will always be room for some direction from SQL writers.
As for 'common sense', it is still more important to be 'asking the database the right question' rather than 'posing the question in the best way'.
PS. When Tom can ask his interview question of "Give me the name of the oldest employee" directly to the optimizer, and it responds with "What if there's more than one ?", that's when we are all out of a job :)
July 07, 2004 - 8:26 pm UTC
I agree with everything said .....
:)
To Gary
A reader, July 10, 2004 - 12:07 am UTC
<quote>
In practice, for complex queries, the optimizer will only be able to see a subset of all possible plans, so I think there will always be room for some direction from SQL writers.
<quote>
I disagree. The whole modus operandi of the CBO is brute force evaluation of the entire universe of all possible join methods, predicate selection, elimination, rewrite, etc, etc. As part of this costing exercise, it can, IMHO, easily _consider_ rewriting, for example, an IN query as EXISTS or vice versa and use the less costly one, without the SQL writer saying so. As you rightly put it, data changes and as long as the user keeps the stats up to date, the CBO is in a position to generate the optimal plan, which might be better than even the programmer anticipated. Isnt that one of the main reasons to be using the CBO in the first place? To generate optimal plans given changing data?
"As for 'common sense', it is still more important to be 'asking the database the right question' rather than 'posing the question in the best way'"
Disagree again. It is my data. You cant tell me that my question is "not right". I have the right to ask any question of the database, and as long as it is syntactically accurate and unambiguous, it is the CBO's job to interpret the question in light of current data distribution, apply all possible costing, rewrites, dbms_sqltune, best-practice heuristics to the question and give me the answer in the shortest possible time.
July 10, 2004 - 9:19 am UTC
you can disagree but you are wrong.
the combinations of possible plans explodes rapidly into the BILLIONS and beyond -- even for simple queries. It cannot and does not consider each and every join method, permutation and so on.
A reader, July 10, 2004 - 9:37 am UTC
"the combinations of possible plans explodes rapidly into the BILLIONS and beyond -- even for simple queries. It cannot and does not consider each and every join method, permutation and so on"
Yes, I realize that. So instead of evaluating and costing the billions of plans, the CBO applies some heuristics and decides to evaluate only a subset of those (optimizer_max_permutations).
All I am saying is that as part of those heuristics, it _could_ consider rewriting a IN as EXISTS (or vice versa). As you yourself say, depending on the data, IN might be more efficient on Monday and EXISTS might be more efficient on Tuesday. That _is_ what the CBO should do, right? Adapt to changing data. Along the same lines, it _could_ consider rewriting it as per its own dbms_sqltune recommendations (configurable, of course, since this could dramatically slow down the parse time). And finally, it could say something like 'alter session set become_tom=true' and apply even more intelligent heuristics!
All I am saying is that while I agree that intelligence and common sense is not totally obsolete, there could be a sort of expert system/knowledge base-based aspect to the CBO that would enable it to "learn" over time, to enable it to become more smart over time, to "grow beyond what Oracle developers programmed it for". Something like a neural network in AI-speak! So, for example, the CBO in one environment could be smarter than the CBO in another environment simply because it has more "experience".
Thanks
July 10, 2004 - 8:46 pm UTC
and it does things like choose not to use transitivity and many many many other things.
it could and does -- when appropriate. today, using the CBO for all intents and purposes -- in and exists are the same.
However, it will not (is not) be smart enough to take:
select *
from t
where date_field = ( select max(date_field)
from t t2
where t2.key_field = t.key_field )
and turn it into something like:
select *
from ( select t.*, max(date_field) over (partition by key_key) max_date_field
from t)
where date_field = max_date_field;
that is where the developer comes back in (and of course, it'll never really be able to take some poor procedural algorithm and recognize "ah hah, it is really just this single bulk sql statment!)
Fine
A reader, July 11, 2004 - 12:13 am UTC
Why wont it rewrite the former with the latter using analytic functions if that is indeed the less expensive one? Is 10g's dbms_sqltune able to come up with the latter as a more efficient alternative for the former? If so, maybe 11g's CBO will indeed have an option to do this automatically!
Let me ask you this: The CBO has kept improving by leaps and bounds over the last few years. You seem to be saying that "everything that can be done, the CBO already does". So, what do you think the CBO in Oracle 15"g" (or whatever the letter of the year is) will do? Where does the CBO have room for improvement? You never seem to concede that there is any, ever!
July 11, 2004 - 9:41 am UTC
because there are dozens of ways to rewrite that query. for example, an even "faster" approach is generally:
select substr( data, n, m ),
substr( data, a, b ),
...
key_field
from (
select max( to_date(date_field,'yyyymmddhh24miss') ||
rpad( field1, ... ) ||
rpad( field2, ... ) ||
..... ) data,
key_field
from t
group by key_field
)
but it relies on "a single max date_field per key". It is a big leap from query 1 to query 2 in many cases.
I've never seen a sql tuning tool (from anyone) rewrite a query using analytics.
I am NOT saying everthing that can be done -- I don't see anywhere I've even indicated that.
I am saying:
In practice, for complex queries, the optimizer will only be able to see a
subset of all possible plans, so I think there will always be room for some
direction from SQL writers.
plain and simple. I think there will always be room for some direction from SQL writers.
You can use your intimate knowledge of something to make this "be so" (eg: you might know that the rewrite I did above gets you the answer you want, the optimizer cannot rewrite to that query because semantically it is not the same as the first)
I've written, on paper -- on the web -- everywhere, that the CBO gets better and better and better with each release.
I've also written that given a cruddy data model, poor though to how the data is layed out, little effort to learn SQL (hey -- the coder might outer join everything -- i see that time and time and time and time and time again, what are we to do? our rewrite ability is crippled due to the semantics of their query) is a sure path to poor performance.
Garbage in, Garbage out. Many constructs we had to take care with (in versus exists, not in versus not exists) have been fixed for us -- but the fundementals, they will be in our court for a while.
Just like an optimizing compiler can make the code you write as fast as possible -- if you write cruddy code, it'll still be lots slower than a finely tuned algorithm (which i keep coming back to, if you run a query 1,000,000 times and it runs in 1/1000 of a second, it'll still take 1,000 seconds).
Thanks
A reader, July 11, 2004 - 9:58 am UTC
Thanks, I understand now.
1. But I am curious to know, given your vast knowledge and mastery of SQL, if you were a Oracle kernel developer, where do you think the CBO can do things better?
2. As someone who works for Oracle and might know the future direction for subsequent releases, have you seen a "sneak preview" (even just in theor) of the CBO in, say, 11g or 12g, etc?
Thanks a lot
July 11, 2004 - 10:09 am UTC
1) I'm not an optimizer guy actually -- query optimization is an art form unto itself. they do not think in terms of "analytics" vs "correlated subquery", it is much deeper than that.
we are dealing with trivial examples -- they have to deal with the 15 table join with predicates that go on for pages.
2) i get the beta's but that is about it. I don't want to be further out then the rest of us actually. I know in general what is being worked on (typically different from one actually gets released :)
Tuning question
A reader, July 14, 2004 - 4:13 pm UTC
Another tuning question...where the CBO plan drastically changes after adding a value in the IN predicate. Let me explain, I have a view like
CREATE OR REPLACE VIEW ALL_MISC_CASH_TRANSACTIONS
AS
SELECT
a.transactionid,
a.translink,
a.cashdate,
a.groupid,
a.controllerid,
b.thisaccttxn,
b.accountnum
FROM cgtmplttbl a,cgalttmplttbl b
WHERE a.transactionid=b.transactionid
UNION
SELECT
a.transactionid,
a.translink,
a.cashdate,
a.groupid,
a.controllerid,
b.thisaccttxn,
b.accountnum
FROM cashtrans a,cashtransalt b
WHERE a.transactionid=b.transactionid;
I have a query like
select TRANSACTIONID, TRANSLINK, CASHDATE, GROUPID, CONTROLLERID from all_misc_cash_transactions
where groupid in (11,12,13,14)
This has the following plan and it executes in less than a second
497 rows selected.
--------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)|
--------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5941 | 353K| | 1244 (1)|
| 1 | VIEW | ALL_MISC_CASH_TRANSACTIONS | 5941 | 353K| | |
| 2 | SORT UNIQUE | | 5941 | 1596K| 3656K| 1244 (1)|
| 3 | UNION-ALL | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID | CGALTTMPLTTBL | 1 | 44 | | 2 (50)|
| 5 | NESTED LOOPS | | 5923 | 1590K| | 995 (0)|
| 6 | INLIST ITERATOR | | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| CGTMPLTTBL | 4885 | 1101K| | 18 (0)|
|* 8 | INDEX RANGE SCAN | CGTMPLT_GROUP | 4885 | | | 1 (0)|
|* 9 | INDEX RANGE SCAN | CGALTTMPLTTBL_TXNID | 1 | | | 1 (0)|
| 10 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | 2 | 88 | | 2 (50)|
| 11 | NESTED LOOPS | | 18 | 5598 | | 4 (0)|
| 12 | INLIST ITERATOR | | | | | |
| 13 | TABLE ACCESS BY INDEX ROWID| CASHTRANS | 11 | 2937 | | 2 (50)|
|* 14 | INDEX RANGE SCAN | ICCSHTXNGROUPID | 12 | | | 1 (0)|
|* 15 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | 2 | | | 1 (0)|
--------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
8 - access("A"."GROUPID"=11 OR "A"."GROUPID"=12 OR "A"."GROUPID"=13 OR "A"."GROUPID"=14)
9 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
14 - access("A"."GROUPID"=11 OR "A"."GROUPID"=12 OR "A"."GROUPID"=13 OR "A"."GROUPID"=14)
15 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
Statistics
----------------------------------------------------------
7 recursive calls
0 db block gets
380 consistent gets
0 physical reads
0 redo size
13675 bytes sent via SQL*Net to client
1014 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
497 rows processed
Now when I change the query to
select TRANSACTIONID, TRANSLINK, CASHDATE, GROUPID, CONTROLLERID from all_misc_cash_transactions
where groupid in (10,11,12,13,14)
The plan changes to
------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)|
------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 7430 | 442K| | 1499 (1)|
| 1 | VIEW | ALL_MISC_CASH_TRANSACTIONS | 7430 | 442K| | |
| 2 | SORT UNIQUE | | 7430 | 1996K| 5944K| 1499 (1)|
| 3 | UNION-ALL | | | | | |
| 4 | TABLE ACCESS BY INDEX ROWID | CGTMPLTTBL | 1 | 231 | | 1187 (1)|
| 5 | NESTED LOOPS | | 7404 | 1988K| | 1187 (1)|
| 6 | TABLE ACCESS FULL | CGALTTMPLTTBL | 131K| 5669K| | 58 (0)|
| 7 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 8 | BITMAP AND | | | | | |
| 9 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 10 | INDEX RANGE SCAN | CGTMPLTTBL_TXNID | 1 | | | |
| 11 | BITMAP OR | | | | | |
| 12 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 13 | INDEX RANGE SCAN | CGTMPLT_GROUP | 1 | | | |
| 14 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 15 | INDEX RANGE SCAN | CGTMPLT_GROUP | 1 | | | |
| 16 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 17 | INDEX RANGE SCAN | CGTMPLT_GROUP | 1 | | | |
| 18 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 19 | INDEX RANGE SCAN | CGTMPLT_GROUP | 1 | | | |
| 20 | BITMAP CONVERSION FROM ROWIDS| | | | | |
|* 21 | INDEX RANGE SCAN | CGTMPLT_GROUP | 1 | | | |
| 22 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | 2 | 88 | | 2 (50)|
| 23 | NESTED LOOPS | | 26 | 8086 | | 5 (0)|
| 24 | INLIST ITERATOR | | | | | |
| 25 | TABLE ACCESS BY INDEX ROWID | CASHTRANS | 17 | 4539 | | 2 (50)|
|* 26 | INDEX RANGE SCAN | ICCSHTXNGROUPID | 17 | | | 1 (0)|
|* 27 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | 2 | | | 1 (0)|
------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
10 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
13 - access("A"."GROUPID"=10)
15 - access("A"."GROUPID"=11)
17 - access("A"."GROUPID"=12)
19 - access("A"."GROUPID"=13)
21 - access("A"."GROUPID"=14)
26 - access("A"."GROUPID"=10 OR "A"."GROUPID"=11 OR "A"."GROUPID"=12 OR "A"."GROUPID"=13 OR "A"."GROUPID"=14)
27 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
and the query just doesnt return at all. I waited for 10 minutes.
Why does the plan tank so badly by adding the groupid 10 to the predicate? What the heck are all the bitmap conversions in the plan?
The RBO plan for both queries is
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | |
| 1 | VIEW | ALL_MISC_CASH_TRANSACTIONS | | | |
| 2 | SORT UNIQUE | | | | |
| 3 | UNION-ALL | | | | |
| 4 | CONCATENATION | | | | |
| 5 | TABLE ACCESS BY INDEX ROWID | CGALTTMPLTTBL | | | |
| 6 | NESTED LOOPS | | | | |
| 7 | TABLE ACCESS BY INDEX ROWID| CGTMPLTTBL | | | |
|* 8 | INDEX RANGE SCAN | CGTMPLT_GROUP | | | |
|* 9 | INDEX RANGE SCAN | CGALTTMPLTTBL_TXNID | | | |
| 10 | TABLE ACCESS BY INDEX ROWID | CGALTTMPLTTBL | | | |
| 11 | NESTED LOOPS | | | | |
| 12 | TABLE ACCESS BY INDEX ROWID| CGTMPLTTBL | | | |
|* 13 | INDEX RANGE SCAN | CGTMPLT_GROUP | | | |
|* 14 | INDEX RANGE SCAN | CGALTTMPLTTBL_TXNID | | | |
| 15 | TABLE ACCESS BY INDEX ROWID | CGALTTMPLTTBL | | | |
| 16 | NESTED LOOPS | | | | |
| 17 | TABLE ACCESS BY INDEX ROWID| CGTMPLTTBL | | | |
|* 18 | INDEX RANGE SCAN | CGTMPLT_GROUP | | | |
|* 19 | INDEX RANGE SCAN | CGALTTMPLTTBL_TXNID | | | |
| 20 | TABLE ACCESS BY INDEX ROWID | CGALTTMPLTTBL | | | |
| 21 | NESTED LOOPS | | | | |
| 22 | TABLE ACCESS BY INDEX ROWID| CGTMPLTTBL | | | |
|* 23 | INDEX RANGE SCAN | CGTMPLT_GROUP | | | |
|* 24 | INDEX RANGE SCAN | CGALTTMPLTTBL_TXNID | | | |
| 25 | CONCATENATION | | | | |
| 26 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | | | |
| 27 | NESTED LOOPS | | | | |
| 28 | TABLE ACCESS BY INDEX ROWID| CASHTRANS | | | |
|* 29 | INDEX RANGE SCAN | ICCSHTXNGROUPID | | | |
|* 30 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | | | |
| 31 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | | | |
| 32 | NESTED LOOPS | | | | |
| 33 | TABLE ACCESS BY INDEX ROWID| CASHTRANS | | | |
|* 34 | INDEX RANGE SCAN | ICCSHTXNGROUPID | | | |
|* 35 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | | | |
| 36 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | | | |
| 37 | NESTED LOOPS | | | | |
| 38 | TABLE ACCESS BY INDEX ROWID| CASHTRANS | | | |
|* 39 | INDEX RANGE SCAN | ICCSHTXNGROUPID | | | |
|* 40 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | | | |
| 41 | TABLE ACCESS BY INDEX ROWID | CASHTRANSALT | | | |
| 42 | NESTED LOOPS | | | | |
| 43 | TABLE ACCESS BY INDEX ROWID| CASHTRANS | | | |
|* 44 | INDEX RANGE SCAN | ICCSHTXNGROUPID | | | |
|* 45 | INDEX RANGE SCAN | ICCSHTXNALTTXNID | | | |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
8 - access("A"."GROUPID"=14)
9 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
13 - access("A"."GROUPID"=13)
14 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
18 - access("A"."GROUPID"=12)
19 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
23 - access("A"."GROUPID"=11)
24 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
29 - access("A"."GROUPID"=14)
30 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
34 - access("A"."GROUPID"=13)
35 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
39 - access("A"."GROUPID"=12)
40 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
44 - access("A"."GROUPID"=11)
45 - access("A"."TRANSACTIONID"="B"."TRANSACTIONID")
Note: rule based optimization
Well, the plan with the 10 has 2 more access predicates at the bottom of the plan but it is basically the same plan...a concatenation of all the individual groupids.
I gathered stats on all the tables using dbms_stats, cascade=>true and 'for all columns size auto'
The data distribution for the groupids accessed is
select groupid,count(*) from cgtmplttbl
where groupid in (10,11,12,13,14)
group by groupid;
GROUPID COUNT(*)
10 141
13 79
14 209
So, adding 10 to the IN clause shouldnt really change things this drastically!
I know the CBO is zillion times smarter than RBO and everything but it is these kinds of things that start a FUD out there, just adding another value to a existing IN clause makes the query tank badly!
Help!!!
July 15, 2004 - 11:30 am UTC
how many distinct values for groupid all together?
and where are your bind variables?
A reader, July 15, 2004 - 11:59 am UTC
July 15, 2004 - 1:31 pm UTC
use one of those techniques -- if you execute this query more than once or twice that is.
try instead of size auto, size 254
Tuning question
A reader, July 15, 2004 - 1:40 pm UTC
But my question had nothing to do with using bind variables.
Yes, if the first encounter of this query since my instance started were to use IN (11,12,13,14), I would get the good plan and if I use using bind variables using one of the techniques in that link, even if I use IN (10,11,13,13,14), I would get the same plan.
But that is relying on chance! What if the first encounter of this query were to be IN (10,11,12,13,14) and the plan was that horrible plan! Now I am screwed for every execution of this query!
Anyway, my question was about why that one plan was so bad?
July 15, 2004 - 6:06 pm UTC
sure it does.
did you do the histograms either
a) by not doing them
b) the way I asked you to try?
A reader, July 15, 2004 - 6:41 pm UTC
Hm, I just reanalyzed the tables involved in the query and now the plan is fine! This is really puzzling since I have a nightly job that does 'gather stale' and all my tables are set to monitoring so the stats should be up to date anyway.
I know you dont like myths like these, but on more than one occasion I have seen query performance improve by simple gathering stats when the gather stale job should have done the same thing.
On a related note, is there any disadvantage to always doing
method_opt=>'FOR ALL COLUMNS SIZE 254'
i.e. using 254-bucket histograms always?
Thanks
July 15, 2004 - 6:49 pm UTC
histograms can be tricky things -- they are done differently if there are more values than buckets and vice versa. Especially when there are more values than buckets -- it can be really confusing -- say x=10 had 5 values, x=11 has 1,000,000 and x=12 has 5. it could well be since 10,11,12 are "contigous" they get thrown into the same bucket -- just because there are not enough buckets to go around.
this is what I hypothesize happened here, auto went too small and put things into grouped buckets -- leading the optimizer astray.
that size I used was just "larger than the set of values you had", it ensured in your case with 98 discrete values, each would have its own bucket - estimates of rows returned should be fairly accurate .
I might not do "for all columns", most of the times either:
o no columns
o indexed columns
is more than sufficient (computing histograms is really expensive, hardest part of the analyze)
Thanks
A reader, July 15, 2004 - 10:34 pm UTC
1. So if I do determine that I need to gather histograms 'for all indexed columns', is there any harm in always using 254 (the maximum allowed) buckets?
2. In general, what is the use of analyzing 'for all columns'? If a column is not indexed, what use does the CBO make of knowing the data distribution of that column? The access path for that column is always FTS, so why do histograms matter for unindexed columns?
Thanks
July 16, 2004 - 10:46 am UTC
1) if you have columns with more than 254 values -- you may well find you achieve different results with different bucket sizes due to the grouping that goes on. You might find you need to tweak some to be different.
2) in a data warehouse where you don't necessarily use binds (queries are run once or at least infrequently if repeated -- not like a transactional system), histograms come into play heavily to predict the cardinality returned from full scans and such.
select * from a, b where (join) and a.non_index_col = 5;
histograms would be used to decide if A or B should drive the hash join that should result (or if perhaps a nested loops should be used from b to a)
deleting statistics?
Reader, July 16, 2004 - 3:00 pm UTC
I'm curious about the discussion here regarding those
who have their optimizer_mode=choose and want to revert
to rbo because some queries run better with RULE.
I believe Tom answered switching back to RBO is "trivial"
along with deleting statistics??
how does one go about deleting statistics once they've
been analyzed and recorded.
thanks
July 16, 2004 - 10:54 pm UTC
use the delete subroutines in dbms_stats? use the analyze delete command?
same commands that gather, are used to remove.
converting from RBO to CBO
Always learning, August 22, 2004 - 2:59 pm UTC
Great information for someone looking to convert RBO to CBO.
CBO is still not there
A reader, November 05, 2004 - 3:13 pm UTC
Oracle 9.2.0.4
Over the last few months, since we upgraded from 8.1.7.4 to 9.2.0.4, I have encountered a dozen relatively simple queries involving no more than 3-4 tables that the CBO just cannot get right. Up to date stats, no hints, CBO has all the information it needs, but it just cannot come up with a good plan. I tweaked the optimizer_* parameters, worked with Oracle Support, all to no avail.
In every single case, switching to RBO for that query fixed it right up! In every single case, Support asked me to try upgrading to the lastest patchset (9.2.0.6 as of today)! I thought 9.2.0.4 was pretty stable thats why I waited so long to upgrade from 8.1.7.4! :-(
I am afraid that developers will "give up" on this CBO thingy and start to embed /*+ rule */ all over the place even when CBO works fine 95% of the time!
Comments? Thanks
November 05, 2004 - 5:46 pm UTC
many things were corrected in 9205/9206 -- you should never have gone with 9204 as 9205 at the very least was out there when you started this. latest production (in fact, should have definitely been 10gr1, i would not have considered 9i at this point in time really)