17
Paper #135 / Page 1 The Art of Indexing in Oracle8i Venkat S. Devraj IMMEDIENT (formerly Raymond James Consulting) Introduction In most applications, OLTP or DSS, indexes are one of the most ardently used objects. In spite of this, they are highly under-estimated. Creating an index is considered to be a simple and precise science. In my experience, I have noted quite a few sites creating indexes with great vigor and finally ending up with one-too-many and not knowing which ones deserve to stay and which ones can be eliminated without an adverse impact on queries. And of course, there are other sites that tend to seriously under-exploit them, worrying about the possible detrimental impact on inserts and deletes. Furthermore, the rich and varied indexing options offered by Oracle8i adds fuel to the fire. Be it new full-fledged index types or new index-access paths, indexing is emerging as an art, demanding a rock-solid understanding of both concepts and implementation from developers and DBAs. Objectives The objective of this paper is two-fold : To introduce the various index types and access-paths available in Oracle8i and shed more technical light on them; Present specific examples of index-usage at my client-sites to foster better understanding. Hopefully, this paper will help an attendee in determining specifically what type of index to create during which situation; when an index should be re-built and what kind of access-path should be determined for major DML statements, using those indexes. I have intentionally breezed through and at times, even avoided standard “run-of- the-mill” commentary about indexes and instead focussed on material not easily available in the manuals. The intention of this paper is not to replace the Oracle documentation-set, but to act as a source of reference for real- world implementation of indexes. Index-types in Oracle8i Oracle8i continues to offer the following major index-types : B-Tree indexes Bit-mapped indexes In addition, there are the following variations of the B-Tree index (sub-types) : Partitioned indexes Reverse-key indexes Index-organized tables (IOTs) Cluster indexes Function-based indexes (Oracle8i only)

Art of indexing_in_o8i

Embed Size (px)

Citation preview

Page 1: Art of indexing_in_o8i

Paper #135 / Page 1

The Art of Indexing in Oracle8 iVenkat S. DevrajIMMEDIENT (formerly Raymond James Consulting)

Introduction

In most applications, OLTP or DSS, indexes are one of the most ardently used objects. In spite of this, they arehighly under-estimated. Creating an index is considered to be a simple and precise science. In my experience, Ihave noted quite a few sites creating indexes with great vigor and finally ending up with one-too-many and notknowing which ones deserve to stay and which ones can be eliminated without an adverse impact on queries. Andof course, there are other sites that tend to seriously under-exploit them, worrying about the possible detrimentalimpact on inserts and deletes. Furthermore, the rich and varied indexing options offered by Oracle8i adds fuel tothe fire. Be it new full-fledged index types or new index-access paths, indexing is emerging as an art, demandinga rock-solid understanding of both concepts and implementation from developers and DBAs.

Objectives

The objective of this paper is two-fold :

• To introduce the various index types and access-paths available in Oracle8i and shed more technical light onthem;

• Present specific examples of index-usage at my client-sites to foster better understanding.

Hopefully, this paper will help an attendee in determining specifically what type of index to create during whichsituation; when an index should be re-built and what kind of access-path should be determined for major DMLstatements, using those indexes. I have intentionally breezed through and at times, even avoided standard “run-of-the-mill” commentary about indexes and instead focussed on material not easily available in the manuals. Theintention of this paper is not to replace the Oracle documentation-set, but to act as a source of reference for real-world implementation of indexes.

Index-types in Oracle8 iOracle8i continues to offer the following major index-types :

• B-Tree indexes

• Bit-mapped indexes

In addition, there are the following variations of the B-Tree index (sub-types) :

• Partitioned indexes

• Reverse-key indexes

• Index-organized tables (IOTs)

• Cluster indexes

• Function-based indexes (Oracle8i only)

Page 2: Art of indexing_in_o8i

Paper #135 / Page 2

• Application domain indexes (Oracle8i only)

Furthermore, you might find the following index-related terms useful :

• Local and global indexes

• Pre-fixed and non-prefixed indexes

• Abstract data-type indexes

Last but not the least, the following index access-paths are provided :

• Regular Index/Table lookups : Uses the ROWID from the index to locate a row in the table.

• Index-only lookups : If all the necessary columns are available in the index itself, the table-lookup is avoided.

• Index range scans : A range of index keys are scanned to locate the desired key-value.

• Fast Full Index scans (FFIs) : FFIs allow reading of multiple index blocks to expedite full index scans. Sincean index is usually smaller than a table, an index full-scan is a much faster alternative to a full table scan, ifthe index includes all required columns. The init.ora parameter db_file_multiblock_read_count determines thenumber of index blocks read in one I/O operation (remember, many operating systems have a 64K upper limiton I/O).

• Parallel Query Option (PQO) “enriched” paths : In Oracle8/8i, PQO is available for all the above access-paths.

This paper assumes prior knowledge of certain key concepts. Before we start, let’s take a quick peek at thoseconcepts :

• Constraints and index-creation : Declaring primary-key and unique constraints automatically creates an indexon the concerned columns. The USING INDEX clause of the CREATE and ALTER TABLE commandsallows more control over this index-creation process.

• Rule and Cost based optimization : Rule based optimization is still supported in Oracle8i. Oracle8i sites canuse either RULE, COST or CHOOSE as a value for the OPTIMIZER_MODE init.ora parameter. This settingis of paramount importance in the way Oracle8i uses indexes. For example, the rule-based optimizer mergesindexes if they are non-unique and the predicates used are equality predicates. This is because range-predicates with multiple indexes generally require additional processing that would impede performance.Under the cost-based optimizer (CBO), this would not necessarily happen if the cost of doing the merge werehigher than the cost of using multiple non-unique indexes. In addition, certain new index sub-types and accesspaths are available only while using CBO. For example, partitioned indexes and fast full index scans (FFIs).

With changes in the CBO in almost every new release, never assume that your DML statements are using acertain index. Always run an EXPLAIN PLAN on every important DML statement to ensure usage of theright indexes.

• Analyzing of tables and indexes : CBO requires all tables and indexes to be analyzed periodically, especiallyafter periods of high DML activity. These statistics are used by CBO to evaluate the “cost” of all availableaccess-paths and the path with the lowest cost is selected.

Page 3: Art of indexing_in_o8i

Paper #135 / Page 3

• Usage of hints to force index-selection : Under both rule or cost based optimization, certain hints may beprovided to DML statements to specifically select a certain index. This functionality is provided to overridethe optimizer, in case the access-path selected by it is sub-optimal. Syntax for hints are very similar tocomments, except for the addition of a plus (+) symbol. If a hint is provided incorrectly, the optimizer ignoresthe hint.

Example : SELECT /*+ INDEX(table_a, index_a) */ id, name FROM table_a;

Index-related hints supported by Oracle8i include INDEX, INDEX_DESC, INDEX_COMBINE,INDEX_FFS and PARALLEL_INDEX for retrieving rows from indexes in an ascending order, descendingorder, combining bit-mapped indexes using boolean logic, prompting the optimizer to use the Fast Full Index(FFI) scan in lieu of a full-table scan and inducing a parallel-scan of an index (using PQO) respectively.

B-Tree Indexes : Everybody’s most favorite index!

This is one of the earliest indexing options available in Oracle. And we are all absolutely familiar with this index-type, right ? OK, let’s have a quick show-of-hands : how many of us here today, understand or remember a B-Tree structure ? Believe me, good usage of these “basic” indexes requires a good understanding of how B-Treesindexes operate. Implementation of B-Trees are a little unique in every application in terms of it’s height andorder. However, the basic behavior is the same. Let’s take a quick practical look at how B-Trees indexes havebeen implemented in Oracle8.

Fig. 1a : A simplistic B-tree implementation (height : 2 and order : 2) [Branch/leaf nodes read to locate “R” are displayed

in bold]

B-Trees are an excellent way of looking up pointers to large information with minimal number of disk-reads. In aB*Tree, all information is either a branch/root node or a leaf-node. The final information (ROWIDs and keys) arealways stored in a leaf (-node). Upto 16 columns can comprise the key in Oracle7.x, whereas in Oracle8.x, it is 32columns. Furthermore, a key cannot be greater than 1/3rd the size of the database block (violating this restrictionwould result in the ORA-01450 error). A leaf may contain multiple physical rows. The leaves are always present

M

G S

A B CD E F

H I J KL

N O PQ R

T U VLeaf nodes

Root/branchnodes

Page 4: Art of indexing_in_o8i

Paper #135 / Page 4

only at the last “level”. A B-Tree is a “balanced tree”, because every leaf is equi-distant from the root-node. Ittakes the same time to access any row in any given leaf. Fig. 1a is a poor man’s picture of a B-Treeimplementation. Anyhow, it clearly indicates the root, the branches and the leaves. Every tree has a certain orderand height. The order is the number of “branches” for every node of the tree. The height is the number of levelsthe tree contains. Ideally the height has to be minimal, since the number of disk-reads required to get informationis proportionate to the height of the tree. For example in fig. 1a, in order to get “R”, two disk-reads are required(“M” at the root-node and “S” at the second-level node). Thus, here a minimum of three reads would be requiredto locate a key (two reads at the root/branch level and the third at the leaf-node level). It is to be borne in mind,that each read may be a logical read or a physical read, depending on how the index is laid out. If a single readoperation places the required number of blocks in memory, subsequent reads would be performed off memory,else they would need to go to disk. The height is inversely proportionate to the order. The higher the order, thelower the height (termed as a “flat” tree). An important objective in any B-Tree implementation is to increase thebranches and reduce the height, thereby ensuring speedy access to the leaves.

A major challenge with a B-Tree implementation occurs during INSERTs, DELETEs and UPDATEs. Duringevery INSERT, the key has to be inserted appropriately within the right leaf. Every key has one and only oneplace within the tree. If the leaf is already full and cannot accommodate any additional keys, then the leaf has tobe split into two. Now, there are two options available :

1. if the new key value is the biggest (of all the keys existing in the old leaf block), then the total keys are split ina 99:1 ratio, wherein only the new key is placed in the new leaf block, whereas the rest of the (old) keys areretained within the old leaf block (including all deleted keys).

2. if the new key value is not the biggest, then the total keys are split in a 50:50 ratio, wherein each of the leafblocks (old and new) would absorb half the keys of the original leaf.

This split has to be propagated upwards to the parent node by means of a new entry to point to the new leaves. Ifthe parent node happens to be full, then the node has to be split into two and it’s parent has to be notified of thesplit. God forbid, if that parent is full, as well! Thus, a split may be propagated all the way up to the root-node,itself. Remember, each split is costly involving physical disk I/O. Additionally, prior to splitting, Oracle has toseek and find an empty block to accommodate the split. The following steps are taken to do so :

1. Seek in the index free-list for a free block. More than one free-list can be defined for an index (via theCREATE/ALTER INDEX commands). Free-lists for indexes do not aid Oracle in finding an available blockfor accommodating the new key-value being inserted. This is because a key-value cannot be randomly placedin the first “free” leaf-block that’s available within the index. The value has to be placed within a specificleaf-block after appropriate sorting. Free-lists in indexes are used only during block splitting. Each free-listcomprises of a linked-list of “empty” blocks. When more than one free-list is defined for an index, the free-list allotted to the process is initially scanned for a free block. If none are found, the master free-list ischecked.

2. If no free blocks are found, Oracle tries to allocate another extent. If there is no free space in the tablespace,Oracle gives an error (ORA-01654).

3. If a free block is found via either of the above steps, the high watermark for the index is increased.

4. Use the free block found to perform the split.

Part of the challenge in creating B-Tree indexes is to avoid splits at runtime or rather, to induce splits duringindex-creation (“pre-splitting”) and take the performance-hit at that point in time, and not during run-time inserts.These splits may not be restricted merely to inserts, they may occur during updates, as well.

Page 5: Art of indexing_in_o8i

Paper #135 / Page 5

Let’s take a look at what goes on within Oracle during an indexed-key UPDATE. Index updates differ vastly fromtable updates. In a table update, data is changed within the data-block (assuming there is sufficient space in theblock for this change to take place). However in an index, when a key is changed, it’s position within the tree hasto change. Remember, a key has one and only one position within a B-Tree. So when it changes, the old entry hasto be deleted and a new one has to be created at a new leaf. Chances are, the old entry may never be re-used.Because Oracle would reuse a key-entry slot only when the newly inserted key is exactly the same as an old(deleted) one, including data-type, length and case. And what are the chances of that happening ? Mostapplications at my client-sites use a sequence to generate NUMBER keys (especially primary keys). Unless theyare using the RECYCLE option, the sequences do not generate the same number twice. So, the deleted spacewithin the index remains unused. This is the reason why during large-scale DELETEs and UPDATEs, the table-size shrinks or at least remains constant, whereas the indexes grow continuously.

From the above explanation of B-Trees, the following dos and don’ts emerge :

• Avoid indexing columns, that are subject to high UPDATEs.

• Avoid indexing multiple columns in tables, that are subject to high DELETEs. If possible, only index theprimary-key and/or column(s) determining the deletes on such tables. If indexing multiple columns isunavoidable, then consider partitioning the tables based on those columns and do a TRUNCATE on each suchpartition (instead of a DELETE). TRUNCATE simulates a drop and re-create of the table and index by re-setting the high-water mark, when used with the DROP STORAGE clause.

• Avoid creating b-tree indexes for columns with low selectivity (less uniqueness). Low selectivity leads todense leaf-blocks, causing large-scale index scans. The higher the uniqueness, the better the performance,since range-scans would reduce and maybe even be replaced with an unique scan.

• Null values are never stored in single-column indexes,. In the case of multi-column indexes, the value isstored only if one of the columns is not null. Bear this in mind while constructing IS NULL or IS NOT NULLclauses for DML statements. An IS NULL would never induce an index-scan, whereas an unrestrained ISNOT NULL would cause a full index range-scan.

You have heard these advises before, but now the precise logic behind these words of wisdom is apparent.

The importance of PCTFREE

For a B-Tree index, PCTFREE determines the extent of a leaf-split. In other words, PCTFREE indicates theamount of free-space within a block for “future updates”. However in the case of an index (unlike a table), theseupdates do not make sense, since an update anyway forces a delete and subsequent insert.

In the case of indexes, PCTFREE plays a role mostly at the time of index-creation. A non-zero value specifies theblock-splitting ratio. If PCTFREE is set to 20, during index-creation, upto 80% of the leaf may contain key-information. However the remaining 20% should be available for future inserts of key-information into that leaf-block. This ensures that during run-time inserts, there is minimal overhead in terms of incurring leaf-block splits.Even though a higher PCTFREE may cause index-creation time to increase, it prevents the performance-hit frombeing deferred to actual usage time. So, the end-user, who is waiting for a row to be inserted does not incur theperformance penalty associated with a leaf-block split.

Page 6: Art of indexing_in_o8i

Paper #135 / Page 6

Based on this information, the following points emerge :

• PCTFREE for an index is used primarily during creation. It is ignored during actual usage.

• Specify a higher PCTFREE for OLTP applications, if the table is a popular one, incurring a lot of DMLchanges (via interactive user-screens);

• Specify a lower PCTFREE, if the index-creation time is critical. This will pack in more rows per leaf-block,thereby avoiding the need for further splitting at creation-time. This is of paramount significance to shops,that have “24x7” availability requirements. Index-creation in most cases, requires considerable downtime(especially if the table is a multi-million row table). Lesser the amount of index-creation time, smaller can bethe maintenance-window. I have seen this tiny, often unnoticed parameter save around 20% of index-creationtime. At a high-availability site, an index on a table containing around 11 million rows took me about 80minutes to build using PCTFREE of 30 and a parallel degree of 4. The same index on the same table, with13.5 million rows took me around 90 minutes to create with a PCTFREE of 0 (without any hardware/softwareenhancements). Nologging (minimal redo) was on during both creations.

• For any column where the values are constantly increasing, it is probably a good idea to set a very lowPCTFREE (even, zero). This is because only the rightmost leaf-block will always be inserted into. This willmake the tree grow towards the right. The leftmost leaves would remain static. So, there is no sense in leavingany part of those blocks empty with a non-zero PCTFREE.

Partitioned indexes

Partitioned indexes are indexes, which have been horizontally sub-divided based on a specific range of key-values. Partitioning allows logically dependent structures to be physically independent. This allows maintenanceoperations to be performed on specific partition, while the others are still available for access. Thus, a specificpartition may be offline and unavailable, while the other partitions are online and available for access. An index-partition may be re-built or recovered with minimal effect on applications. Each index-partition may havedifferent storage characteristics and may be created in different tablespaces. Partitioned indexes may be createdeven for non-partitioned tables.

Partitioned index-related terms

Global and Local indexes : When an index is partitioned in a similar fashion as the table, in terms of ranges ofpartition-keys, then it is referred to as a local index. In other words, the local index shares the key-ranges with theparent table. It would store the keys of only it’s corresponding table-partition. A global index does not have a one-to-one correspondence with any table-partition in terms of partition-key ranges. A global index would store rowsfrom multiple table-partitions. A local index is also referred to as an equi-partitioned index; whereas a globalindex is also referred to as a non equi-partitioned index.

