Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
CHAPTER 10 ■ DATABASE TABLES 357 7 zip number, 8 primary key (empno,addr_type) 9 ) 10 ORGANIZATION INDEX 11 / Table created. I populated these tables by inserting into them a work address for each employee, then a home address, then a previous address, and finally a school address. A heap table would tend to place the data at “the end” of the table; as the data arrives, the heap table would simply add it to the end, due to the fact that the data is just arriving and no data is being deleted. Over time, if addresses are deleted the inserts would become more random throughout the table. But suffice it to say that the odds an employee’s work address would be on the same block as his home address in the heap table is near zero. For the IOT, however, since the key is on EMPNO,ADDR_TYPE, we’ll be pretty sure that all of the addresses for a given EMPNO are located on one or maybe two index blocks together. The inserts used to populate this data were ops$tkyte@ORA10GR1> insert into heap_addresses 2 select empno, 'WORK', '123 main street', 'Washington', 'DC', 20123 3 from emp; 48250 rows created. ops$tkyte@ORA10GR1> insert into iot_addresses 2 select empno, 'WORK', '123 main street', 'Washington', 'DC', 20123 3 from emp; 48250 rows created. I did that three more times, changing WORK to HOME, PREV, and SCHOOL in turn. Then I gathered statistics: ops$tkyte@ORA10GR1> exec dbms_stats.gather_table_stats( user, 'HEAP_ADDRESSES' ); PL/SQL procedure successfully completed. ops$tkyte@ORA10GR1> exec dbms_stats.gather_table_stats( user, 'IOT_ADDRESSES' ); PL/SQL procedure successfully completed. Now we are ready to see what measurable difference we could expect to see. Using AUTOTRACE, we’ll get a feeling for the change: ops$tkyte@ORA10GR1> set autotrace traceonly ops$tkyte@ORA10GR1> select * 2 from emp, heap_addresses 3 where emp.empno = heap_addresses.empno 4 and emp.empno = 42; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=8 Card=4 Bytes=336) 1 0 NESTED LOOPS (Cost=8 Card=4 Bytes=336)
358 CHAPTER 10 ■ DATABASE TABLES 2 1 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (TABLE) (Cost=2 Card=1... 3 2 INDEX (UNIQUE SCAN) OF 'EMP_PK' (INDEX (UNIQUE)) (Cost=1 Card=1) 4 1 TABLE ACCESS (BY INDEX ROWID) OF 'HEAP_ADDRESSES' (TABLE) (Cost=6... 5 4 INDEX (RANGE SCAN) OF 'SYS_C008078' (INDEX (UNIQUE)) (Cost=2 Card=4) Statistics ---------------------------------------------------------- ... 11 consistent gets ... 4 rows processed That is a pretty common plan: go to the EMP table by primary key; get the row; then using that EMPNO, go to the address table; and using the index, pick up the child records. We did 11 I/Os to retrieve this data. Now running the same query, but using the IOT for the addresses ops$tkyte@ORA10GR1> select * 2 from emp, iot_addresses 3 where emp.empno = iot_addresses.empno 4 and emp.empno = 42; Execution Plan ---------------------------------------------------------- 0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=4 Bytes=336) 1 0 NESTED LOOPS (Cost=4 Card=4 Bytes=336) 2 1 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (TABLE) (Cost=2 Card=1... 3 2 INDEX (UNIQUE SCAN) OF 'EMP_PK' (INDEX (UNIQUE)) (Cost=1 Card=1) 4 1 INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_59615' (INDEX (UNIQUE)) (Cost=2... Statistics ---------------------------------------------------------- ... 7 consistent gets ... 4 rows processed ops$tkyte@ORA10GR1> set autotrace off we did four fewer I/Os (the four should have been guessable); we skipped four TABLE ACCESS ➥ (BY INDEX ROWID) steps. The more child records we have, the more I/Os we would anticipate skipping. So, what is four I/Os? Well, in this case it was over one-third of the I/O performed for the query, and if we execute this query repeatedly, that would add up. Each I/O and each consistent get requires an access to the buffer cache, and while it is true that reading data out of the buffer cache is faster than disk, it is also true that the buffer cache gets are not free and not totally cheap. Each will require many latches of the buffer cache, and latches are serialization devices that will inhibit our ability to scale. We can measure both the I/O reduction as well as latching reduction by running a PL/SQL block such as this:
- Page 351 and 352: 306 CHAPTER 9 ■ REDO AND UNDO ins
- Page 353 and 354: 308 CHAPTER 9 ■ REDO AND UNDO So,
- Page 355 and 356: 310 CHAPTER 9 ■ REDO AND UNDO ops
- Page 357 and 358: 312 CHAPTER 9 ■ REDO AND UNDO ops
- Page 359 and 360: 314 CHAPTER 9 ■ REDO AND UNDO •
- Page 361 and 362: 316 CHAPTER 9 ■ REDO AND UNDO ...
- Page 363 and 364: 318 CHAPTER 9 ■ REDO AND UNDO •
- Page 365 and 366: 320 CHAPTER 9 ■ REDO AND UNDO bac
- Page 367 and 368: 322 CHAPTER 9 ■ REDO AND UNDO As
- Page 369 and 370: 324 CHAPTER 9 ■ REDO AND UNDO ops
- Page 371 and 372: 326 CHAPTER 9 ■ REDO AND UNDO wil
- Page 373 and 374: 328 CHAPTER 9 ■ REDO AND UNDO Thi
- Page 375 and 376: 330 CHAPTER 9 ■ REDO AND UNDO ops
- Page 377 and 378: 332 CHAPTER 9 ■ REDO AND UNDO Whe
- Page 379 and 380: 334 CHAPTER 9 ■ REDO AND UNDO Tha
- Page 381 and 382: 336 CHAPTER 9 ■ REDO AND UNDO tou
- Page 383 and 384: 338 CHAPTER 10 ■ DATABASE TABLES
- Page 385 and 386: 340 CHAPTER 10 ■ DATABASE TABLES
- Page 387 and 388: 342 CHAPTER 10 ■ DATABASE TABLES
- Page 389 and 390: 344 CHAPTER 10 ■ DATABASE TABLES
- Page 391 and 392: 346 CHAPTER 10 ■ DATABASE TABLES
- Page 393 and 394: 348 CHAPTER 10 ■ DATABASE TABLES
- Page 395 and 396: 350 CHAPTER 10 ■ DATABASE TABLES
- Page 397 and 398: 352 CHAPTER 10 ■ DATABASE TABLES
- Page 399 and 400: 354 CHAPTER 10 ■ DATABASE TABLES
- Page 401: 356 CHAPTER 10 ■ DATABASE TABLES
- Page 405 and 406: 360 CHAPTER 10 ■ DATABASE TABLES
- Page 407 and 408: 362 CHAPTER 10 ■ DATABASE TABLES
- Page 409 and 410: 364 CHAPTER 10 ■ DATABASE TABLES
- Page 411 and 412: 366 CHAPTER 10 ■ DATABASE TABLES
- Page 413 and 414: 368 CHAPTER 10 ■ DATABASE TABLES
- Page 415 and 416: 370 CHAPTER 10 ■ DATABASE TABLES
- Page 417 and 418: 372 CHAPTER 10 ■ DATABASE TABLES
- Page 419 and 420: 374 CHAPTER 10 ■ DATABASE TABLES
- Page 421 and 422: 376 CHAPTER 10 ■ DATABASE TABLES
- Page 423 and 424: 378 CHAPTER 10 ■ DATABASE TABLES
- Page 425 and 426: 380 CHAPTER 10 ■ DATABASE TABLES
- Page 427 and 428: 382 CHAPTER 10 ■ DATABASE TABLES
- Page 429 and 430: 384 CHAPTER 10 ■ DATABASE TABLES
- Page 431 and 432: 386 CHAPTER 10 ■ DATABASE TABLES
- Page 433 and 434: 388 CHAPTER 10 ■ DATABASE TABLES
- Page 435 and 436: 390 CHAPTER 10 ■ DATABASE TABLES
- Page 437 and 438: 392 CHAPTER 10 ■ DATABASE TABLES
- Page 439 and 440: 394 CHAPTER 10 ■ DATABASE TABLES
- Page 441 and 442: 396 CHAPTER 10 ■ DATABASE TABLES
- Page 443 and 444: 398 CHAPTER 10 ■ DATABASE TABLES
- Page 445 and 446: 400 CHAPTER 10 ■ DATABASE TABLES
- Page 447 and 448: 402 CHAPTER 10 ■ DATABASE TABLES
- Page 449 and 450: 404 CHAPTER 10 ■ DATABASE TABLES
- Page 451 and 452: 406 CHAPTER 10 ■ DATABASE TABLES
358<br />
CHAPTER 10 ■ DATABASE TABLES<br />
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (TABLE) (Cost=2 Card=1...<br />
3 2 INDEX (UNIQUE SCAN) OF 'EMP_PK' (INDEX (UNIQUE)) (Cost=1 Card=1)<br />
4 1 TABLE ACCESS (BY INDEX ROWID) OF 'HEAP_ADDRESSES' (TABLE) (Cost=6...<br />
5 4 INDEX (RANGE SCAN) OF 'SYS_C008078' (INDEX (UNIQUE)) (Cost=2 Card=4)<br />
Statistics<br />
----------------------------------------------------------<br />
...<br />
11 consistent gets<br />
...<br />
4 rows processed<br />
That is a pretty common plan: go to the EMP table by primary key; get the row; then using<br />
that EMPNO, go to the address table; <strong>and</strong> using the index, pick up the child records. We did 11<br />
I/Os to retrieve this data. Now running the same query, but using the IOT for the addresses<br />
ops$tkyte@ORA10GR1> select *<br />
2 from emp, iot_addresses<br />
3 where emp.empno = iot_addresses.empno<br />
4 <strong>and</strong> emp.empno = 42;<br />
Execution Plan<br />
----------------------------------------------------------<br />
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=4 Card=4 Bytes=336)<br />
1 0 NESTED LOOPS (Cost=4 Card=4 Bytes=336)<br />
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'EMP' (TABLE) (Cost=2 Card=1...<br />
3 2 INDEX (UNIQUE SCAN) OF 'EMP_PK' (INDEX (UNIQUE)) (Cost=1 Card=1)<br />
4 1 INDEX (RANGE SCAN) OF 'SYS_IOT_TOP_59615' (INDEX (UNIQUE)) (Cost=2...<br />
Statistics<br />
----------------------------------------------------------<br />
...<br />
7 consistent gets<br />
...<br />
4 rows processed<br />
ops$tkyte@ORA10GR1> set autotrace off<br />
we did four fewer I/Os (the four should have been guessable); we skipped four TABLE ACCESS ➥<br />
(BY INDEX ROWID) steps. The more child records we have, the more I/Os we would anticipate<br />
skipping.<br />
So, what is four I/Os? Well, in this case it was over one-third of the I/O performed for the<br />
query, <strong>and</strong> if we execute this query repeatedly, that would add up. Each I/O <strong>and</strong> each consistent<br />
get requires an access to the buffer cache, <strong>and</strong> while it is true that reading data out of the<br />
buffer cache is faster than disk, it is also true that the buffer cache gets are not free <strong>and</strong> not<br />
totally cheap. Each will require many latches of the buffer cache, <strong>and</strong> latches are serialization<br />
devices that will inhibit our ability to scale. We can measure both the I/O reduction as well as<br />
latching reduction by running a PL/SQL block such as this: