Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
CHAPTER 11 ■ INDEXES 473 select * from T where x is null; This query cannot use the index we just created—the row (NULL, NULL) simply is not in the index, hence the use of the index would in fact return the wrong answer. Only if at least one of the columns is defined as NOT NULL can the query use an index. For example, the following shows Oracle will use an index for an X IS NULL predicate if there is an index with X on the leading edge and at least one other column in the index is NOT NULL: ops$tkyte@ORA10GR1> create table t ( x int, y int NOT NULL ); Table created. ops$tkyte@ORA10GR1> create unique index t_idx on t(x,y); Index created. ops$tkyte@ORA10GR1> insert into t values ( 1, 1 ); 1 row created. ops$tkyte@ORA10GR1> insert into t values ( NULL, 1 ); 1 row created. ops$tkyte@ORA10GR1> begin 2 dbms_stats.gather_table_stats(user,'T'); 3 end; 4 / PL/SQL procedure successfully completed. When we go to query that table this time, we’ll discover this: ops$tkyte@ORA10GR1> set autotrace on ops$tkyte@ORA10GR1> select * from t where x is null; X Y ---------- ---------- 1 Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=5) 1 0 INDEX (RANGE SCAN) OF 'T_IDX' (INDEX (UNIQUE)) (Cost=1 Card=1 Bytes=5) Previously, I said that you can use to your advantage the fact that totally null entries are not stored in a B*Tree index—here is how. Say you have a table with a column that takes exactly two values. The values are very skewed; say, 90 percent or more of the rows take on one value and 10 percent or less take on the other value. You can index this column efficiently to gain quick access to the minority rows. This comes in handy when you would like to use an index to get to the minority rows, but you want to full scan to get to the majority rows, and you want to conserve space. The solution is to use a null for majority rows and whatever value you want for minority rows or, as demonstrated earlier, use a function-based index to index only the non-null return values from a function.
474 CHAPTER 11 ■ INDEXES Now that you know how a B*Tree will treat null values, you can use that to your advantage and take precautions with unique constraints on sets of columns that all allow nulls (be prepared to have more than one row that is all null as a possibility in this case). Should Foreign Keys Be Indexed? The question of whether or not foreign keys should be indexed comes up frequently. We touched on this subject in Chapter 6 when discussing deadlocks. There, I pointed out that unindexed foreign keys are the biggest single cause of deadlocks that I encounter, due to the fact that an update to a parent table’s primary key or the removal of a parent record will place a table lock on the child table (no modifications to the child table will be allowed until the statement completes). This locks many more rows than it should and decreases concurrency. I see it frequently when people are using a tool that generates the SQL to modify a table. The tool generates an updates that updates every column in the table, regardless of whether or not the value was UPDATE statement modified. This in effect updates the primary key (even though they never changed the value). For example, Oracle Forms will do this by default, unless you tell it to just send modified columns over to the database. In addition to the table lock issue that might hit you, an unindexed foreign key is bad in the following cases as well: • When you have an ON DELETE CASCADE and have not indexed the child table. For example, EMP is child of DEPT. DELETE FROM DEPT WHERE DEPTNO = 10 should cascade to EMP. If DEPTNO in EMP is not indexed, you will get a full table scan of EMP. This full scan is probably undesirable, and if you delete many rows from the parent table, the child table will be scanned once for each parent row deleted. • When you query from the parent to the child. Consider the EMP/DEPT example again. It is very common to query the EMP table in the context of a DEPTNO. If you frequently query select * from dept, emp where emp.deptno = dept.deptno and dept.dname = :X; to generate a report or something, you’ll find not having the index in place will slow down the queries. This is the same argument I gave for indexing the NESTED_COLUMN_ID of a nested table in Chapter 10. The hidden NESTED_COLUMN_ID of a nested table is nothing more than a foreign key. So, when do you not need to index a foreign key? In general, when the following conditions are met: • You do not delete from the parent table. • You do not update the parent table’s unique/primary key value, either purposely or by accident (via a tool). • You do not join from the parent table to the child table, or more generally the foreign key columns do not support an important access path to the child table and you do not use them in predicates to select data from this table (such as DEPT to EMP).
- Page 468 and 469: CHAPTER 11 ■ INDEXES 423 value of
- Page 470 and 471: CHAPTER 11 ■ INDEXES 425 One of t
- Page 472 and 473: CHAPTER 11 ■ INDEXES 427 We then
- 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 520 and 521: CHAPTER 11 ■ INDEXES 475 If you s
- Page 522 and 523: CHAPTER 11 ■ INDEXES 477 we’ll
- Page 524 and 525: CHAPTER 11 ■ INDEXES 479 Predicat
- 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
CHAPTER 11 ■ INDEXES 473<br />
select * from T where x is null;<br />
This query cannot use the index we just created—the row (NULL, NULL) simply is not in<br />
the index, hence the use of the index would in fact return the wrong answer. Only if at least<br />
one of the columns is defined as NOT NULL can the query use an index. For example, the following<br />
shows <strong>Oracle</strong> will use an index for an X IS NULL predicate if there is an index with X on the<br />
leading edge <strong>and</strong> at least one other column in the index is NOT NULL:<br />
ops$tkyte@ORA10GR1> create table t ( x int, y int NOT NULL );<br />
Table created.<br />
ops$tkyte@ORA10GR1> create unique index t_idx on t(x,y);<br />
Index created.<br />
ops$tkyte@ORA10GR1> insert into t values ( 1, 1 );<br />
1 row created.<br />
ops$tkyte@ORA10GR1> insert into t values ( NULL, 1 );<br />
1 row created.<br />
ops$tkyte@ORA10GR1> begin<br />
2 dbms_stats.gather_table_stats(user,'T');<br />
3 end;<br />
4 /<br />
PL/SQL procedure successfully completed.<br />
When we go to query that table this time, we’ll discover this:<br />
ops$tkyte@ORA10GR1> set autotrace on<br />
ops$tkyte@ORA10GR1> select * from t where x is null;<br />
X<br />
Y<br />
---------- ----------<br />
1<br />
Execution Plan<br />
----------------------------------------------------------<br />
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=5)<br />
1 0 INDEX (RANGE SCAN) OF 'T_IDX' (INDEX (UNIQUE)) (Cost=1 Card=1 Bytes=5)<br />
Previously, I said that you can use to your advantage the fact that totally null entries are<br />
not stored in a B*Tree index—here is how. Say you have a table with a column that takes<br />
exactly two values. The values are very skewed; say, 90 percent or more of the rows take on one<br />
value <strong>and</strong> 10 percent or less take on the other value. You can index this column efficiently to<br />
gain quick access to the minority rows. This comes in h<strong>and</strong>y when you would like to use an<br />
index to get to the minority rows, but you want to full scan to get to the majority rows, <strong>and</strong> you<br />
want to conserve space. The solution is to use a null for majority rows <strong>and</strong> whatever value<br />
you want for minority rows or, as demonstrated earlier, use a function-based index to index<br />
only the non-null return values from a function.