Oracle FAQ | Your Portal to the Oracle Knowledge Grid |
Home -> Community -> Usenet -> c.d.o.misc -> Re: Number 1 gripe about CBO: 0 <Cardinality< 1 (?????)
On Apr 6, 10:31 am, "Kevin Kirkpatrick" <kvnkrkpt..._at_gmail.com> wrote:
> On Apr 4, 10:10 pm, "Charles Hooper" <hooperc2..._at_yahoo.com> wrote:
> > 1) SYSDATE BETWEEN NVL(START_DATE_ACTIVE,SYSDATE) AND
> > NVL(END_DATE_ACTIVE,SYSDATE)
> > 2) AND UPPER(KK_SMALL.HUGE_VAL) LIKE '%ALU%'
> > 3) AND UPPER(KK_SMALL.HUGE_VAL) LIKE '%AL%'
> > 4) AND KK_HUGE.HUGE_VAL = KK_SMALL.HUGE_VAL
> > 5) AND KK_BIG.ID = KK_HUGE.ID;
>
> > Selectivity:
> > 1) 0.05 * 0.05
> > 2) * 0.05
> > 3) * 0.05
> > = 0.05^4 = 0.00000625
>
> Right - Everything is perfectly sensible up until this point. The
> selectivity problem is mostly tied to the condition
> "SYSDATE BETWEEN NVL(START_DATE,SYSDATE) AND NVL(END_DATE,SYSDATE)"
>
> which can be expressed as
>
> "(START_DATE IS NULL OR START_DATE < SYSDATE) AND (END_DATE IS NULL OR
> END_DATE > SYSDATE)"
I have included this run below to see how it compares on an otherwise
idle system.
> to give identical results, but without confusing the CBO (which can
> use stats/histograms on the latter to give accurate selection
> estimates but, given the former condition, will ignore all stats/
> histograms and yield a selectivity of (0.05 * 0.05) for the
> condition).
>
> My objection is not how the CBO gets the excessive selectivity, but
> rather how it applies the number:
>
> > Expected number of rows = 0.00000625 * 500
>
> At this point, I think the CBO approach is flawed. We all know no
> WHERE clause, no matter how small the starting cardinality and no
> matter how many conditionals, can ever return a fraction of a row. I
> believe the logic of the CBO should be:
>
> Expected number of rows = ceil(0.00000625 * 500)
>
> That is my gripe - the CBO uses floating point values to represent
> cardinality estimates when, based on a proper understanding of the
> problem domain, it ought to be using discrete integral values of the
> most likely non-zero cardinality. The latter approach would avoid
> bizarre decision making as exemplified in the above example. In fact,
> the CBO developers seemed to know that fractional cardinality
> estimates are flawed - notice that on the explain plan displayed to
> the world, the value "1" is displayed even though behind the scenes
> CBO is using "0.00003"
I have an unrelated code sample that will cause the 10053 trace file to show a cardinality of 0.5 for one of the tables in a SQL statement that references three tables. This apparently causes a number of overflows and leads to an ORA-600 in 10.2.0.2 (not tested yet in 10.2.0.3). I understand your argument and mostly agree with you. However, what if Oracle is most of the time effectively using these partial row calculations to correctly reduce the cardinality coming out of 95% of the join operations where they occur (I am NOT stating this as fact, just offering it for consideration).
> <snip>
>
> > It is still taking about 49 seconds to execute, so the MERGE JOIN
> > CARTESIAN was not as damaging as initially expected.
>
> Your hint replaced the MERGE JOIN cartesian product with a NESTED LOOP
> cartesian product (tomayto / tomahto). Try this hint: /*+
> CARDINALITY(kk_small,1) */, which will force the CBO to use "1" as the
> expected rows after applying conditions to kk_small (as I believe it
> ought to do). Also, to see the real damage, make kk_big 100,000 rows
> and kk_huge 1,000,000 rows.
The different join methods: nested loops, sort-merge, and hash do not offer the same performance as the number of rows involved in the join changes. The sort-merge join can be very time consuming when two large data sets are joined. Samples are provided below.
Test runs of different methods:
Original SQL statement:
SELECT /*+ GATHER_PLAN_STATISTICS */
*
FROM
KK_SMALL, KK_BIG, KK_HUGE
Cost_io: 3.00 Cost_cpu: 236657 Resp_io: 3.00 Resp_cpu: 236657 Best:: AccessPath: TableScan Cost: 3.06 Degree: 1 Resp: 3.06 Card: 0.00 Bytes: 0
SELECT
*
FROM
TABLE (DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
| Id | Operation | Name | Starts | E-Rows |A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
| 1 | NESTED LOOPS | | 1 | 1 | 446 |00:00:50.09 | 4460K| | | | | 2 | MERGE JOIN CARTESIAN | | 1 | 16 | 2230K|00:00:13.38 | 50 | | | | |* 3 | TABLE ACCESS FULL | KK_SMALL | 1 | 1 | 446 |00:00:00.01 | 33 | | | | | 4 | BUFFER SORT | | 446 | 5000 | 2230K|00:00:04.46 | 17 | 181K| 181K| 160K (0)| | 5 | TABLE ACCESS FULL | KK_BIG | 1 | 5000 | 5000 |00:00:00.01 | 17 | | | | |* 6 | TABLE ACCESS BY INDEX ROWID| KK_HUGE | 2230K| 1 | 446 |00:00:43.42 | 4460K| | | | |* 7 | INDEX UNIQUE SCAN | SYS_C007864 | 2230K| 1 | 2230K|00:00:20.61 | 2230K| | | |
Predicate Information (identified by operation id):
3 - filter((UPPER("KK_SMALL"."HUGE_VAL") LIKE '%ALU%' AND UPPER("KK_SMALL"."HUGE_VAL") LIKE '%AL%' AND NVL("START_DATE_ACTIVE",SYSDATE@!)<=SYSDATE@! AND NVL("END_DATE_ACTIVE",SYSDATE@!)>=SYSDATE@!)) 6 - filter("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") 7 - access("KK_BIG"."ID"="KK_HUGE"."ID")
The modified SQL statement, which allows Oracle to use histograms:
SELECT /*+ GATHER_PLAN_STATISTICS */
*
FROM
KK_SMALL, KK_BIG, KK_HUGE
Cost_io: 3.00 Cost_cpu: 235774 Resp_io: 3.00 Resp_cpu: 235774 Best:: AccessPath: TableScan Cost: 3.06 Degree: 1 Resp: 3.06 Card: 1.18 Bytes: 0
SELECT
*
FROM
TABLE (DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
| Id | Operation | Name | Starts | E-Rows |A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
| 1 | NESTED LOOPS | | 1 | 1 | 446 |00:00:00.02 | 1111 | | | | |* 2 | HASH JOIN | | 1 | 1 | 446 |00:00:00.01 | 188 | 878K| 878K| 1131K (0)| |* 3 | TABLE ACCESS FULL | KK_SMALL | 1 | 1 | 446 |00:00:00.01 | 4 | | | | | 4 | TABLE ACCESS FULL | KK_HUGE | 1 | 50000 | 50000 |00:00:00.10 | 184 | | | | | 5 | TABLE ACCESS BY INDEX ROWID| KK_BIG | 446 | 1 | 446 |00:00:00.01 | 923 | | | | |* 6 | INDEX UNIQUE SCAN | SYS_C007865 | 446 | 1 | 446 |00:00:00.01 | 477 | | | | ---------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
2 - access("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") 3 - filter((UPPER("KK_SMALL"."HUGE_VAL") LIKE '%ALU%' AND UPPER("KK_SMALL"."HUGE_VAL") LIKE '%AL%' AND ("END_DATE_ACTIVE" IS NULL OR "END_DATE_ACTIVE">=SYSDATE@!) AND ("START_DATE_ACTIVE" IS NULL OR "START_DATE_ACTIVE"<=SYSDATE@!))) 6 - access("KK_BIG"."ID"="KK_HUGE"."ID")
Note that the execution plan in the above changed significantly - the
join order changed and the sort-merge join is now gone. If we know
that works, let's see if we can fix the original SQL statement with
just a hint:
SELECT /*+ GATHER_PLAN_STATISTICS USE_HASH(KK_SMALL KK_HUGE) */
*
FROM
KK_SMALL, KK_BIG, KK_HUGE
Cost_io: 3.00 Cost_cpu: 236657 Resp_io: 3.00 Resp_cpu: 236657 Best:: AccessPath: TableScan Cost: 3.06 Degree: 1 Resp: 3.06 Card: 0.00 Bytes: 0
SELECT
*
FROM
TABLE (DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
| Id | Operation | Name | Starts | E-Rows |A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
| 1 | NESTED LOOPS | | 1 | 1 | 446 |00:00:00.02 | 1111 | | | | |* 2 | HASH JOIN | | 1 | 1 | 446 |00:00:00.01 | 188 | 878K| 878K| 1133K (0)| |* 3 | TABLE ACCESS FULL | KK_SMALL | 1 | 1 | 446 |00:00:00.01 | 4 | | | | | 4 | TABLE ACCESS FULL | KK_HUGE | 1 | 50000 | 50000 |00:00:00.10 | 184 | | | | | 5 | TABLE ACCESS BY INDEX ROWID| KK_BIG | 446 | 1 | 446 |00:00:00.01 | 923 | | | | |* 6 | INDEX UNIQUE SCAN | SYS_C007865 | 446 | 1 | 446 |00:00:00.01 | 477 | | | |
Predicate Information (identified by operation id):
2 - access("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") 3 - filter((UPPER("KK_SMALL"."HUGE_VAL") LIKE '%ALU%' AND UPPER("KK_SMALL"."HUGE_VAL") LIKE '%AL%' AND NVL("START_DATE_ACTIVE",SYSDATE@!)<=SYSDATE@! AND NVL("END_DATE_ACTIVE",SYSDATE@!)>=SYSDATE@!)) 6 - access("KK_BIG"."ID"="KK_HUGE"."ID")
The above completed very quickly when compared to the unhinted version.
Let's try again to force a sort-merge join:
SELECT /*+ GATHER_PLAN_STATISTICS USE_MERGE(KK_SMALL KK_HUGE) */
*
FROM
KK_SMALL, KK_BIG, KK_HUGE
Cost_io: 3.00 Cost_cpu: 236657 Resp_io: 3.00 Resp_cpu: 236657 Best:: AccessPath: TableScan Cost: 3.06 Degree: 1 Resp: 3.06 Card: 0.00 Bytes: 0
SELECT
*
FROM
TABLE (DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
| 1 | MERGE JOIN | | 1 | 1 | 446 | 00:00:00.07| 175 | | | | | 2 | SORT JOIN | | 1 | 5000 | 4991 | 00:00:00.04| 171 | 267K| 267K| 237K (0)| |* 3 | HASH JOIN | | 1 | 5000 | 5000 | 00:00:00.05| 171 | 870K| 870K| 1238K (0)| | 4 | TABLE ACCESS FULL| KK_BIG | 1 | 5000 | 5000 | 00:00:00.01| 17 | | | | | 5 | TABLE ACCESS FULL| KK_HUGE | 1 | 50000 | 50000 | 00:00:00.10| 154 | | | | |* 6 | SORT JOIN | | 4991 | 1 | 446 | 00:00:00.03| 4 | 15360 | 15360 |14336 (0)| |* 7 | TABLE ACCESS FULL | KK_SMALL | 1 | 1 | 446 | 00:00:00.01| 4 | | | |
Predicate Information (identified by operation id):
3 - access("KK_BIG"."ID"="KK_HUGE"."ID") 6 - access("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") filter("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") 7 - filter((UPPER("KK_SMALL"."HUGE_VAL") LIKE '%ALU%' AND UPPER("KK_SMALL"."HUGE_VAL") LIKE '%AL%' AND NVL("START_DATE_ACTIVE",SYSDATE@!)<=SYSDATE@! ANDNVL("END_DATE_ACTIVE",SYSDATE@!)>=SYSDATE@!)) Oracle apparently did not join the tables in the order that I specified - interesting.
And finally, the apparent quickest method of the night, which yielded
hash joins:
SELECT /*+ GATHER_PLAN_STATISTICS LEADING(KK_BIG KK_HUGE) */
*
FROM
KK_SMALL, KK_BIG, KK_HUGE
Cost_io: 3.00 Cost_cpu: 236657 Resp_io: 3.00 Resp_cpu: 236657 Best:: AccessPath: TableScan Cost: 3.06 Degree: 1 Resp: 3.06 Card: 0.00 Bytes: 0
SELECT
*
FROM
TABLE (DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS LAST'));
| Id | Operation | Name | Starts | E-Rows | A-Rows |
A-Time | Buffers | OMem | 1Mem | Used-Mem |
|* 1 | HASH JOIN | | 1 | 1 | 446 | 00:00:00.01 | 205 | 878K| 878K| 1133K (0)| |* 2 | TABLE ACCESS FULL | KK_SMALL | 1 | 1 | 446 | 00:00:00.01 | 4 | | | | |* 3 | HASH JOIN | | 1 | 5000 | 5000 | 00:00:00.05 | 201 | 870K| 870K| 1277K (0)| | 4 | TABLE ACCESS FULL| KK_BIG | 1 | 5000 | 5000 | 00:00:00.01 | 17 | | | | | 5 | TABLE ACCESS FULL| KK_HUGE | 1 | 50000 | 50000 | 00:00:00.10 | 184 | | | |
Predicate Information (identified by operation id):
1 - access("KK_HUGE"."HUGE_VAL"="KK_SMALL"."HUGE_VAL") 2 - filter((UPPER("KK_SMALL"."HUGE_VAL") LIKE '%ALU%' AND UPPER("KK_SMALL"."HUGE_VAL") LIKE '%AL%' AND NVL("START_DATE_ACTIVE",SYSDATE@!)<=SYSDATE@! AND NVL("END_DATE_ACTIVE",SYSDATE@!)>=SYSDATE@!)) 3 - access("KK_BIG"."ID"="KK_HUGE"."ID")
This behavior must cause significant headaches for developers who try to write platform independent code.
Charles Hooper
PC Support Specialist
K&M Machine-Fabricating, Inc.
Received on Fri Apr 06 2007 - 22:21:48 CDT