-- 32 --

Performance Considerations

The purpose of this chapter is to address performance issues related to Personal Oracle and, more importantly, to an upsized implementation of a Personal Oracle database prototype. The material in this chapter is certainly not exhaustive. My intent is to provide some guidelines on performance improvement. Here are some general suggestions:

Keep these guidelines in mind as you read about the performance characteristics that occur in an Oracle client/server environment.

Where's the Bottleneck?

Users are the best judges of the performance of an information system. Users measure performance in terms of response time, report turnaround time, and data transfer speed. A very inefficient system may be perceived by users as providing excellent turnaround time. Conversely, a group of users might consider the performance of a very efficient information system to be inadequate. Ignore the perception of the users at your peril.

Suppose you have to analyze a system that is the object of complaints about lackluster performance. You will need to examine four elements of a client/server architecture:

Your goal in performance tuning should be to make the biggest immediate improvement with the least amount of disruption to existing software, hardware, and procedures. The following sections look at each element in some detail.

Network

The network used in a client/server architecture may be a LAN, WAN, or a combination of both. Seek the help of a networking expert to determine the saturation level of the network. Characterize the network demands of a single client. If the network appears to be the performance bottleneck, investigate the client application software to discover whether you can reduce the number of SQL requests to the server. Changing the client application software has a huge impact; this strategy should be your last resort!

If you're just beginning to design an application, you can reduce the network traffic by identifying functionality that can be implemented with stored procedures and functions in the database. (Refer to Chapter 19, "Creating and Using Stored Procedures, Functions, and Packages.")

Database

Oracle performance-tuning efforts can be classified in three ways:

Your best bet is to focus on tuning efforts that have a minimal effect on the existing application.

Tuning the System Global Area (SGA) in Windows 95

Among its other uses, the SGA functions as Oracle's cache. If you increase the number of data block buffers in the SGA, you increase the probability that a SQL statement will find the block that it needs in memory--;thereby reducing the number of disk reads it needs to locate a block. However, be sure that the SGA isn't so large that it's swapped out of memory by the operating system.

Personal Oracle7 stores its initialization parameters in C:\Orawin95\Database\Initorcl.ora. One of these parameters specifies the number of database buffers. To increase the number of database buffers, simply edit the appropriate parameter--;in this case, db_block_buffers. For example, if you want to increase the number of database buffers to 400, change

db_block_buffers =  200

to

db_block_buffers = 400



The new value for an initialization parameter will not take effect until the database is started again.

Tuning the System Global Area (SGA) in Windows 3.11

To increase the number of database buffers for Personal Oracle7 for Windows 3.11, invoke Database Manager by double-clicking its icon. Click the Configure button. On the Configure Initialization Parameters screen, the Block Buffers parameter is set to 400. (See Figure 32.1.)

Figure 32.1. Viewing the initialization parameters with Database Manager.

Change the value of Block Buffers from 400 to 450. (See Figure 32.2.) Click the OK button.

Figure 32.2. Increasing the number of block buffers.

Database Manager informs you that the change will not take effect until the next time the database is started. (See Figure 32.3.)

Figure 32.3. Database Manager displays notice.

If you wish, you can then use Database Manager to shut down and then start up the Personal Oracle database. You can leave the value of Block Buffers at 450 or set it to its original value of 400.

Tuning the MultiThreaded Server

If your Oracle server is supporting many users and the server process count is very high, consider switching from SQL*Net v1 to SQL*Net v2. By switching to SQL*Net v2, you'll be able to use Oracle's MultiThreaded Server, which enables several remote users to share the same server process. The net result is a reduction in the number of server processes and a reduction in server CPU utilization. Also, network usage is slightly more efficient in SQL*Net v2 than it is in SQL*Net v1.

If you're already using SQL*Net v2 and the MultiThreaded Server, experiment with the number of dispatchers and the number of remote connections that can share a single server process.

Reducing Data Segment Fragmentation

