Apress.Expert.Oracle.Database.Architecture.9i.and.10g.Programming.Techniques.and.Solutions.Sep.2005
CHAPTER 11 ■ INDEXES 431 The data in the index is not sorted by X before it is stored, but rather by REVERSE(X), hence the range scan for X > 5 will not be able to use the index. On the other hand, some range scans can be done on a reverse key index. If I have a concatenated index on (X, Y), the following predicate will be able to make use of the reverse key index and will “range scan” it: where x = 5 This is because the bytes for X are reversed, and then the bytes for Y are reversed. Oracle does not reverse the bytes of (X || Y), but rather stores (REVERSE(X) || REVERSE(Y)). This means all of the values for X=5will be stored together, so Oracle can range scan that index to find them all. Now, assuming you have a surrogate primary key on a table populated via a sequence, and you do not need to use range scanning on this index—that is, you don’t need to query for MAX(primary_key), MIN(primary_key), WHERE primary_key < 100, and so on—then you could consider a reverse key index in high insert scenarios even in a single instance of Oracle. I set up two different tests, one in a pure PL/SQL environment and one using Pro*C to demonstrate the differences between inserting into a table with a reverse key index on the primary key and one with a conventional index. In both cases, the table used was created with the following DDL (we will avoid contention on table blocks by using ASSM so we can isolate the contention on the index blocks): create table t tablespace assm as select 0 id, a.* from all_objects a where 1=0; alter table t add constraint t_pk primary key (id) using index (create index t_pk on t(id) &indexType tablespace assm); create sequence s cache 1000; whereby &indexType was replaced with either the keyword REVERSE, creating a reverse key index, or with nothing, thus using a “regular” index. The PL/SQL that would be run by 1, 2, 5, 10, or 15 users concurrently was create or replace procedure do_sql as begin for x in ( select rownum r, all_objects.* from all_objects ) loop insert into t ( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY ) values
432 CHAPTER 11 ■ INDEXES ( s.nextval, x.OWNER, x.OBJECT_NAME, x.SUBOBJECT_NAME, x.OBJECT_ID, x.DATA_OBJECT_ID, x.OBJECT_TYPE, x.CREATED, x.LAST_DDL_TIME, x.TIMESTAMP, x.STATUS, x.TEMPORARY, x.GENERATED, x.SECONDARY ); if ( mod(x.r,100) = 0 ) then commit; end if; end loop; commit; end; / Now, since we discussed the PL/SQL commit time optimization in Chapter 9, I wanted to run a test that was using a different environment as well, so as to not be misled by this commit time optimization. I used Pro*C to emulate a data warehouse extract, transform, load (ETL) routine that processed rows in batches of 100 at a time between commits: exec sql declare c cursor for select * from all_objects; exec sql open c; exec sql whenever notfound do break; for(;;) { exec sql fetch c into :owner:owner_i, :object_name:object_name_i, :subobject_name:subobject_name_i, :object_id:object_id_i, :data_object_id:data_object_id_i, :object_type:object_type_i, :created:created_i, :last_ddl_time:last_ddl_time_i, :timestamp:timestamp_i, :status:status_i, :temporary:temporary_i, :generated:generated_i, :secondary:secondary_i; exec sql insert into t ( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY, GENERATED, SECONDARY ) values ( s.nextval, :owner:owner_i, :object_name:object_name_i, :subobject_name:subobject_name_i, :object_id:object_id_i, :data_object_id:data_object_id_i, :object_type:object_type_i, :created:created_i, :last_ddl_time:last_ddl_time_i, :timestamp:timestamp_i, :status:status_i, :temporary:temporary_i, :generated:generated_i, :secondary:secondary_i );
- 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
- Page 453 and 454: 408 CHAPTER 10 ■ DATABASE TABLES
- Page 455 and 456: 410 CHAPTER 10 ■ DATABASE TABLES
- Page 457 and 458: 412 CHAPTER 10 ■ DATABASE TABLES
- Page 459 and 460: 414 CHAPTER 10 ■ DATABASE TABLES
- Page 461 and 462: 416 CHAPTER 10 ■ DATABASE TABLES
- Page 463 and 464: 418 CHAPTER 10 ■ DATABASE TABLES
- Page 466 and 467: CHAPTER 11 ■ ■ ■ Indexes Inde
- 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 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 524 and 525: CHAPTER 11 ■ INDEXES 479 Predicat
CHAPTER 11 ■ INDEXES 431<br />
The data in the index is not sorted by X before it is stored, but rather by REVERSE(X), hence<br />
the range scan for X > 5 will not be able to use the index. On the other h<strong>and</strong>, some range scans<br />
can be done on a reverse key index. If I have a concatenated index on (X, Y), the following<br />
predicate will be able to make use of the reverse key index <strong>and</strong> will “range scan” it:<br />
where x = 5<br />
This is because the bytes for X are reversed, <strong>and</strong> then the bytes for Y are reversed. <strong>Oracle</strong><br />
does not reverse the bytes of (X || Y), but rather stores (REVERSE(X) || REVERSE(Y)). This<br />
means all of the values for X=5will be stored together, so <strong>Oracle</strong> can range scan that index to<br />
find them all.<br />
Now, assuming you have a surrogate primary key on a table populated via a sequence,<br />
<strong>and</strong> you do not need to use range scanning on this index—that is, you don’t need to query for<br />
MAX(primary_key), MIN(primary_key), WHERE primary_key < 100, <strong>and</strong> so on—then you could<br />
consider a reverse key index in high insert scenarios even in a single instance of <strong>Oracle</strong>. I set<br />
up two different tests, one in a pure PL/SQL environment <strong>and</strong> one using Pro*C to demonstrate<br />
the differences between inserting into a table with a reverse key index on the primary key <strong>and</strong><br />
one with a conventional index. In both cases, the table used was created with the following<br />
DDL (we will avoid contention on table blocks by using ASSM so we can isolate the contention<br />
on the index blocks):<br />
create table t tablespace assm<br />
as<br />
select 0 id, a.*<br />
from all_objects a<br />
where 1=0;<br />
alter table t<br />
add constraint t_pk<br />
primary key (id)<br />
using index (create index t_pk on t(id) &indexType tablespace assm);<br />
create sequence s cache 1000;<br />
whereby &indexType was replaced with either the keyword REVERSE, creating a reverse key<br />
index, or with nothing, thus using a “regular” index. The PL/SQL that would be run by 1, 2, 5,<br />
10, or 15 users concurrently was<br />
create or replace procedure do_sql<br />
as<br />
begin<br />
for x in ( select rownum r, all_objects.* from all_objects )<br />
loop<br />
insert into t<br />
( id, OWNER, OBJECT_NAME, SUBOBJECT_NAME,<br />
OBJECT_ID, DATA_OBJECT_ID, OBJECT_TYPE, CREATED,<br />
LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,<br />
GENERATED, SECONDARY )<br />
values