For example, a table may be partitioned into 52 segments based on the week-of-year. However, the index mightbe partitioned into 12 segments based on month-of-year. Such an index would be termed a global index. If theindex were partitioned into 52 segments, it would be referred to as a local index.

A bit-mapped index on a partitioned table has to be a local index.

Page 7: Art of indexing_in_o8i

Paper #135 / Page 7

Prefixed and non-prefixed indexes : When an index of a partitioned table contains the partition key as the leadingcolumn, it is termed as a prefixed index. A non-prefixed index does not have partition key columns as the leadingkeys.

Local pre-fixed indexes should be created, whenever possible. Since they have a one-to-one correspondence withthe partitioned table, administration is easier. Global pre-fixed indexes theoretically may have a slightperformance edge at times, since the overhead of accessing large number of indexes is avoided. Howeverpartition pruning (where the partition key-value specified in the WHERE clause helps Oracle in determiningwhich partition might contain the required results and accesses only that partition) helps in super-charging localpartition access. Even in situations where partition pruning is not possible, parallel access of multiple localindexes via PQO may provide higher performance. It is generally more expensive to scan a non-prefixed index,compared to a pre-fixed index, since as mentioned earlier, in the case of a pre-fixed index, the optimizer can usethe partition key-value to scan just the necessary index partitions (rather than all) for a predicate referring to theindexed columns.

Oracle8.x does not support global non-prefixed indexes, since they do not have any practical utility. A globalindex may be split into local indexes or multiple local indexes may be merged into a global one.

Considering partitioned indexes

Let me illustrate this “partitioning scenario” with an example. One of my clients is a company providing free e-mail via the web. Their users receive a lot of e-mails. When a user deletes a message, the mail-entry is “deleted”from the main-messages table and moved to a garbage-bin. This is achieved by setting the garbage-flag to ‘1’ forthat row in the main table. Only when the user specifically deletes it from his garbage-bin, the mail is permanentlydeleted. Many users set up their account such that once a week, their garbage-bin is automatically purged. Soevery week-end, this huge batch-job was run to delete all rows from the main-table where the garbage-flag = ‘1’.Since the garbage-flag column was being used in the WHERE clause of the DELETE, it was part of a regular B-Tree index (a bit-mapped index was not used even though the cardinality was low, because the column wassubject to high updates and another key column was being used as the index-prefix). However, every time it wasupdated to ‘1’, it’s index-entry would be deleted and a new-one would be created. Furthermore, when the recordwould finally be deleted, the index-entry would continue to take up space. Thus the table would not significantlygrow in size (due to e-mails being deleted and inserted all the time). However the index was growing at anincredible rate, taking up large amounts of tablespace area. Finally this problem was solved by replacing thedesign with a “partition-aware” design.

Consider partitioning indexes :

• for intense OLTP applications, dealing with a large number of rows. The flexibility in administratingrelatively small segments is a great boon.

• for high-availability (24x7) environments, where the down-time associated with an index-rebuild cannot betolerated

• for better overall performance. Whenever possible, Oracle8/8i parallelizes scans of partitioned indexes. Alsopartition elimination allows specific partitions to be eliminated from searches based on the key-ranges. Thisvastly expedites index-scans. Finally, partitioning allows an index to be spread out over multiple physicaltablespaces/data-files, thereby reducing the occurrence of I/O hot-spots.

Page 8: Art of indexing_in_o8i

Paper #135 / Page 8

Do not partition indexes on key-values prone to constant updates, especially if such updates cause partitionmigration, where a row has to be moved from one partition to another.

Lastly, one frequently question that pops up at client-sites is, how many partitions can be realistically createdwithout any performance degradation ? Theory apart (where thousands and thousands of partitions can becreated), the answer to this question depends on various factors such as system resources (CPUs, memory, I/Ochannels across which the partitions are created, etc.) and varies from site to site. A lot also depends onapplications and data. If the data lends itself to a “clean” partitioning structure, based on a popular (frequentlyused in most queries) key-column(s), then partitioning across a large number of partitions would be moreeffective, since partition pruning is possible in most situations. However if such a popular key column is notavailable, then utilizing a large number of partitions may cause performance degradation, since multiple partitionswould need to be frequently visited to retrieve the required values. As such, it is always essential to ensure thatyou select a popular key column as the partitioning key. If such a column does not currently exist, evaluateintroducing it explicitly (for instance, by denormalizing the table). Additionally, always ensure that there issufficient scope for parallelizing partitioned operations (such as multiple CPUs, data spread across multipledisks/controllers, etc.). I have seen partitioning to be very effective in certain situations, when the number ofpartitions do not exceed a few hundreds. After that, the law of diminishing returns comes into play, as systemresources get saturated and performance begins to stink.

Reverse-key indexes

A reverse-key index is primarily a b-tree index, where the bytes of key-values are stored in a reversed order. Byreversing the keys, index entries are spread more evenly throughout the b-tree. This is very useful in an OPS(Oracle Parallel Server) environment, where contention for the same leaf-blocks might cause inter-instance“pinging” to occur, thereby affecting performance adversely due to excessive disk I/O.

Examples of a reversed key are as follows :

ADAMSON NOSMADA

1558 8551

Dates in a reverse-key index are stored by reversing the seven bytes contained in a DATE data-type.

Considering reverse-key indexes

In late 1996, at a client-site in Bombay, India, our team was implementing a Custodial Services system. We faceda major performance-impediment with the share-certificates table; especially when the clients would acquire alarge number of share-certificates with sequential numbering. We had created the right index on the table and hadensured that the index was indeed, being used. However, even a pure index-access would take unusually long : atleast 15 seconds more than what we expected (based on our experiments with other similar tables). Finally wefigured out that storing those sequential certificate numbers caused the index to be lop-sided. In other words,some of the leaves would be dense (heavily populated), whereas some of them would be sparse, based on the

Page 9: Art of indexing_in_o8i

Paper #135 / Page 9

series of share-certificates we obtained. Most DML activity would focus on these dense leaves, thereby creating“hot-spots” on the index. By hot-spots, I mean index-activity would be much more for these few leaf-blocks,compared to the others. Since there would usually be only one disk-controller accessing those blocks, all activitywould have to be serialized, thereby adversely affecting response-time and throughput.

In such a situation, reverse-key indexes would have been a boon. However we were using Oracle7 v7.3 at thattime, where the feature was still not available. We lived with the situation for a while. Finally, we circumventedthe situation by reversing the values within the application. We had two columns in the table, a column that wouldstore the share-certificate number as it was and a primary-key column, which would store the same share-certificate in a reversed order. We wrote a stored function, which would accept the actual share-certificate as aninput parameter and return the reversed number, which would subsequently be inserted in the table as the primarykey. This gave us a relatively “well-balanced” index.

The same concept applies to any value that increases in spurts : sequential numbers, date and time columns, etc.Increasing in spurts indicates a scenario where the value monotonously increases, then there is not much activityfor a while, thereby creating “gaps” in the values; then the value constantly increases again. Such columns wouldbe prime candidates for using the Oracle8/8i reverse-key indexes. Also, in fast-growing tables where indexes arebuilt on sequential values (such as a primary-key index), and large-scale deletes frequently occur, the index maybegin to “slide”. A sliding index is one that continuously grows towards the right (newer rows being inserted),while the left-portion is constantly being deleted (older rows being purged). The left-portions of the treecomprises of nulls or empty leaf-nodes, whereas the tree is expanding to the right, thus appearing to “slide”.Reversing the index may halt the right-oriented growth (due to constantly increasing key-values) and make thegrowth more balanced by placing the reversed key-values in all directions (left and right). Additionally,partitioning the index (on a “rolling window” scheme) and truncating older partitions (rather than deleting) wouldhelp in eliminating the large number of empty leaf-blocks.

One question might come to your mind : how did we manage to figure out that our index was indeed lop-sided ?We used an “ALTER SESSION SET EVENTS” command to understand the index-structure better. Thiscommand is mentioned in more detail in Appendix A.

Some caveats to bear in mind while using reverse-key indexes :

• Reverse key indexes are not effective when used in range scans

• Reverse key indexes are prone to all the restrictions of regular b-tree indexes, such as avoiding excessiveDML activity, periodic re-building, etc. (since they are merely a variation of B-Trees).

Index-organized tables (IOTs)

Index-organized tables are tables, where the rows are stored in a B-Tree structure, based on the primary-keyvalue. A primary key definition is mandatory for an IOT. In Oracle8 v8.0.x, IOTs do not have rowids (thisrestriction is removed in 8i via “logical rowids” [explained below]). An index entry is expected to be terse toremain effective. Since a full-fledged row may be too big to retain index-effectiveness, Oracle8/8i provides thePCTTHRESHOLD clause to indicate a percentage of the total block-size. All non-primary key columns that causea row to grow beyond the size of this percentage are moved to an “overflow” tablespace.

Page 10: Art of indexing_in_o8i

Paper #135 / Page 10

Considering index-organized tables

A short while ago, I was part of a team, building a large data-warehouse for a major investment company inOracle8/8i. In the data-warehouse, we had a bunch of code-tables, acting as dimension tables. In the first iterationof the physical design, we built all those code-tables as regular tables with two columns : the code-id and thedescription. We had a primary key index on the code-id. We also cached some of these tables and their indexes inthe Oracle SGA.

However with more familiarity with Oracle8i, we observed that these code-tables were prime candidates for usingthe index-organized feature. Since there was only one index on each of these tables, we could use the primary-keyto structure the rows in these tables in a B-Tree format. In the second iteration of the physical design, we re-defined most of our code-tables as index-organized tables. This change brought us space-savings to the tune of35%. From around 800 MB for both regular-tables and the primary key indexes, the space-usage has droppeddown to approximately 520 MB for the index-organized tables. These space-savings affect not only disk-usage,but also memory usage (since many of the tables were cached). That was true space-savings for us, since memoryis always at a premium! Soon after we could consider caching additional information within the SGA, withouthaving to expand it.

Needless to add, this space-saving was obtained due to absence of the physical rowids and any indexes on theindex-organized tables. This example illustrates that tables such as code-tables or certain master tables, not proneto frequent changes are ideal for using the index-organized feature of Oracle8/8i. This useful option bringssubstantial space-savings to the table, especially when going from a regular table with an index comprising ofmost of the columns in the table (built that way to exploit the “index-only lookup” access-path) to an index-organized table.

Some caveats with IOTs in Oracle8 versions (prior to 8i):

• The table structure cannot be altered

• Non-primary key indexes cannot be created

• Absence of rowids might cause problems while migrating Oracle7 applications to Oracle8, if the applicationDML explicitly depends on rowids

Note that all the above restrictions have been eliminated in Oracle8i, allowing IOT usage to be more practical.The new “logical rowids” contain the primary key of the referenced row, instead of a physical location within thedatabase. This means that if a row is moved to a new location, the logical rowid would still contain a validreference to the row and would not need to be updated. To retain the speed of conventional rowids, logical rowidsalso contain a physical location hint, which can be used to optimize performance. If an index is created on a fieldother than the primary key (note that IOTs support secondary indexes in 8i), Oracle8i will create a logical indexusing logical rowids to reference the table rows.

One caveat that does remain even in 8i is row-chaining, which is inevitable with large-sized rows.

Page 11: Art of indexing_in_o8i

Paper #135 / Page 11

Bit-mapped indexes

The biggest difference between a b-tree index and a bit-mapped index is that the former encourages indexing oncolumns with high selectivity, whereas the latter encourages low selectivity. This is primarily due to inherentstructural differences. A bit-mapped index creates a binary bit-map stream, consisting of 1s and 0s for everydistinct key-value. It uses distinct bits (of every byte) to create these binary steams. Due to application of bit-levelbinary arithmetic, large-numbers of rows can be processed at high speeds. Also, if a table has multiple low-cardinality columns, then bit-map indexes may be created on each of those columns. Multiple indexes in turn willbe merged using bit-level boolean logic resulting in very fast index-access times. In the case of compoundindexes, a bit-mapped index can have upto a maximum of 14 columns in Oracle7.x and upto 30 columns inOracle8/8i (always 2 less than B*Tree indexes). Bit-mapped indexes are available only with CBO.

Bit-mapped indexes are stored in compressed form, thereby causing huge space-savings. Oracle uses a proprietarycompression algorithm, capable of handling even high-cardinality columns (this is useful in scenarios such as astar-schema, where the dimension tables have very high cardinality). On a data-warehouse at a client-site, a B-treeindex on a column with 2 distinct values on a table with 12 million rows took up around 184Mb, whereas a bit-mapped index on the same column now takes up a mere 3Mb – a direct saving of more than 98% disk-space!

The star-transformation access-path (introduced in Oracle8) uses bit-mapped indexes to merge results frommultiple dimension tables with a large fact table to produce extremely fast query results, without having toperform an expensive Cartesian product as in a regular star query.

An example of a good candidate for a bit-mapped index would be the “marital status” column on the customertable in a data-mart for the Sales department. The marital status column would have a domain consisting of thefollowing 4 values : single, married, divorced, widowed. The logical view of a bit-mapped index created on thiscolumn is represented in table 2a :

'RPDLQ 9DOXH

&XVWRPHU /DVW1DPH

6LQJOH 0DUULHG 'LYRUFHG :LGRZHG %LW�PDSSDWWHUQ

6PLWK � � � � ����

:KLWH � � � � ����

%URZQ � � � � ����

%ODFN � � � � ����

Table 2a : column “Bit-map pattern” provides the logical index-key pattern [remember a bit may either be on (1) or off (0) ]

From a physical standpoint, each bit-map stream consists of a header portion and a body, wherein the headercomprises of the key-value and the beginning and ending rowids for a range of rows having that key-value. Thebody comprises of the actual bit-map stream for the same range of rows. The size of the body is guaranteed to be50% or less of the initialization parameter DB_BLOCK_SIZE. Thus, a bitmap may be stored as smaller pieces toadhere to this rule. The physical structure is depicted in figure 2b.

Page 12: Art of indexing_in_o8i

Paper #135 / Page 12

Header body

{Key-value} {begin ROWID} {end ROWID} {bit-map stream}

In Oracle7 :

Key-value Begin ROWID End ROWID Bit-map stream

‘Salaried’ 12345211.3415.5642 12345211.3415.5822 0101010000111

In Oracle8/8 i :

Key-value Begin ROWID End ROWID Bit-map stream

‘Salaried’ AAAAgmAADAAAAE8AAB AAAAgoAADAAAAFVAAC 01110111011

Figure 2b: Physical view of a bit-mapped index

A few things to consider while creating a bit-map index :

• Is the table often queried by this column ? If not, is an index really needed on the column ?

• How many rows are present in the table ? A small table (a few thousand rows) does not warrant a bit-mappedindex.

• Does the column have low cardinality ? If not, is the degree of uniqueness high ? Degree of uniqueness maybe depicted as “number of unique values / total number of rows in the table * 100”.

• Is the column prone to frequent DML (non-batch, OLTP DML) ? If so, it’s probably a good idea to leave italone or maybe use it as a secondary column to create a regular B-tree index (the leading column should havehigh selectivity).

• Are there multiple columns in the table, which are good candidates for bit-mapped indexes ? If so, would suchcolumns be frequently referred in the WHERE clause of queries ? If so, there are high chances of the bit-mapped indexes on these columns being merged by CBO to provide high query throughput.

• Is the data in the table subject to frequent changes simultaneously by multiple users ? If so, a bit-mappedindex may not be convenient. Bit-mapped indexes restrict concurrency, due to the entire bit-map being lockedduring a single DML operation affecting any row in the range of rows represented by the bit-map. This couldpotentially cause hundreds of rows to be locked by a single DML statement, intending to affect a single row.This is not an issue during parallel DML operations affecting partitioned tables, since index-maintenanceduring such parallel DML occurs at the partition-level. As such, a single index partition is not subjected toparallelism, thus leading to concurrency problems.

Function-based indexing

This new feature introduced in Oracle8i allows functions to be indexed and used in SQL statements. EarlierOracle releases expressly disabled index-usage when functions were utilized on the key column. However now,SQL or user-defined functions can be indexed. For instance, a query such as the following would benefittremendously from function-based indexes :

SQL> SELECT * FROM names WHERE INITCAP(name) LIKE ‘Bill%’;

Page 13: Art of indexing_in_o8i

Paper #135 / Page 13

An index can be created on a function as follows :

CREATE SQL> INDEX upper_nm_idx01 ON names(INITCAP(name))

TABLESPACE <tblspc_nm> STORAGE(<storage_clause>);

Now, applications can issue case-insensitive queries such as the following :

SQL> SELECT * FROM name WHERE name LIKE ‘Bill%’;

Function-based indexes are also physically stored in a B-tree structure. Please refer to the Oracle8i documentation(Oracle8i Concepts) set for further explanation of function-based indexing.

Application domain indexes

Application domain indexes are “extended” indexes specific to a certain application domain. These are indexescreated on non-conventional data-types (domains) such as spatial data and multimedia data-types such as imagesand audio/video streams, spreadsheets and documents, etc. Here, an encapsulated set of code-routines referred toas an indextype define the index access and implementation rules for efficient and speedy retrieval of complexdomains. A data-cartridge is custom-built (via the Oracle8i Data-cartridge Interface) to define the applicationthat controls the domain and also defines the inherent structure of the domain index. In other words, all operationsperformed on the index such as range scans, equality checks, etc. need to be explicitly defined. The index can bestored inside the database as an index-organized table or externally, as a file. As of Oracle8i v8.1.5, a domainindex can have only a single column. This column can be a regular scalar data-type or a user-defined attribute ofan object or a LOB. Information on domain indexes (meta-data) cannot be found in conventional index-relatedviews such as DBA_INDEXES, USER_INDEXES, etc. The data-cartridge needs to utilize a specific index meta-data management strategy and nominate a pre-defined table to hold the meta-data on domain indexes of a certainindextype. Furthermore, wrapper views can be created on top of the meta-data table, if necessary, for additionalencapsulation. The Oracle8i documentation set (Oracle8i Data Cartridge Developer's Guide) discussesapplication domain indexes in detail.

Abstract Data-types & their Indexes

Peek into your abstract data-types with a magnifying glass to identify the scalar data-types, which need indexing.Once the abstract type has been broken down into scalars, all the above mentioned indexing rules apply to them.Whenever possible, ensure that all your DML specifies the absolute path to these scalar types (else the optimizermay not use the right index). By absolute path, I mean “person.name.last_name” and not just “person” even“person.name”.

Considering an index-rebuild

This is the million-dollar question that many DBAs and developers face : when should one think about re-buildingan application’s indexes ? I have noted that in some organizations, there is a fixed schedule : every 3 months,indexes are re-created on tables, undergoing major DML activity. However some of these frequent index-rebuildsmight be unnecessary or even indexes of less frequently used tables might need to be re-built on a more regular

Page 14: Art of indexing_in_o8i

Paper #135 / Page 14

basis. How does one determine whether they need to be re-built or not ? How does one get actual numbersindicating that an index is a candidate for re-building ? By using the command ANALYZEINDEX…VALIDATE STRUCTURE. There is another old command called VALIDATE INDEX. This is stillavailable in Oracle8/8i (mainly for backward compatibility). Either of these commands populate theINDEX_STATS view with meaningful information, that helps in determining whether an index should be re-builtor not. Please refer to figure 3a for a description of the structure of INDEX_STATS view.

As is apparent from the structure, DBAs and developers can use the INDEX_STATS view to glean some usefulinformation. The statistics in INDEX_STATS are different from the ones in DBA_INDEXES. The statistics inDBA_INDEXES are created as a result of using the COMPUTE STATISTICS or ESTIMATE STATISTICSoptions of the ANALYZE command and are used by the cost-based optimizer (CBO). The statistics inINDEX_STATS are created by the VALIDATE STRUCTURE option of the ANALYZE command. Anotherimportant difference is that the ANALYZE STRUCTURE…DELETE STATISTICS command erases statistics inDBA_INDEXES; however the statistics in INDEX_STATS are not erased. They get erased only when the indexis dropped or get re-computed every time the ANALYZE…VALIDATE STRUCTURE command is run. TheINDEX_STATS view only contains information about one index at any given point in time. Don’t worry aboutold statistics in the INDEX_STATS misleading CBO. CBO does not use the statistics in INDEX_STATS.

SQL> desc sys.index_stats

Name Null? Type My comments

------------------------------- -------- ---- --------------

HEIGHT NUMBER Height of tree

BLOCKS NUMBER

NAME VARCHAR2(30)

PARTITION_NAME VARCHAR2(30)

LF_ROWS NUMBER Leaf rows

LF_BLKS NUMBER Leaf blocks

LF_ROWS_LEN NUMBER

LF_BLK_LEN NUMBER

BR_ROWS NUMBER Branch rows

BR_BLKS NUMBER Branch blocks

BR_ROWS_LEN NUMBER

BR_BLK_LEN NUMBER

DEL_LF_ROWS NUMBER Deleted leaves

DEL_LF_ROWS_LEN NUMBER

DISTINCT_KEYS NUMBER

MOST_REPEATED_KEY NUMBER

BTREE_SPACE NUMBER

USED_SPACE NUMBER

PCT_USED NUMBER

ROWS_PER_KEY NUMBER = 1 for UNIQUE keys

BLKS_GETS_PER_ACCESS NUMBER

Figure 3a : description of the INDEX_STATS view in an Oracle8/8i database

Page 15: Art of indexing_in_o8i

Paper #135 / Page 15

COLUMN name FORMAT a25 -- increase if your index-names are > 25 chars

COLUMN “Total leaf rows” FORMAT 999,999,999,999

COLUMN “Deleted leaf rows” FORMAT 999,999,999,999

SELECT name, lf_rows “Total leaf rows”, del_lf_rows “Deleted leaf rows”

FROM SYS.index_stats

WHERE name = UPPER(‘&enter_index_name’);

Figure 3b : Query to determine whether index is to be re-built (to be run after ANALYZE INDEX…VALIDATE STRUCTURE

as a DBA user)

The query listed in figure 3b provides the total leaf rows and the deleted leaf rows for a given index. If the deletedleaf rows seem relatively high compared to the total leaf rows, then the index should be re-built. I use 20% as ageneral rule-of-thumb. If the deleted leaf rows are 20% or more of the total leaf rows, I rebuild the index(provided Management waves the green flag!). Also , the command provided in appendix A (ALTER SESSIONSET EVENTS…) provides additional information to help in deciding whether an index should be rebuilt.

The REBUILD option

Starting with Oracle7 7.3, you can use the REBUILD option of the ALTER INDEX command to rebuild indexes.You can even use this option to reverse a regular B-Tree index and vice-versa. However a caveat with REBUILDis, it does not detect and eliminate index corruption, since it bases the rebuild on the existing index (which mightbe corrupt already). (This holds true even in Oracle8i.)

ANALYZE TABLE …VALIDATE STRUCTURE CASCADE is the best mechanism to detect index-corruption.This command matches each row in the table against all indexes and each row in every index against the table. Ifthere is any corruption found, an error message is returned. Subsequently the index may be dropped and re-builtusing the regular DROP and CREATE INDEX commands, rather than the REBUILD option of the ALTERINDEX command.

Conclusion

By selecting an approach that considers the tips outlined in this paper, you can not only determine when to rebuildan index, but also how should the new index be re-built – as a reverse-key index or as a partitioned index or evenconverting a table to an index-organized table.

Even though Oracle8i has vastly increased the number of indexing options available, B-Tree indexes continue tooffer the best solution in most cases. As long as they are well-maintained, they provide acceptable performanceunder diverse conditions. However, certain situations, explained above, prove to be ideal for deviating from theB-Tree approach and instead using another index type such as bit-mapped, or even variations of the B-Tree suchas reverse-key, function-based, partitioned or index-organized tables. With the versatile index offerings ofOracle8i, there has never been a better time to put on that new thinking cap and look at the process of indexing asan art, not a science!

Page 16: Art of indexing_in_o8i

Paper #135 / Page 16

Appendix A

Command to dump an index tree structure

The command “ALTER SESSION SET EVENTS ‘IMMEDIATE TRACE NAME TREEDUMP LEVEL<level_number>’;” dumps the structure of an index b-tree. It gives useful information such as number of levels inthe index and number of rows per level. Such information helps the DBA to decide whether an index needs to bere-built and if so, whether it should be reversed.

The <level_number> mentioned in the command above needs to be replaced with an actual number. InOracle8/8i, this level-number is the “object_id” of the index in SYS.DBA_OBJECTS.

Running the command gives a trace-file in the “user_dump_dest” directory. The trace-file name usually is of theform : “ora_<SID_NAME>_<PID#>.trc” , where SID_NAME is the ORACLE_SID and PID# is the process-idnumber. Immediately after running this command, you can go to the user_dump_dest directory and run thefollowing command : “ls –lt” to pick up the latest trace file. A sample trace-file is provided in fig. 4a. The lastlines of the dump-file contain the meat of the output (between “begin tree dump” and “end tree dump”). The tracefile size is directly proportional to the size of the index.

The file-contents can be interpreted by looking at the number of levels : the branches and the leaves per branch. Inthe sample file provided in fig. 4a, there is only one branch (0x40002b ) and 4 leaves (0x4003f0, 0x4003f1,

0x400b0f and 0x400b10) , since the dump was produced for a small index with just 1,819 rows. The rowscontained in each leaf are 503, 418, 498 and 400 respectively. This indicates that all the leaves are well-balanced(in terms of rows per leaf). So, it is a good idea to leave this index alone. However if the rows in some leavesseem excessively high compared to other leaves, then probably those leaves are hot-spots and could lead to I/Obottlenecks.

Generally, spurts of sequential values or large delete-jobs cause such uneven distribution of keys. If the culprit ismerely a huge batch delete job, then probably the situation can be set right by partitioning the table and truncatingthe partition. If partitioning is a not a feasible solution, then re-building the index (without reversing) after suchbatch-deletes would help. Alternatively, if the imbalance is caused by spurts of sequential values being inserted inthe table, then reversing the key(s) would help.

Page 17: Art of indexing_in_o8i

Paper #135 / Page 17

Dump f i l e /o pt/ app/ or acl e/a dmi n/d v01/ udump/ or a_dv0 1_4231. trc

Ora cle 8 E nte r pr i se Edi ti on Rel eas e 8 . 1.5.0 . 0 – Pro duc t ion

PL/ SQL Rel ease 8.1 . 5. 0. 0 – Pro duc t ion

ORACLE_HOME = / opt / app/o r ac l e/ pro duc t / 8.1.5

Sys t em name: sa mple

Node n ame: sa mple

Rel eas e: 4.0

Ver sio n: 3.0

Mac hin e: 3446A

I ns t ance name: dv01

Redo t hre ad mounte d b y t hi s in sta nce : 1

Ora cl e pr oce ss number : 7

Uni x p r oc ess pi d: 4231, i mage: or acl edv01

Wed Ju l 1 4 1 1:5 2:1 1 1 999

* ** SE SSI ON I D: ( 8. 69) 19 99. 07. 14. 11. 52.11. 000

- -- - - begi n t re e d ump

bra nch : 0 x40002b 4 194347 (0 : n r ow: 4 , leve l : 1)

l eaf : 0x4 003f 0 4195312 ( - 1: nr ow: 503 rro w: 503)

l eaf : 0x4 003f 1 4195313 ( 0: nro w: 418 rr ow: 4 17)

l eaf : 0x4 00b0f 4197135 ( 1: nro w: 498 rr ow: 4 98)

l eaf : 0x4 00b10 4197136 ( 2: nro w: 400 rr ow: 4 00)

- -- - - end tr ee dump

Figure 4a : Sample trace-file (output of the ALTER SESSION…SET EVENTS command described in appendix A)

Warning : Please use all commands and scripts provided in this paper at your own risk. I have used all the commands, provided herein,

multiple times on Oracle8 v8.0.x and v8.1.x databases without any kind of adverse effect. However neither I nor my company, Immedient(formerly Raymond James Consulting), warrant that this document is error-free and/or assume any risk associated with it’s usage . Please

test all scripts and commands on a development database prior to usage on a production database.

References

• Oracle8i Server Documentation

• Oracle 24x7 Tips and Techniques; Venkat S. Devraj; Oracle Press (Osborne/McGraw-Hill ); 1999