In Chapter 26, "Managing Space," I explained how Oracle manages the storage of tables and indexes. As you recall, when a table is created, Oracle allocates an initial extent for the table. As more rows are inserted into the table and the free space in the initial extent is exhausted, Oracle allocates another extent for the table. Index storage works in the same way. The extents may be dispersed across a datafile--;or even across datafiles. As the number of extents increases, the following problems can occur:

To determine the level of fragmentation of a table, query the USER_EXTENTS data dictionary view. For instance, if you wanted to see the number of extents allocated to My_Table, you would issue the following query:

SQL> select bytes

  2  from user_extents

  3  where

  4  segment_name = 'MY_TABLE';



    BYTES

---------

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240

    10240



16 rows selected.

As you can see, the value of PCTINCREASE for My_Table is 0 because the size of each extent is the same. To store all of My_Table's rows in its initial extent, you have the following choices:

Ideally, the contents of every table and index should be contained within its initial extent. This arrangement minimizes the number of disk reads needed to retrieve the rows to satisfy a query.

Client

To achieve acceptable performance on the client machine, the design of the client application should invoke Oracle stored procedures and functions where appropriate. Very few performance enhancements can be applied to a poorly designed application; performance must be designed into the application. Even with a good design, the client platform must have adequate RAM and disk space to support the application.

Server

The performance of the server and the Oracle database are tightly coupled. For instance, if the Oracle database processes many queries with each query returning a large number of rows, the server will be handling many disk I/O requests. To improve server performance, focus on tuning disk I/O and CPU utilization.

Tuning Disk I/O

To tune disk I/O, you must first determine if disk I/O requests are balanced across all the server's disk drives. Use operating system commands and utilities to identify the average number of I/O requests serviced by each disk drive. Your objectives should include the following:

Tuning CPU Utilization

The server's CPU utilization depends a lot on the use of the database that the server is supporting. If the database is processing many small transactions, CPU utilization might be very high. If this situation is true, here are options to ponder:

Creating Indexes to Improve Performance

A significant portion of database activity consists of SELECT statements. Accordingly, improving query performance results in better overall application performance. A query generally is processed more quickly if it uses an index to access the qualified rows. A full table scan is a query in which all of a table's rows are read to find the qualified rows. To determine whether a query performs a full table scan, you must obtain the query's execution plan. When an execution plan indicates that a full table scan is being performed, consider creating an index that the query can use.

However, be alert to the possibility of overindexing a table. Remember that Oracle automatically maintains a table's indexes whenever the contents of the table change because of an INSERT, UPDATE, or DELETE statement. Your goal should be to optimize the most frequent queries without forcing Oracle to maintain an inordinate number of indexes.

Rule-Based and Cost-Based Optimization

Oracle furnishes two optimizers: the rule-based optimizer and the cost-based optimizer. Both optimizers are concerned with maximizing the performance of queries. The rule-based optimizer looks at the structure of the SQL statement to be optimized, determines what indexes exist for the table, and constructs an execution plan; it doesn't use any information about the contents of the table to be queried or its index values. The cost-based optimizer looks at statistics regarding the table, its columns, and its indexes and then calculates an execution plan based on the lowest cost path--;the number of database blocks that must be read to retrieve the query results. Oracle stores these statistics in the data dictionary tables whenever an analysis of the tables and indexes is performed--;which I explain at the end of the chapter.

An initialization parameter named OPTIMIZER_MODE controls the choice between the rule-based and cost-based optimizer; this parameter has three possible values:



Note

Measuring the performance of a query is not as simple as it sounds. If you perform the same query twice against a large table, the second query will probably retrieve the results more quickly because the SGA already contains the database blocks that the query needs to read. Fewer disk reads are needed so the second query takes less time to complete. You can accomplish this by using the following SQL statement:

SQL> alter system flush shared_pool;


Determining the Execution Plan for a SQL Statement with EXPLAIN PLAN

If you don't analyze tables and indexes, Oracle will use the rule-based optimizer to determine the best execution plan for each query. You can employ the EXPLAIN PLAN statement to obtain the execution plan for a query. The syntax for EXPLAIN PLAN is

