Cost Control: Inside the Oracle Optimizer This article has the following sections: • • • • • • • •
Part 1 - Using SQL CBO Parameters Part 2 - Using CBO statistics Part 3 - Proper SQL development environment Part 4 - Using histograms to tune SQL Part 5 - Clustering and SQL tuning Part 6 - External costs and SQL execution Part 7 - Using hints to tune SQL Part 8 - Locating sub-optimal SQL
So let's start by examining the CBO optimizer modes and the Oracle parameters that influence the CBO.
PART 1 - CBO Parameters The CBO is influenced by many configuration settings. Your settings for important CBO parameters can have a dramatic impact of CBO performance, and this is the place to start when exploring the CBO. Let's start by choosing your CBO optimizer_mode and then examine other important CBO parameters. The CBO and optimizer modes. In Oracle9i Database there are four optimizer modes, all determined by the value of the optimizer_mode parameter: rule, choose, all_rows, and first_rows. The rule and choose modes reflect the obsolete rule-based optimizer, so we will focus on the CBO modes here. The optimizer mode can be set at the system-wide level, for an individual session, or for a specific SQL statement: alter system set optimizer_mode=first_rows_10; alter session set optimizer_goal = all_rows; select /*+ first_rows(100) */ from student;
We need to start by defining what is the "best" execution plan for a SQL statement. Is the best execution plan the one that begins to return rows the fastest, or is the best execution plan the one that executes with the smallest amount of computing resources? Of course, the answer depends on the processing needs of your database. Let's take a simple example. Assume the following query:
select customer_name from customer where region = 'south' order by customer_name;
If the best execution plan is the one that starts to return rows the fastest, a concatenated index on region and customer_name could be used to immediately start delivering table rows in their proper order, even though excess I/O will be required to read the nonadjacent data blocks (see Figure 1).
Let's assume that this execution plan starts delivering results in .0001 seconds and requires 10,000 db_block_gets. But what if your goal is to minimize computing resources? If this SQL is inside a batch program, then it is not important to start returning rows quickly, and a different execution plan would take fewer resources. In this example, a parallel full-table scan followed by a back-end sort will require less machine resources and less I/O because blocks do not have to be reread to pull the data in sorted order (see Figure 2). In this example, we expect the result to take longer to deliver (no rows until the sort is complete), but we will see far less I/O because blocks will not have to be reaccessed to deliver the rows in presorted order. Let's assume that this execution plan delivers the result in 10 seconds with 5,000 db_block_gets.
Oracle offers several optimizer modes that allow you to choose your definition of the "best" execution plan for you:
This CBO mode will return rows as soon as possible, even if the overall query runs longer or consumes more computing resources than other plans. The first_rows optimizer_mode usually involves choosing an index scan over a full-table scan because index access will return rows quickly. Because the first_rows mode favors index scans over full-table scans, the first_rows mode is more appropriate for OLTP systems where the end user needs to see small result sets as quickly as possible.
•
optimizer_mode=first_rows_
•
optimizer_mode=all_rows_
•
optimizer_mode=first_rows_n This
•
Optimizer_mode=rule The
This CBO mode ensures that the overall computing resources are minimized, even if no rows are available until the entire query has completed. The all_rows access method often favors a parallel full-table scan over a full-index scan, and sorting over presorted retrieval via an index. Because the all_rows mode favors full-table scans, it is best suited for data warehouses, decision-support systems, and batch-oriented databases where intermediate rows are not required for real-time viewing.
Oracle9i Database optimizer mode enhancement optimizes queries for a small, expected return set. The values are first_rows_1, first_rows_10, first_rows_100, and first_rows_1000. The CBO uses the n in first_rows_n as an important driver in determining cardinalities for query result sets. By telling the CBO, a priori, that we only expect a certain number of rows back from the query, the CBO will be able to make a better decision about whether to use an index to access the table rows.
rule-based optimizer (RBO) is the archaic optimizer mode from the earliest releases of Oracle Database. The rule-based optimizer has not been updated in nearly a decade and is not recommended for production use because the RBO does not support any new features of Oracle since 1994 (such as bitmap indexes, table partitions, and function-based indexes).
While the optimizer_mode is the single most important factor in invoking the cost-based optimizer, there are other parameters that influence the CBO behavior. Let's take a quick look at these parameters. Oracle parameters that influence the CBO. While the optimizer_mode parameter governs the global behavior of the CBO, there are many other Oracle parameters that have a great impact on CBO behavior. Because of the power of the CBO, Oracle provides several system-level parameters that can adjust the overall behavior of the CBO. These adjustment parameters generally involve the choice of using an
index versus doing a full-table scan, and the CBO's choice of table join methods. However, Oracle does not recommend changing the default values for many of these CBO setting because the changes can affect the execution plans for thousands of SQL statements. Here are the major optimizer parameters: This parameter alters the costing algorithm for access paths involving indexes. The smaller the value, the lower the cost of index access.
•
optimizer_index_cost_adj_
•
optimizer_index_caching_ This
•
db_file_multiblock_read_count_
•
parallel_automatic_tuning_ When
•
hash_area_size (if not using pga_aggregate_target) _ The setting for hash_area_size parameter governs the propensity of the CBO to favor hash
parameter tells Oracle how much of your index is likely to be in the RAM data buffer cache. The setting for optimizer_index_caching affects the CBO's decision to use an index for a table join (nested loops) or to favor a full-table scan.
When this parameter is set to a high value, the CBO recognizes that scattered (multiblock) reads may be less expensive than sequential reads. This makes the CBO friendlier to full-table scans.
set to "on", this parameter parallelizes fulltable scans . Because parallel full-table scans are very fast, the CBO will give a higher cost to index access and be friendlier to full-table scans.
joins
over nested loop and sort merge table joins.
•
(if not using pga_aggregate_target) _ The sort_area_size influences the CBO when deciding whether to perform an index access or a sort of the result set. The higher the value for sort_area_size, the more likely that a sort will be performed in RAM, and the more likely that the CBO will favor a sort over presorted index retrieval. sort_area_size
The idea optimizer settings depend on your environment and are heavily influenced by your system's costs for scattered disk reads
versus sequential disk reads. Listing 1 contains a great script you can use to measure these I/O costs on your database. LISTING 1: optimizer_index_cost_adj.sql col c1 heading 'Average Waits|forFull| Scan Read I/O' format 9999.999 col c2 heading 'Average Waits|for Index|Read I/O' format 9999.999 col c3 heading 'Percent of| I/O Waits|for Full Scans' format 9.99 col c4 heading 'Percent of| I/O Waits|for Index Scans' format 9.99 col c5 heading 'Starting|Value|for|optimizer|index|cost|adj' format 999 select a.average_wait c1, b.average_wait c2, a.total_waits /(a.total_waits + b.total_waits) c3, b.total_waits /(a.total_waits + b.total_waits) c4, (b.average_wait / a.average_wait)*100 c5 from v$system_event a, v$system_event b where a.event = 'db file scattered read' and b.event = 'db file sequential read' ;
Now that we understand the CBO parameters, let's look at how we can help the CBO make good execution-plan decisions by providing the CBO with information about our schema.
PART 2 - CBO Statistics The most important key to success with the CBO is to carefully define and manage your statistics. In order for the CBO to make an intelligent decision about the best execution plan for your SQL, it must have information about the table and indexes that participate in the query. When the CBO knows the size of the tables and the distribution, cardinality, and selectivity of column values, the CBO can make an informed decision and almost always generates the best execution plan. Let's examine the following areas of CBO statistics and see how to gather top-quality statistics for the CBO and how to create an appropriate CBO environment for your database. Getting top-quality statistics for the CBO. The choices of executions plans made by the CBO are only as good as the statistics available to it. The old-fashioned analyze table and dbms_utility methods for generating CBO statistics are obsolete and somewhat dangerous to SQL performance. As we may know, the CBO uses object statistics to choose the best execution plan for all SQL statements.
The dbms_stats utility does a far better job in estimating statistics, especially for large partitioned tables, and the better statistics result in faster SQL execution plans. Here is a sample execution of dbms_stats with the OPTIONS clause: exec dbms_stats.gather_schema_stats( ownname => 'SCOTT', options => 'GATHER AUTO', estimate_percent => dbms_stats.auto_sample_size, method_opt => 'for all columns size repeat', degree => 34 ) There are several values for the OPTIONS parameter
that we need to
know about: •
GATHER_ reanalyzes
the whole schema
•
GATHER EMPTY_ only analyzes
•
GATHER STALE_ only reanalyzes
tables that have no existing statistics tables with more than 10 percent modifications
(inserts, updates, deletes) •
will reanalyze objects that currently have no statistics and objects with stale statistics. Using GATHER AUTO is like combining GATHER STALE and GATHER AUTO_
GATHER EMPTY.
Note that both GATHER STALE and GATHER AUTO require monitoring. If you issue the ALTER TABLE XXX MONITORING command, Oracle tracks changed tables with the dba_tab_modifications view. Below we see that the exact number of inserts, updates and deletes are tracked since the last analysis of statistics: SQL> desc dba_tab_modifications; Name Type -------------------------------TABLE_OWNER VARCHAR2(30) TABLE_NAME VARCHAR2(30) PARTITION_NAME VARCHAR2(30) SUBPARTITION_NAME VARCHAR2(30) INSERTS NUMBER UPDATES NUMBER DELETES NUMBER TIMESTAMP DATE TRUNCATED VARCHAR2(3)
The most interesting of these options is the GATHER STALE option. Because all statistics will become stale quickly in a robust OLTP database, we must remember the rule for GATHER STALE is > 10% row change (based on num_rows at statistics collection time). Hence, almost every table except read-only tables will be reanalyzed with the
option, making the GATHER STALE option best for systems that are largely read-only. For example, if only five percent of the database tables get significant updates, then only five percent of the tables will be reanalyzed with the GATHER STALE option. Automating sample size with dbms_stats.The better the quality of the statistics, the better the job that the CBO will do when determining your execution plans. Unfortunately, doing a complete analysis on a large database could take days, and most shops must sample your database to get CBO statistics. The goal is to take a large enough sample of the database to provide top-quality data for the CBO. Now that we see how the dbms_stats option works, let's see how to specify an adequate sample size for dbms_stats. In earlier releases, the DBA had to guess what percentage of the database provided the best sample size and sometimes underanalyzed the schema. Starting with Oracle9i Database, the estimate_percent argument is a great way to allow Oracle's dbms_stats to automatically estimate the "best" percentage of a segment to sample when gathering statistics: GATHER STALE
estimate_percent => dbms_stats.auto_sample_size
After collecting automatic sample sizes, you can verify the accuracy of the automatic statistics sampling by looking at the sample_size column on any of these data dictionary views: • • • • • • • • • • • • •
DBA_ALL_TABLES DBA_INDEXES DBA_IND_PARTITIONS DBA_IND_SUBPARTITIONS DBA_OBJECT_TABLES DBA_PART_COL_STATISTICS DBA_SUBPART_COL_STATISTICS DBA_TABLES DBA_TAB_COLS DBA_TAB_COLUMNS DBA_TAB_COL_STATISTICS DBA_TAB_PARTITIONS DBA_TAB_SUBPARTITIONS
Note that Oracle generally chooses a sample_size from 5 to 20 percent when using automatic sampling, depending on the size of the tables and the distribution of column values. Remember, the better the quality of your statistics, the better the decision of the CBO. Now that we understand the value of CBO statistics, let's look at ways that the CBO statistics are managed in a successful Oracle shop.
PART 3 - Proper Development Environment
Many infrastructure issues must be addressed in order to avoid surprises with SQL optimization. Shops that do not create this infrastructure are plagued by constantly changing SQL execution plans and poor database performance. The key to success with the CBO is stability and ensuring your success with the CBO involves several important infrastructure issues. •
Reanalyze statistics only when necessary. One of the most common mistakes made by Oracle DBAs is to frequently re-analyze the schema. Remember, the sole purpose of doing that is to change the execution plans for your SQL, and if it ain't broke, don't fix it. If you are satisfied with your current SQL performance, reanalyzing a schema could cause significant performance problems and undo the tuning efforts of the development staff. In practice, very few shops are sufficiently dynamic to require periodic schema re-analysis.
•
Force developers to tune their SQL. Many developers falsely assume that their sole goal is to write SQL statements that deliver the correct data from Oracle. In reality, formulating the SQL is only half their job. Successful Oracle shops always require that developers ensure that their SQL accesses the database in an optimal fashion and require migration forms that include the execution plan for all new SQL.
•
Carefully manage CBO statistics. Successful Oracle shops carefully manage the CBO statistics to ensure that the CBO works the same in their test and production environments. A savvy DBA will collect high-quality statistics and migrate their production statistics into their test environments. This approach ensures that all SQL migrating into production has the same execution plan as it did in the test database.
•
Rarely change CBO parameters. The CBO parameters are very dangerous because a single parameter change could adversely affect the performance of an entire enterprise. Changes to critical CBO parameters such as optimizer_mode, optimizer_index_cost_adj, and optimizer_index_caching should only be made after careful system testing.
•
Ensure static execution plans. Nobody like surprises, and successful CBO shops lock down SQL execution plans by carefully controlling CBO statistics, using stored outlines optimizer plan stability, or adding detailed hints to their SQL.
Let's take a closer look at these issues.
Re-analyze statistics only when necessary. It is very rare for the fundamental nature of a schema to change; large tables remain large, and index columns rarely change distribution, cardinality, and skew. You should only consider periodically re-analyzing your total schema statistics if your database matches these criteria: •
Data-analysis databases. Many scientific systems load experimental data, analyze the data, produce reports, and then truncate and reload a new set of experiments. For these types of systems it may be necessary to re-analyze the schema each time the database is reloaded.
•
Highly volatile databases. In these rare cases, the size of table and the characteristics of index column data changes radically. For example, if you have a table that has 100 rows one week and 10,000 rows the next week, then you may want to consider a periodic reanalysis of statistics.
Force developers to tune their SQL. It is amazing how many Oracle shops do not consider their SQL execution plans. They assume that because the CBO is intelligent and sophisticated it will always provide the best execution plan, no matter what. Because SQL is a declarative language, a query can be written in many different ways, each with a different execution plan. For example, all of the following SQL queries give the correct answer, but with widely varying execution plans: -- Form one using non-correlated subquery) select book_title from book where book_key not in (select book_key from sales); Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=64) 1 0 FILTER 2 1 TABLE ACCESS (FULL) OF 'BOOK' (Cost=1 Card=1 Bytes=64) 3 1 TABLE ACCESS (FULL) OF 'SALES' (Cost=1 Card=5 Bytes=25) -- Form two using outer join select book_title from book b,
sales s where b.book_key = s.book_key(+) and quantity is null; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=100 Bytes=8200) 1 2 3 4 5
0 FILTER 1 FILTER 2 HASH JOIN (OUTER) 3 TABLE ACCESS (FULL) OF 'BOOK' (Cost=1 Card=20 Bytes=1280) 3 TABLE ACCESS (FULL) OF 'SALES' (Cost=1 Card=100 Bytes=1800)
-- Form three using correlated subquery select book_title from book where book_title not in ( select distinct book_title from book, sales where book.book_key = sales.book_key and quantity > 0); Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=59) 1 0 FILTER 2 1 TABLE ACCESS (FULL) OF 'BOOK' (Cost=1 Card=1 Bytes=59) 3 1 FILTER 4 3 NESTED LOOPS (Cost=6 Card=1 Bytes=82) 5 4 TABLE ACCESS (FULL) OF 'SALES' (Cost=1 Card=5 Bytes=90) 6 4 TABLE ACCESS (BY INDEX ROWID) OF 'BOOK' (Cost=1 Card=1) 7 6 INDEX (UNIQUE SCAN) OF 'PK_BOOK' (UNIQUE)
As we can see, the proper formulation of the query has a dramatic impact on the execution plan for the SQL. Savvy Oracle developers know the most efficient way to code Oracle SQL for optimal execution plans, and savvy Oracle shops train their developers to formulate efficient SQL. Some techniques for assisting developers in tuning their SQL include:
•
Training them to use the autotrace and TKPROF utilities and to interpret SQL execution results. Oracle University has several excellent classes on CBO optimization techniques.
•
Forcing all SQL that is migrating into production to have verification that the SQL has been tuned.
•
Making performance an evaluation criterion. Instead of noting that the best developer is the developer who writes SQL the fastest, add the mandate that a good developer also writes SQL that performs efficiently.
Carefully manage CBO statistics. Because the CBO relies on information about database objects, it is imperative that the CBO has the best possible statistics and that the same excellent statistics be used in the production, test, QA, and development instances. It is an important job of the Oracle DBA to properly gather and distribute statistics for the CBO. The goal of the DBA is to keep the most accurate production statistics for the current processing. In some cases, there may be more than one set of optimal statistics. For example, the best statistics for OLTP processing may not be the best statistics for the data warehouse processing that occurs each evening. In this case, the DBA will keep two sets of statistics and import them into the schema when processing modes change. Exporting CBO statistics is done with the export_system_stats procedure in the dbms_stats package. In this example we export the current CBO statistics into a table called stats_table_oltp: dbms_stats.export_system_Stats('stats_table_oltp');
When captured, we can move the table to other instances and use the import_system_stats procedure in dbms_stats to overlay the CBO statistics when processing modes change: dbms_stats.import_system_stats('stats_table_oltp'); dbms_stats.import_system_stats('stats_table_dss');
Change CBO parameters only rarely. Many Oracle shops change the fundamental characteristics of their CBO by changing the global CBO parameters. Especially dangerous are changes to optimizer_mode, optimizer_index_cost_adj, and optimizer_index_caching, and these changes should only be made when a sound reason exists. Other CBO parameters such as hash_area_size and sort_area_size are less dangerous and can be set at the individual session level to change the CBO evaluates a query. Ensure static execution plans. Remember, re-analyzing a schema could cause thousands of SQL statements to change execution plans. Many
Oracle shops have implemented standards that require that all SQL, when tested and approved in their test environment, function identically in production. The only way to achieve this mandate is to migrate the statistics that were used in SQL testing into the production environment when the SQL is migrated. However, the DBA must ensure that a migration of statistics from test into production does not adversely affect the execution plans of other SQL that touch the target table. Hence, the DBA will carefully manage the CBO statistics, ensuring that no SQL changes execution plans after it is migrated into production.
PART 4 - Considering the CBO
While we have gone into great detail on the CBO, there is always more to learn as the CBO becomes more powerful (and complex) with each new release of Oracle. The main points of this article include general guidelines for adjusting the behavior of the CBO: •
The DBA can control the overall behavior of the CBO with several Oracle parameters, but they should only be changed under limited circumstances.
•
The CBO relies on statistics to determine the optimal execution plan for SQL statements, and statistics should always be collected with the dbms_stats package.
•
An important job of the Oracle DBA is the collection and management of statistics for the CBO. CBO statistics can be collected, stored, and migrated to other related instances to ensure consistency of execution plans.
•
Re-analyzing schema statistics without exporting the old statistics (using export_system_stats) is dangerous because the execution plans for thousands of SQL statements may change, and you cannot get back to previous SQL performance. Re-analyze a schema only when there are significant changes to the data.
In Part 1 of the series, we discussed the basic mechanisms of Oracle's cost-based SQL optimizer for making the best decisions about the access paths to data. In this concluding installment, we'll address the use of histograms, external costing features, SQL hints for changing execution plans, and techniques for locating and tuning suboptimal SQL. Using Histograms In some cases, the distribution of values within a column of a table will affect the optimizer's decision to use an index vs. perform a full-table scan. This scenario occurs when the value with a where clause has a disproportional amount of values, making a full-table scan cheaper than index access.
A column histogram should only be created when we have data skew exists or is suspected. In the real world, that happens rarely, and one of the most common mistakes with the optimizer is the unnecessary introduction of histograms into optimizer statistics. The histograms signals the optimizer that the column is not linearly distributed, and the optimizer will peek into the literal value in the SQL where clause and compare that value to the histogram buckets in the histogram statistics (see Figure 3).
Many Oracle professionals misunderstand the purpose of histograms. While they are used to make a yes-or-no decision about the use of an index to access the table, histograms are most commonly used to predict the size of the intermediate result set from a multi-way table join. For example, assume that we have a five-way table join whose result set will be only 10 rows. Oracle will want to join the tables together in such a way as to make the result set (cardinality) of the first join as small as possible. By carrying less baggage in the intermediate result sets, the query will run faster. To minimize intermediate results, the optimizer attempts to estimate the cardinality of each result set during the parse phase of SQL execution. Having histograms on skewed column will greatly aid the optimizer in making a proper decision. (Remember, you can create a histogram even if the column does not have an index and does not participate as a join key.) Because a complex schema might have tens of thousands of columns, it is impractical to evaluate each column for skew and thus Oracle provides an automated method for building histograms as part of the dbms_stats utility. By using the method_opt=>'for all columns size skewonly' option of dbms_stats, you can direct Oracle to automatically create
histograms for those columns whose values are heavily skewed. We'll take a look at this option in more detail later. As a general rule, histograms are used to predict the cardinality and the number of rows returned in the result set. For example, assume that we have a product_type index and 70% of the values are for the HARDWARE type. Whenever SQL with where product_type='HARDWARE'is specified, a full-table scan is the fastest execution plan, while a query with where product_type='SOFTWARE' would be fastest using index access. Because histograms add additional overhead to the parsing phase of SQL, you should avoid them unless they are required for a faster optimizer execution plan. But there are several conditions where creating histograms is advised: •
When the column is referenced in a query — Remember, there is no point in creating histograms if the queries do not reference the column. This mistake is common, and many DBAs will create histograms on a skewed column, even though it is not referenced by any queries.
•
When there is a significant skew in the distribution of columns values —This skew should be sufficiently significant that the value in the WHERE clause will make the optimizer choose a different execution plan.
•
When the column values cause an incorrect assumption — If the optimizer makes an incorrect guess about the size of an intermediate result set it may choose a sub-optimal table join method. Adding a histogram to this column will often provide the information required for the optimizer to use the best join method.
So how do we find those columns that are appropriate for histograms? One exciting feature of dbms_stats is the ability to automatically look for columns that should have histograms, and create the histograms. Again, remember that multi-bucket histograms add a huge parsing overhead to SQL statements, and histograms should only be used when the SQL will choose a different execution plan based upon the column value. To aid in intelligent histogram generation, Oracle uses the method_opt parameter of dbms_stats. There are also important new options within the method_opt clause, namely skewonly, repeat and auto. method_opt=>'for all columns size skewonly' method_opt=>'for all columns size repeat' method_opt=>'for all columns size auto'
Let's take a close look at each method option. The first is the "skewonly" option, which is very time-intensive because it examines the distribution of values for every column within every index. If dbms_stats discovers an index with columns that are unevenly distributed, it will create histograms for that index to aid the cost-
based SQL optimizer in making a decision about index vs. full-table scan access. For example, if an index has one column that is in 50% of the rows, a full-table scan is faster than and index scan to retrieve these rows. Histograms are also used with SQL that has bind variables and SQL with cursor_sharing enabled. In these cases, the optimizer determines if the column value could affect the execution plan, and if so, replaced the bind variable with a literal and performs a hard parse: begin dbms_stats.gather_schema_stats( ownname => 'SCOTT', estimate_percent => dbms_stats.auto_sample_size, method_opt => 'for all columns size skewonly', degree => 7 ); end; /
The auto option is used when monitoring is implemented (alter table xxx monitoring;) and creates histograms based upon data distribution and the manner in which the column is accessed by the application (e.g. the workload on the column as determined by monitoring). Using method_opt=>'auto'is similar to using the gather auto in the option parameter of dbms_stats: begin dbms_stats.gather_schema_stats( ownname => 'SCOTT', estimate_percent => dbms_stats.auto_sample_size, method_opt => 'for all columns size auto', degree => 7 ); end; /
PART 5 - Selectivity, Clustering, and Histograms It is important to remember that the optimizer has knowledge of many important characteristics of column data within tables, most notably the selectivity of a column values and the clustering factor for the column. For example, here we see a query using a column value to filer the result set: select customer_name from customer where customer_state = 'Rhode Island';
In this example, the choice to use an index versus a full-table scan is influenced by the proportion of customers in Rhode Island. If there are a super-small proportion of customers in Rhode Island and the values are clustered on the data blocks, then an index scan might be the fastest execution plan for this query. Many Oracle developers are perplexed when the optimizer chooses a full-table scan when they are only retrieving a small number of rows, not realizing that the optimizer is considering the clustering of the column values within the table. Oracle provides a column called clustering_factor in the dba_indexes view that tells the optimizer how synchronized the table rows are with the index. When the clustering factor is close to the number of data blocks, the table rows are synchronized with the index. The selectivity of a column value, the db_block_size, the avg_row_len and the cardinality all work together in helping the optimizer decide whether to use and index versus using a full-table scan. If a data column has high selectivity and a low clustering_factor, then an index scan is usually the fastest execution method (see Figure 4).
In cases where most of the SQL references a column with a high clustering_factor, a large db_block_size and a small avg_row_len, the DBA will sometimes periodically re-sequence the table rows or use a singletable cluster to maintain row order. This approach places all adjacent rows in the same data block, removing the full-table scan and making the query up to 30x faster. Conversely, a high clustering_factor, where the value approaches the number of rows in the table (num_rows), indicates that the rows are not in the same sequence as the index, and additional I/O will be required for index range scans. As the clustering_factor approaches the number of rows in the table, the rows are out of sync with the index. However, even if a column has high selectivity, a high clustering_factor and small avg_row_len will indicates that the column values are randomly distributed across the table, and additional I/O will be required to fetch the rows. In these cases, an index range scan would
cause a huge amount of unnecessary I/O (see Figure 5); a full-table scan would be far more efficient.
In sum, the clustering_factor, db_block_size and avg_row_len all influence the optimizer's decision about performing a full-table scan versus an index range scan, and it is important to understand how these statistics are used by the optimizer. As we have noted, the optimizer improves with each new release, and the latest enhancement with Oracle Database 10g is the consideration of external influences when determining an execution plan. Oracle calls this feature external costing and includes both CPU and I/O cost estimates. PART 6 - External Costing with the Optimizer Starting in Oracle9i Database and continuing in Oracle Database 10g, the optimizer has been enhanced to consider external influences when determining the best execution plan. Because the Oracle Database does not run in a vacuum, the optimizer must be able to factor-in the costs of external disk I/O and the cost of CPU cycles for each SQL operation. This process is especially critical for queries running all_rows optimization, where minimizing server resources is a primary goal. •
CPU_COST — The optimizer can now estimate the number of machine cycles required for an operation, and factors this cost into the execution plan calculation. The CPU costs associated with servicing an Oracle query depends upon the current server configuration (which Oracle cannot see). In Oracle Database 10g, CPU costing is the default behavior because of the importance of considering the CPU costs associated with each SQL execution phase—thus, the savvy Oracle professional should turn on CPU costing with dbms_stats.get_system_stats. CPU costs are generally not important unless the entire Oracle instance is using excessive CPU resources.
•
IO_COST — The optimizer had been enhanced to estimate the number of physical block reads required for an operation. The I/O cost is proportional to the
number of physical data blocks read by the operation. However, the optimizer has no a priori knowledge of the data buffer contents and cannot distinguish between a logical read (in-buffer) and a physical read. Because of this shortcoming, the optimizer cannot know if the data blocks are already in the RAM data buffers. According to the Oracle documentation, the I/O and CPU costs are evaluated as follows: Cost = (#SRds * sreadtim + #MRds * mreadtim + #CPUCycles / cpuspeed ) / sreadtim where: #SRDs - number of single block reads #MRDs - number of multi block reads #CPUCycles - number of CPU Cycles *) sreadtim - single block read time mreadtim - multi block read time cpuspeed - CPU cycles per second
Note that the costs are a function of the number of reads and the relative read times, plus the CPU cost estimate for the query. Also note the external costing does not consider the number of data blocks that reside in the RAM data buffers, but a future release of the optimizer is likely to consider this factor. Here we see that Oracle uses the both the CPU and I/O cost estimations in evaluating the execution plans. This equation becomes even more complex when we factor-in parallel query where many concurrent processes are servicing the query. The best benefit for using CPU costing is for all_rows execution plans where costs is more important than with first_rows optimization. Next, let's look at how the optimizer is influenced by statistics. In order to make an intelligent decision about the best execution plan, the optimizer must use information about all of the data objects that are involved in the query. Because you control how the statistics are collected, this aspect of optimizer tuning is a critical. PART 7 - Using Hints to Change Execution Plans As the optimizer becomes more sophisticated with each release, Oracle provides an increasing number of methods for changing the execution plans for your SQL. The most common use for Oracle hints is as a debugging tool. You can use the hints to determine the optimal execution plan, and then work backward, adjusting the statistics to make the vanilla SQL simulate the hinted query.
Using Oracle hints can be very complicated and Oracle developers only use hints as a last resort, preferring to alter the statistics to change the execution plan. Oracle contains more than 124 hints, and many of them are not found in the Oracle documentation. (See Listing 2) Listing 2: Documented Oracle Hints: ALL_ROWS INDEX_SS_ASC PARALLEL AND_EQUAL INDEX_SS_DESC PARALLEL_INDEX ANTIJOIN INLINE PIV_GB APPEND LEADING PIV_SSF BITMAP LIKE_EXPAND PQ_DISTRIBUTE BUFFER LOCAL_INDEXESMATERIALIZE PQ_MAP BYPASS_RECURSIVE_CHECK MERGE PQ_NOMAP BYPASS_UJVC MERGE_AJ PUSH_PRED CACHE MERGE_SJ PUSH_SUBQ CACHE_CB MV_MERGE REMOTE_MAPPED CACHE_TEMP_TABLE NESTED_TABLE_GET_REFS RESTORE_AS_INTERVALS CARDINALITY NESTED_TABLE_SET_REFS REWRITE CHOOSE NESTED_TABLE_SET_SETID RULE CIV_GB NL_AJ SAVE_AS_INTERVALS COLLECTIONS_GET_REFS NL_SJ SCN_ASCENDING CPU_COSTING NO_ACCESS SELECTIVITY CUBE_GB NO_BUFFER SEMIJOIN CURSOR_SHARING_EXACT NO_EXPAND SEMIJOIN_DRIVER DEREF_NO_REWRITE NO_EXPAND_GSET_TO_UNION SKIP_EXT_OPTIMIZER DML_UPDATE NO_FACT SQLLDR DOMAIN_INDEX_NO_SORT NO_FILTERING STAR DOMAIN_INDEX_SORT NO_INDEX STAR_TRANSFORMATION DRIVING_SITE NO_MERGE SWAP_JOIN_INPUTS DYNAMIC_SAMPLING NO_MONITORING SYS_DL_CURSOR DYNAMIC_SAMPLING_EST_CDN NO_ORDER_ROLLUPS SYS_PARALLEL_TXN EXPAND_GSET_TO_UNION NO_PRUNE_GSETS SYS_RID_ORDER FACT NO_PUSH_PRED TIV_GB FIRST_ROWS NO_PUSH_SUBQ TIV_SSF FORCE_SAMPLE_BLOCK NO_QKN_BUFF UNNEST FULL NO_SEMIJOIN USE_ANTI GBY_CONC_ROLLUP NO_STATS_GSETS USE_CONCAT GLOBAL_TABLE_HINTS NO_UNNEST USE_HASH HASH NOAPPEND USE_MERGE HASH_AJ NOCACHE USE_NL HASH_SJ NOCPU_COSTING USE_SEMI HWM_BROKERED NOPARALLEL USE_TTT_FOR_GSETS IGNORE_ON_CLAUSE NOPARALLEL_INDEX IGNORE_WHERE_CLAUSE NOREWRITE INDEX_ASC OR_EXPAND INDEX_COMBINE ORDERED INDEX_DESC ORDERED_PREDICATES INDEX_FFS OVERFLOW_NOMOVE INDEX_JOIN INDEX_RRS INDEX_SS
Undocumented Hints: BYPASS_RECURSIVE_CHECK IGNORE_ON_CLAUSE OVERFLOW_NOMOVE BYPASS_UJVC IGNORE_WHERE_CLAUSE PIV_GB CACHE_CB INDEX_RRS PIV_SSF CACHE_TEMP_TABLE INDEX_SS PQ_MAP CIV_GB INDEX_SS_ASC PQ_NOMAP COLLECTIONS_GET_REFS INDEX_SS_DESC REMOTE_MAPPED CUBE_GB LIKE_EXPAND RESTORE_AS_INTERVALS CURSOR_SHARING_EXACT LOCAL_INDEXES SAVE_AS_INTERVALS DEREF_NO_REWRITE MV_MERGE SCN_ASCENDING DML_UPDATE NESTED_TABLE_GET_REFS SKIP_EXT_OPTIMIZER DOMAIN_INDEX_NO_SORT NESTED_TABLE_SET_REFS SQLLDR DOMAIN_INDEX_SORT NESTED_TABLE_SET_SETID SYS_DL_CURSOR DYNAMIC_SAMPLING NO_EXPAND_GSET_TO_UNION SYS_PARALLEL_TXN DYNAMIC_SAMPLING_EST_CDN NO_FACT SYS_RID_ORDER EXPAND_GSET_TO_UNION NO_FILTERING TIV_GB FORCE_SAMPLE_BLOCK NO_ORDER_ROLLUPS TIV_SSF GBY_CONC_ROLLUP NO_PRUNE_GSETS UNNEST GLOBAL_TABLE_HINTS NO_STATS_GSETS USE_TTT_FOR_GSETS HWM_BROKERED NO_UNNEST NOCPU_COSTING
Let's take a quick look at how hints are used to alter optimizer execution plans: A optimizer hint is an optimizer directive placed inside comments inside your SQL statement and used in those rare cases where the optimizer makes an incorrect decision about the execution plan. Because hints are inside comments, it is important to ensure that the hint name is spelled correctly and that the hint is appropriate to the query. For example, the following hint is invalid because first_rows access and parallel access are mutually exclusive. That's because parallel always assumes a full-table scan and first_rows favors index access.
-- An invalid hint select /*+ first_rows parallel(emp,8)*/ emp_name from emp order by ename;
Some Oracle professionals will place hints together to reinforce their wishes. For example, if we have an SMP server with eight or more CPUs, we may want to use Oracle Parallel Query to speed-up legitimate full-table scans. When using parallel query, we seldom want to turn-on parallelism at the table level (alter table customer parallel 35;)
because the setting of parallelism for a table influences the optimizer, causing the optimizer to see full-table scan is inexpensive. Hence, most Oracle professionals specify parallel query on a query-by-query basis, combining the full hint with the parallel hint to ensure a fast parallel full-table scan: -- A valid hint select /*+ full parallel(emp,35)*/ emp_name from emp order by ename;
Now that we have the general concept of hints, let's take a look at one of the most important hints for optimizer tuning. The ordered hint determines the driving table for the query execution and also specifies the order that tables are joined together. The ordered hint requests that the tables should be joined in the order that they are specified in the from clause, with the first table in the from clause specifying the driving table. Using the ordered hint can save a huge amount of parse time and speed SQL execution because you are telling the optimizer the best order to join the tables. For example, the following query uses the ordered hint to join the tables in their specified order in the from clause. In this example, we further refine the execution plan by specifying that the emp to dept join use a hash join and the sal to bonus join use a nested loop join: select /*+ ordered use_hash (emp, dept) use_nl (sal, bon) */ from emp, dept, sal, bon where . . .
Of course, the ordered hint is most commonly used in data warehouse queries or in SQL that joins more than five tables. Next let's look at another last resort, the adjustment of Oracle parameters. Oracle does not recommend changing any of these parameters, except as a last resort. However, it is interesting to see how these parameters change the way that the optimizer determines execution plans.
Part 8 -
Locating Sub-optimal SQL
While complex queries may have extremely complex execution plans, most Oracle professionals must tune SQL with the following problems: •
Sub-optimal index access to a table — This problem occurs when the optimizer cannot find an index or the most restrictive where clause in the SQL is not matched with an index. When the optimizer cannot find an appropriate index to access table rows, the optimizer will always invoke a full-table scan, reading every row in the table. Hence, a large-table full-table scan might indicate a suboptimal SQL statement that can be tuned by adding an index that matches the where clause of the query.
•
Sub-optimal join methods — The optimizer has many join methods available including a merge join, a nested loop join, hash join and a star join. To choose the right join method, the optimizer must guess at the size of the intermediate result sets from multi-way table joins. To make this guess, the optimizer has incomplete information. Even if histograms are present, the optimizer cannot know for certain the exact number of rows returned from a join. The most common remedy is to use hints to change the join (use_nl, use_hash) or re-analyze the statistics on the target tables.
Let's examine how the v$sql_plan view can help us locate SQL tuning opportunities. When searching for tuning opportunities, we start by interrogating the v$sql_plan view to find these large-table full-table scans as shown in Listing 3. Listing 3: --***************************************************** -- Object Access script report --- (c) 2003 by Donald K. Burleson -- This is freeware, Never to be sold --***************************************************** column column column column column column set set set set
nbr_FTS num_rows blocks owner name ch
format format format format format format
999,999 999,999,999 999,999 a14; a24; a1;
heading on; feedback on; echo off; pages 999;
ttitle 'full table scans and counts| is in the KEEP Pool (Oracle8).' select p.owner, p.name, t.num_rows,
|The "K" indicates that the table
ltrim(t.cache) ch, decode(t.buffer_pool,'KEEP','Y','DEFAULT','N') K, s.blocks blocks, sum(a.executions) nbr_FTS from dba_tables t, dba_segments s, v$sqlarea a, (select distinct address, object_owner owner, object_name name from v$sql_plan where operation = 'TABLE ACCESS' and options = 'FULL') p where a.address = p.address and t.owner = s.owner and t.table_name = s.segment_name and t.table_name = p.name and t.owner = p.owner and t.owner not in ('SYS','SYSTEM') having sum(a.executions) > 9 group by p.owner, p.name, t.num_rows, t.cache, t.buffer_pool, s.blocks order by sum(a.executions) desc;
Then, we extract the corresponding SQL and see if the full-table scan is warranted or due to a missing index. How can we locate small tables that are subject to full-table scans? One method is to search the SQL that is currently in the library cache. Oracle can then generate a report that lists all the full-table scans in the database at that time. The script in Listing 3 examines the execution plans from v$sql_plan and reports on the frequency of fulltable scans. The report (see Listing 4) has the following columns: • • • •
OWNER — The schema owner for the table NAME — The table name from dba_tables NUM_ROWS — The number of rows in the table as of the last compute statistics from dba_tables C (Oracle7 only) — An Oracle7 column that displays Y if the table is cached, N if it is not cached
• • •
K (Oracle8 and later only) — Displays "K" if the table is assigned to the KEEP pool BLOCKS — Number of blocks in the table as defined in dba_segments NBR_FTS — The number of full-table scans against the table (for SQL currently in the library cache).
Listing 4: Full table scans and counts: OWNER NAME NUM_ROWS C K BLOCKS NBR_FTS ---------- -------------------- ------------ - - -------- -------APPLSYS FND_CONC_RELEASE_DISJS 39 N K 2 98,864 APPLSYS FND_CONC_RELEASE_PERIODS 39 N K 2 98,864 APPLSYS FND_CONC_RELEASE_STATES 1NK 2 98,864 APPLSYS FND_CONC_PP_ACTIONS 7,021 N 1,262 52,036 APPLSYS FND_CONC_REL_CONJ_MEMBER 0NK 22 50,174 APPLSYS FND_CONC_REL_DISJ_MEMBER 39 N K 2 50,174 APPLSYS FND_FILE_TEMP 0N 22 48,611 APPLSYS FND_RUN_REQUESTS 99 N 32 48,606 INV MTL_PARAMETERS 6NK 6 21,478 APPLSYS FND_PRODUCT_GROUPS 1N 2 12,555 APPLSYS FND_CONCURRENT_QUEUES_TL 13 N K 10 12,257 AP AP_SYSTEM_PARAMETERS_ALL 1NK 6 4,521
This report gives information about two tuning areas: •
Tables & indexes for the KEEP pool—SQL speed may benefit from placing small tables (and associated indexes) that have frequent full-table scans in the KEEP pool. The report above shows full-table scans on both large and small tables. Examining this report, we can quickly identify possible candidates for the KEEP pool by selecting the tables with less than 50 blocks that have no "K" designation. Assigning a table, partition or index to the KEEP pool is easy, and objects can be added or removed at-will with alter system commands:
•
alter table CUSTOMER storage (buffer_pool KEEP);
•
Possible missing indexes — Large-table full-table scans can sometimes indicate a missing index. Oracle function-based indexes are especially useful for this purpose because any where clause can be matched with a function-based index. For example, here is a function-based index that uses the substr and to_char BIFs:
• • • • • • • •
create index fbi_book on book ( substr(book_key,1,3) || to_char(book_retail_price) );
In summary, the information contained in the v$sql_plan is a great way to perform system-wide SQL tuning.