Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
CHAPTER 11 ■ INDEXES 479 Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(TO_NUMBER("X")=5) it uses the index, but not for a UNIQUE SCAN as we might expect—it is FULL SCANNING this index. The reason lies in the last line of output there: filter(TO_NUMBER("X")=5). There is an implicit function being applied to the database column. The character string stored in X must be converted to a number prior to comparing to the value 5. We cannot convert 5 to a string, since our NLS settings control what 5 might look like in a string (it is not deterministic), so we convert the string into a number, and that precludes the use of the index to rapidly find this row. If we simply compare strings to strings ops$tkyte@ORA10GR1> delete from plan_table; 2 rows deleted. ops$tkyte@ORA10GR1> explain plan for select * from t where x = '5'; Explained. ops$tkyte@ORA10GR1> select * from table(dbms_xplan.display); PLAN_TABLE_OUTPUT ------------------------------------------------------------------- Plan hash value: 1301177541 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 12 | 1 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 12 | 1 (0)| 00:00:01 | |* 2 | INDEX UNIQUE SCAN | T_PK | 1 | | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("X"='5') we get the expected INDEX UNIQUE SCAN, and we can see the function is not being applied. You should always avoid implicit conversions anyway. Always compare apples to apples and oranges to oranges. Another case where this comes up frequently is with dates. We try to query: -- find all records for today select * from t where trunc(date_col) = trunc(sysdate); and discover that the index on DATE_COL will not be used. We can either index the TRUNC(DATE_COL) or, perhaps more easily, query using range comparison operators. The following demonstrates the use of greater than and less than on a date. Once we realize that the condition TRUNC(DATE_COL) = TRUNC(SYSDATE)
480 CHAPTER 11 ■ INDEXES is the same as the condition select * from t where date_col >= trunc(sysdate) and date_col < trunc(sysdate+1) this moves all of the functions to the right-hand side of the equation, allowing us to use the index on DATE_COL (and it has the same exact effect as WHERE TRUNC(DATE_COL) = ➥ TRUNC(SYSDATE)). If possible, you should always remove the functions from database columns when they are in the predicate. Not only will doing so allow for more indexes to be considered for use, but also it will reduce the amount of processing the database needs to do. In the preceding case, when we used where date_col >= trunc(sysdate) and date_col < trunc(sysdate+1) the TRUNC values are computed once for the query, and then an index could be used to find just the qualifying values. When we used TRUNC(DATE_COL) = TRUNC(SYSDATE), the TRUNC(DATE_COL) had to be evaluated once per row for every row in the entire table (no indexes). Case 5 The index, if used, would actually be slower. I see this a lot—people assume that, of course, an index will always make a query go faster. So, they set up a small table, analyze it, and find that the optimizer doesn’t use the index. The optimizer is doing exactly the right thing in this case. Oracle (under the CBO) will use an index only when it makes sense to do so. Consider this example: ops$tkyte@ORA10GR1> create table t 2 ( x, y , primary key (x) ) 3 as 4 select rownum x, object_name 5 from all_objects 6 / Table created. ops$tkyte@ORA10GR1> begin 2 dbms_stats.gather_table_stats 3 ( user, 'T', cascade=>true ); 4 end; 5 / PL/SQL procedure successfully completed. If we run a query that needs a relatively small percentage of the table, as follows:
- Page 474 and 475: CHAPTER 11 ■ INDEXES 429 we ended
- Page 476 and 477: CHAPTER 11 ■ INDEXES 431 The data
- Page 478 and 479: CHAPTER 11 ■ INDEXES 433 if ( (++
- Page 480 and 481: CHAPTER 11 ■ INDEXES 435 Table 11
- Page 482 and 483: CHAPTER 11 ■ INDEXES 437 When Sho
- Page 484 and 485: CHAPTER 11 ■ INDEXES 439 an 8KB b
- Page 486 and 487: CHAPTER 11 ■ INDEXES 441 select *
- Page 488 and 489: CHAPTER 11 ■ INDEXES 443 select *
- Page 490 and 491: CHAPTER 11 ■ INDEXES 445 Indicate
- Page 492 and 493: CHAPTER 11 ■ INDEXES 447 an index
- Page 494 and 495: CHAPTER 11 ■ INDEXES 449 Table 11
- Page 496 and 497: CHAPTER 11 ■ INDEXES 451 9 1, 'M'
- Page 498 and 499: CHAPTER 11 ■ INDEXES 453 column w
- Page 500 and 501: CHAPTER 11 ■ INDEXES 455 Bitmap j
- Page 502 and 503: CHAPTER 11 ■ INDEXES 457 INSERT a
- Page 504 and 505: CHAPTER 11 ■ INDEXES 459 7 l_last
- Page 506 and 507: CHAPTER 11 ■ INDEXES 461 ops$tkyt
- Page 508 and 509: CHAPTER 11 ■ INDEXES 463 If we co
- Page 510 and 511: CHAPTER 11 ■ INDEXES 465 ops$tkyt
- Page 512 and 513: CHAPTER 11 ■ INDEXES 467 Caveat o
- Page 514 and 515: CHAPTER 11 ■ INDEXES 469 ops$tkyt
- Page 516 and 517: CHAPTER 11 ■ INDEXES 471 Frequent
- Page 518 and 519: CHAPTER 11 ■ INDEXES 473 select *
- Page 520 and 521: CHAPTER 11 ■ INDEXES 475 If you s
- Page 522 and 523: CHAPTER 11 ■ INDEXES 477 we’ll
- Page 526 and 527: CHAPTER 11 ■ INDEXES 481 ops$tkyt
- Page 528 and 529: CHAPTER 11 ■ INDEXES 483 ops$tkyt
- Page 530 and 531: CHAPTER 11 ■ INDEXES 485 This dem
- Page 532 and 533: CHAPTER 11 ■ INDEXES 487 SELECT /
- Page 534 and 535: CHAPTER 12 ■ ■ ■ Datatypes Ch
- Page 536 and 537: CHAPTER 12 ■ DATATYPES 491 • TI
- Page 538 and 539: CHAPTER 12 ■ DATATYPES 493 (in th
- Page 540 and 541: CHAPTER 12 ■ DATATYPES 495 That d
- Page 542 and 543: CHAPTER 12 ■ DATATYPES 497 ops$tk
- Page 544 and 545: CHAPTER 12 ■ DATATYPES 499 Table
- Page 546 and 547: CHAPTER 12 ■ DATATYPES 501 The IN
- Page 548 and 549: CHAPTER 12 ■ DATATYPES 503 ops$tk
- Page 550 and 551: CHAPTER 12 ■ DATATYPES 505 • BI
- Page 552 and 553: CHAPTER 12 ■ DATATYPES 507 NUMBER
- Page 554 and 555: CHAPTER 12 ■ DATATYPES 509 MSG NU
- Page 556 and 557: CHAPTER 12 ■ DATATYPES 511 They a
- Page 558 and 559: CHAPTER 12 ■ DATATYPES 513 ■Not
- Page 560 and 561: CHAPTER 12 ■ DATATYPES 515 Coping
- Page 562 and 563: CHAPTER 12 ■ DATATYPES 517 Note t
- Page 564 and 565: CHAPTER 12 ■ DATATYPES 519 We are
- Page 566 and 567: CHAPTER 12 ■ DATATYPES 521 Format
- Page 568 and 569: CHAPTER 12 ■ DATATYPES 523 ops$tk
- Page 570 and 571: CHAPTER 12 ■ DATATYPES 525 You ca
- Page 572 and 573: CHAPTER 12 ■ DATATYPES 527 month
CHAPTER 11 ■ INDEXES 479<br />
Predicate Information (identified by operation id):<br />
---------------------------------------------------<br />
2 - filter(TO_NUMBER("X")=5)<br />
it uses the index, but not for a UNIQUE SCAN as we might expect—it is FULL SCANNING this index.<br />
The reason lies in the last line of output there: filter(TO_NUMBER("X")=5). There is an implicit<br />
function being applied to the database column. The character string stored in X must be converted<br />
to a number prior to comparing to the value 5. We cannot convert 5 to a string, since<br />
our NLS settings control what 5 might look like in a string (it is not deterministic), so we convert<br />
the string into a number, <strong>and</strong> that precludes the use of the index to rapidly find this row.<br />
If we simply compare strings to strings<br />
ops$tkyte@ORA10GR1> delete from plan_table;<br />
2 rows deleted.<br />
ops$tkyte@ORA10GR1> explain plan for select * from t where x = '5';<br />
Explained.<br />
ops$tkyte@ORA10GR1> select * from table(dbms_xplan.display);<br />
PLAN_TABLE_OUTPUT<br />
-------------------------------------------------------------------<br />
Plan hash value: 1301177541<br />
------------------------------------------------------------------------------------<br />
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |<br />
------------------------------------------------------------------------------------<br />
| 0 | SELECT STATEMENT | | 1 | 12 | 1 (0)| 00:00:01 |<br />
| 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 12 | 1 (0)| 00:00:01 |<br />
|* 2 | INDEX UNIQUE SCAN | T_PK | 1 | | 1 (0)| 00:00:01 |<br />
------------------------------------------------------------------------------------<br />
Predicate Information (identified by operation id):<br />
---------------------------------------------------<br />
2 - access("X"='5')<br />
we get the expected INDEX UNIQUE SCAN, <strong>and</strong> we can see the function is not being applied.<br />
You should always avoid implicit conversions anyway. Always compare apples to apples <strong>and</strong><br />
oranges to oranges. Another case where this comes up frequently is with dates. We try to<br />
query:<br />
-- find all records for today<br />
select * from t where trunc(date_col) = trunc(sysdate);<br />
<strong>and</strong> discover that the index on DATE_COL will not be used. We can either index the<br />
TRUNC(DATE_COL) or, perhaps more easily, query using range comparison operators. The<br />
following demonstrates the use of greater than <strong>and</strong> less than on a date. Once we realize<br />
that the condition<br />
TRUNC(DATE_COL) = TRUNC(SYSDATE)