EXPLAIN PLAN FOR sql-statement

where sql-statement is the SQL statement for which an execution plan is to be generated.

Before you use the EXPLAIN PLAN statement, you'll need to run a script from SQL*Plus that creates the PLAN_TABLE in your Oracle account.

SQL> @c:\orawin\rdbms71\admin\utlxplan.sql



Table created.



SQL> desc plan_table

 Name                            Null?    Type

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

 STATEMENT_ID                             VARCHAR2(30)

 TIMESTAMP                                DATE

 REMARKS                                  VARCHAR2(80)

 OPERATION                                VARCHAR2(30)

 OPTIONS                                  VARCHAR2(30)

 OBJECT_NODE                              VARCHAR2(128)

 OBJECT_OWNER                             VARCHAR2(30)

 OBJECT_NAME                              VARCHAR2(30)

 OBJECT_INSTANCE                          NUMBER(38)

 OBJECT_TYPE                              VARCHAR2(30)

 OPTIMIZER                                VARCHAR2(255)

 SEARCH_COLUMNS                           NUMBER(38)

 ID                                       NUMBER(38)

 PARENT_ID                                NUMBER(38)

 POSITION                                 NUMBER(38)

 OTHER                                    LONG

After the PLAN_TABLE has been created, you can begin using the EXPLAIN PLAN statement. Whenever the EXPLAIN PLAN statement is executed, Oracle inserts rows into the PLAN_TABLE; as a result, you need to delete the contents of PLAN_TABLE before each use of EXPLAIN PLAN. For example, suppose you create a table that records the day of the year and the maximum temperature in degrees Fahrenheit for each day. As a developer, you want to determine the efficiency of a query's execution plan.

SQL> delete from plan_table;



0 rows deleted.



SQL> explain plan for

  2  select day_number, temp_deg_f

  3  from day_temp

  4  where day_number = 100;



Explained.



SQL> select operation, options, object_name, id, parent_id, position

  2  from plan_table

  3  order by id;





OPERATION           OPTIONS         OBJECT_NAME         ID PARENT_ID  POSITION

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

SELECT STATEMENT                                         0

TABLE ACCESS        FULL            DAY_TEMP             1         0         1

EXPLAIN_PLAN is looking for the word FULL that appears in the OPTIONS column in conjunction with the TABLE ACCESS operation. FULL signifies that the query performs a full table scan to retrieve the data. If a query involving Day_Number is a fairly common operation, you should consider adding an index on the Day_Number column. Here's how to create the index and rerun EXPLAIN_PLAN.

SQL> create index day_temp_day_number_ck

  2  on day_temp (day_number);



Index created.



SQL> delete from plan_table;



2 rows deleted.



SQL> explain plan for

  2  select day_number, temp_deg_f

  3  from day_temp

  4  where day_number = 100;



Explained.



SQL> select operation, options, object_name, id, parent_id, position

  2  from plan_table

  3  order by id;



OPERATION          OPTIONS     OBJECT_NAME              ID PARENT_ID  POSITION

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

SELECT STATEMENT                                         0

TABLE ACCESS       BY ROWID    DAY_TEMP                  1         0         1

INDEX              RANGE SCAN  DAY_TEMP_DAY_NUMBER_CK    2         1         1

As you can see, by creating the index, you've changed the optimizer's execution plan for the query. Instead of performing a full table scan, the optimizer will perform an index range scan, which is almost always a more efficient operation (although not for a table with a small number of rows). Even though the EXPLAIN PLAN statement supplies useful information about the methods used by the optimizer, it doesn't provide any hard performance numbers. To retrieve performance data, you'll want to use the TKPROF utility.

The TKPROF Utility in Windows 95

In the directory c:\orawin95\bin, you'll find an executable file named tkprof72.exe. This program must be executed from an MS-DOS window. TKPROF processes Oracle trace files to produce a text file that describes the SQL activity that occurred during a particular Oracle session. A trace file is extremely useful for performance analysis and tuning because

By default, Personal Oracle7 won't produce trace files. To produce a trace file with performance data, you'll need to set the TIMED_STATISTICS initialization parameter to TRUE and restart the Oracle instance. Include the following line in C:\ORAWIN95\DATABASE\INITORCL.ORA:

timed_statistics=TRUE

You must stop the database and restart it for this parameter to take effect.

If you wanted to analyze the performance of a group of SQL statements processed by SQL*Plus, you can enable a trace file for the Oracle session in the following way:

SQL> alter session set sql_trace true;



Session altered.

You may then process any SQL statements that you were interested in analyzing. Oracle stores trace files in c:\orawin95\rdbms72\trace. You should end the Oracle session before you try to analyze the trace file.

Next, open an MS-DOS window. If you type tkprof72 at the prompt, the program will display a list of its command-line arguments. (See Figure 32.4.)

Figure 32.4. TKPROF displays command-line arguments.

At a minimum, you should specify the name of the trace file, the name of the output file, and the Oracle user and password whose PLAN_TABLE will be used to determine the statement execution plan. (See Figure 32.5.)

Figure 32.5. Example of running TKPROF.

By default, the output file will have an extension of .prf. You may then use an editor to display and print the contents of the output file. The summary portion of a sample TKPROF output file follows.

OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS



call     count       cpu    elapsed       disk      query    current        rows

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

Parse        7      0.00       1.59          2          0          4           0

Execute      8      0.00       0.49          2         13         38          10

Fetch        5      0.00       0.04          2          3          9          35

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

total       20      0.00       2.12          6         16         51          45



Misses in library cache during parse: 4

Misses in library cache during execute: 1



    8  user  SQL statements in session.

   26  internal SQL statements in session.

   34  SQL statements in session.

    4  statements EXPLAINed in this session.

********************************************************************************

Trace file: c:\orawin95\rdbms72\trace\ora63171.trc

Trace file compatibilty: 7.02.01

Sort options: execpu

       1  session in tracefile.

       8  user  SQL statements in trace file.

      26  internal SQL statements in trace file.

      34  SQL statements in trace file.

      18  unique SQL statements in trace file.

       4  SQL statements EXPLAINed using schema:

           FRAYED_WIRES.prof$plan_table

             Default table was used.

             Table was created.

             Table was dropped.

     382  lines in trace file.

As you can see, the trace file provides a wealth of statistical information about the SQL statements that were processed during the Oracle session. This tool is particularly powerful in helping you solve the so-called 80-20 problem: identifying and improving the 20 percent of the SQL statements that represent 80 percent of the work.

Analyzing Tables and Indexes

Oracle collects table, column, and index statistics by executing the ANALYZE TABLE and ANALYZE INDEX statements. The syntax for the ANALYZE_TABLE statement is

ANALYZE TABLE table-name COMPUTE STATISTICS

where table-name is the name of the table to be analyzed.

Here's an example of the results produced by the ANALYZE TABLE statement. It creates a TEST_TABLE table and puts 1,000 rows into it.

SQL> create table Test_Table (

  2  Record_Number integer,

  3  Current_Date  date);



Table created.



SQL>

SQL> declare

  2

  2  max_records constant int := 1000;

  3  i           int := 1;

  4

  4  begin

  5

  5  for i in 1..max_records loop

  6

  6    insert into Test_Table

  7      (record_number, current_date)

  8    values

  9      (i, SYSDATE);

 10

 10  end loop;

 11

 11  end;

 12  /



PL/SQL procedure successfully completed.

If you inspect the row in the data dictionary view USER_TABLES, which contains information about TEST_TABLE, you'll see that the relevant columns are either null or zero.

SQL> select num_rows, blocks, empty_blocks, avg_space, chain_cnt, avg_row_len

  2  from user_tables

  3  where

  4  table_name = 'TEST_TABLE';



 NUM_ROWS    BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN

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

                               0         0         0           0

However, once you analyze the table, the statistics related to TEST_TABLE are populated.

SQL> analyze table test_table compute statistics;



Table analyzed.



SQL> select num_rows, blocks, empty_blocks, avg_space, chain_cnt, avg_row_len

  2  from user_tables

  3  where

  4  table_name = 'TEST_TABLE';



 NUM_ROWS    BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN

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

     1000        14            5       196         0          15

Besides filling in values in USER_TABLES, the ANALYZE TABLE statement also updates related information in USER_TAB_COLUMNS.

SQL> select column_name, num_distinct, low_value, high_value, density

  2  from user_tab_columns

  3  where

  4  table_name = 'TEST_TABLE'

  5  order by column_name;



COLUMN_NAME      NUM_DISTINCT LOW_VALUE          HIGH_VALUE          DENSITY

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

CURRENT_DATE                5 77C3060C101D32     77C3060C101D36      .02631579

RECORD_NUMBER            1000 C102               C20B                .001

If a table contains many rows, you may want to consider an alternative form of the statement, which will sample a percentage of the rows:

ANALYZE TABLE table-name ESTIMATE STATISTICS SAMPLE percentage PERCENT

where table-name is the table to be analyzed and percentage is the percentage of the table's rows to be analyzed.

You can also remove the statistics for the pertinent table with the following statement:

ANALYZE TABLE table-name DELETE STATISTICS

where table-name is the table whose statistics are to be eliminated.

Using the previous table as an example, you can analyze TEST_TABLE by sampling a portion of the rows.

SQL> analyze table Test_Table delete statistics;



Table analyzed.



SQL> analyze table Test_Table estimate statistics sample 10 percent;



Table analyzed.



SQL> select num_rows, blocks, empty_blocks, avg_space, chain_cnt, avg_row_len

  2  from user_tables

  3  where

  4  table_name = 'TEST_TABLE';



 NUM_ROWS    BLOCKS EMPTY_BLOCKS AVG_SPACE CHAIN_CNT AVG_ROW_LEN

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

     1000        14            5       196         0          15

For the cost-based optimizer to work well, the indexes that belong to the table being queried must also be analyzed. The syntax for the ANALYZE INDEX statement is almost identical to that of the ANALYZE TABLE statement.

ANALYZE INDEX index-name COMPUTE STATISTICS

index-name is the name of an index on the queried table.

To illustrate how the ANALYZE INDEX statement updates index statistics in the data dictionary, here is an example that uses the unique index PK_MANUFACTURER, which enforces the primary key for the Manufacturer table. To obtain information about table indexes, you can query the data dictionary view USER_INDEXES.

SQL> select index_name, blevel, leaf_blocks, distinct_keys, avg_leaf_blocks_per_key,

  2         avg_data_blocks_per_key

  3  from user_indexes

  4  where

  5  index_name = 'PK_MANUFACTURER';



INDEX_NAME              BLEVEL LEAF_BLOCKS DISTINCT_KEYS

                            AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY

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

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

PK_MANUFACTURER

Now you can analyze the index and then query the data dictionary view USER_INDEXES again.

SQL> analyze index pk_manufacturer compute statistics;



Index analyzed.



SQL> select index_name, blevel, leaf_blocks, distinct_keys, avg_leaf_blocks_per_key,

  2         avg_data_blocks_per_key

  3  from user_indexes

  4  where

  5  index_name = 'PK_MANUFACTURER';



INDEX_NAME              BLEVEL LEAF_BLOCKS DISTINCT_KEYS

                           AVG_LEAF_BLOCKS_PER_KEY AVG_DATA_BLOCKS_PER_KEY

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

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

PK_MANUFACTURER              0           1            10

                                                 1                       1

As you can see, executing the ANALYZE INDEX statement updates the index statistics, such as LEAF_BLOCKS and DISTINCT_KEYS.

Summary

This chapter discusses the following aspects of Oracle client/server performance tuning: