Finding the Performance Bottlenecks in Your Application

Ian Jones and Roger Schrag

Database Specialists, Inc.

Abstract

When a complex application runs too slowly, it can be difficult to choose where to focus tuning efforts. Running EXPLAIN PLAN on every SQL statement or TKPROF on every session is usually not practical. Searching for the slow SQL in a large application can feel like a wild goose chase sometimes. By querying v$ views and using built-in PL/SQL packages in the Oracle Server, however, you can usually zero in on the module or the exact SQL statement that is performing poorly. Once you know where the problem is, you can concentrate your tuning efforts there and turn things around. This presentation demonstrates how these techniques were used to locate performance problems in half a dozen real applications. The focus of this presentation is how to find the slow SQL in an application—as opposed to how to tune it.

Introduction

To a database administrator it often seems as if no application ever runs fast enough to satisfy the user community. The perception that applications run too slowly can be caused by many different things—unreasonable expectations, insufficient hardware, poorly tuned application code, a poorly tuned database, or an architecture that doesn’t scale well. Regardless of the true cause of the perceived performance problem, it’s often left up to the DBA to make applications run faster and provide better response time to end users.

While it may not be possible to rein in wild user expectations or eliminate contention on a heavily overloaded server, DBAs can certainly ensure the application code and database are performing optimally. In fact, in many situations tuning the application can offer the best "bang for the buck" in speeding up a slow system. Why is that? Well, literally one bad SQL statement can bring an entire system to its knees. One bad SQL statement in a stored procedure could turn one disk I/O into tens of thousands of I/Os. And if that code is called frequently, the I/O volume could impact all users of the system.

Unfortunately, most DBAs are not intimately familiar with every line of code that makes up the applications they must support. So, finding the performance bottleneck in a sophisticated application can seem like searching for a needle in a haystack. Luckily, there are tactics available to quickly hone in on the most resource-intensive modules of an application. Once performance bottlenecks can be found, tuning efforts can be focused where they are most needed.

This presentation illustrates techniques you may use in order to find the performance bottlenecks in your system. These techniques are demonstrated by way of real world situations encountered by the authors. The client-specific details have been changed to protect the innocent, but otherwise this is real stuff. The tools used here are all either native to Oracle7 and Oracle8, or are available free to download from the Internet.

Observe the V$ Views

Every Oracle database has a set of special objects owned by the SYS user with names that begin "v_$". These objects are commonly referred to as "v$ fixed tables", "dynamic performance views", or more simply "v$ views". Public synonyms make these objects accessible to users with the SELECT ANY TABLE privilege and also transform their names by dropping the first underscore. The v$ views may be queried just like read-only views, but in fact the query results come from data structures within the SGA instead of blocks in a data file.

The v$ views give real time information about the state of the Oracle instance—what statements are currently being processed, which sessions are waiting for locks, and so on. In this section we will look at the v$ views that are the most helpful in finding performance bottlenecks. Check the Oracle Server Reference manual for full details of all v$ views.

v$session

v$session contains one row for each session connected to the instance. Figure 1 shows some of the useful columns in v$session. You’ll find one row in v$session for each of the Oracle daemons (such as PMON and LGWR) as well as one row for each user connection.

Column

Description

sid

Session identifier unique among all current sessions

serial#

Session identifier unique among all sessions that have existed since the instance was started

sql_address

The address of the statement currently executing in this session

status

The status of the session—typically ACTIVE, INACTIVE, or KILLED

username

The Oracle username the session has connected as

osuser

The user who initiated the application that connected to the instance, as provided by the operating system on the machine running the client application

machine

The name of the machine running the client application that has connected to the instance

program

The name of the client application that connected to the instance to create this session

module

Voluntary information provided by the client application to describe itself

action

Voluntary information provided by the client application to describe the current activity

Figure 1: Useful columns in v$session

Oracle assigns each session a session identifier or sid that is unique among all sessions currently in existence. Each session is also assigned a serial number that is unique among all sessions since the instance was started. The sid is valuable for joining to other v$ views and the serial number will prove handy in a later discussion about SQL Trace. The sql_address is a pointer to the statement currently being executed in the session, and will be discussed shortly.

The status column in v$session indicates which sessions are busy and which are idle. A status of ACTIVE indicates that Oracle is currently processing a request for the session, while a status of INACTIVE means that Oracle is waiting for a request from the client application. An ACTIVE status probably means that Oracle is consuming system resources on behalf of the session, but not necessarily. For example, if an application calls the dbms_pipe package in order to listen for a message on a pipe, the session will have a status of ACTIVE. However, on most platforms, the Oracle process will actually go to sleep until it is woken up by a message or timeout event.

If you believe your Oracle database is consuming lots of system resources, you can look at v$session to determine which sessions are currently active. By examining the username, osuser, machine, program, module, and action columns of v$session, you may be able to isolate the resource usage to an individual user or a particular application module.

v$sqlarea

v$sqlarea contains one row for each statement currently in the shared pool. A "statement" is typically a SQL command or a PL/SQL block. Figure 2 shows some of the useful columns in v$sqlarea. You’ll find one row in v$sqlarea for each statement currently executing, as well as rows for recently executed statements that have not yet aged out of the shared pool. Note that if multiple users execute the exact same statement, or if one user executes the same statement multiple times, only one copy of the statement will be present in the shared pool and hence only one row will appear in v$sqlarea.

Column

Description

address

The address of the statement within the shared pool—a unique identifier

sql_text

The text of the statement, possibly truncated if the statement is not short

executions

The number of times this statement has been executed

parse_calls

The number of times this statement has been parsed

rows_processed

The total number of rows processed (selected, inserted, etc.) by all executions of this statement

buffer_gets

The total number of blocks read by all executions of this statement (logical reads)

disk_reads

The total number of blocks read by all executions of this statement that resulted in physical reads from disk (physical reads)

optimizer_mode

The optimizer mode (rule, first_rows, etc.) used to create an execution plan for this statement

Figure 2: Useful columns in v$sqlarea

For each statement, v$sqlarea shows the memory address at which its entry is located in the shared pool. This address can be used to uniquely identify each row in v$sqlarea. For each row, sql_text shows the first portion of the actual statement. If you wish to see the entire statement, check out v$sqltext.

By joining v$session and v$sqlarea, you can see the actual statement being executed by an active session. This can be useful for isolating the slow SQL in an application. This technique is most effective when the bulk of the code in an application is efficient, but one or two inefficient statements gum up the works.

Example: #1: A transportation company began communicating with its customers via EDI. When they received an EDI transmission from a customer, they would use SQL*Loader to load the data into a temporary table in their Oracle database and then run a PL/SQL stored procedure to parse the data, validate it, and update their application tables. As the volume of EDI data increased, the stored procedure could no longer keep up. Processing just one file sometimes took as long as 24 hours.

By monitoring v$session and v$sqlarea while the stored procedure ran, we discovered that over 99% of the processing time was spent executing one SELECT statement that queried an item number translation table. By replacing a SUBSTR function in the WHERE clause with LIKE, we were able to make use of an available index and bring average run time down from 20 hours to five minutes. Our query of v$session and v$sqlarea looked something like this:

SELECT B.sql_text

FROM v$session A, v$sqlarea B

WHERE A.osuser = 'EDI_LOAD'

AND B.address = A.sql_address;

In addition to showing the memory address and text of statements in the shared pool, v$sqlarea also shows key statistics such as how many times a statement has been executed, how many times it has been parsed, how many rows it has processed, how many data blocks already in the SGA have been read in the processing of the statement, and how many physical disk reads have been caused. You can use these statistics to answer many performance-related questions about your system. Some examples are:

Which statements are performing the most logical reads per execution?

Which statements are performing the most disk reads per execution?

How efficient are our most frequently executed statements?

How frequently do our statements need to be parsed?

By monitoring v$sqlarea you can determine where to concentrate your tuning. For example, shaving a fraction of a second off the run time of a statement that executes many thousands of times (inside a PL/SQL loop, for example) can improve response times dramatically. On the other hand, knocking 1000 I/Os off a statement that executes only once a day may not bring much satisfaction to the user community.

Example #2: A Midwest financial institution engaged a large consulting firm to build a PowerBuilder application to assist the loan processing department. Initially one third of the 300 potential users were set up with the new application. Although response times were acceptable, the disk drives on the server were working at capacity. This suggested that performance problems would arise if more users were brought online with the new application.

By monitoring v$sqlarea during a period of usual activity with dozens of concurrent users, we discovered that the overwhelming majority of disk reads were caused by only six SQL statements. All six statements were queries that implemented case-insensitive searches. Applying the UPPER function to an indexed column defeats the index, and this caused many full table scans of large tables. By implementing a different strategy for case-insensitive searching, we were able to cut disk I/O enough that the additional 200 users could be brought online without widespread performance degradation. Our query of v$sqlarea looked something like this:

SELECT sql_text, disk_reads, executions,

disk_reads / DECODE (executions, 0, 1, executions) reads_per_exec

FROM v$sqlarea

ORDER BY reads_per_exec;

v$statname, v$sysstat, and v$sesstat

Oracle maintains over 100 statistics in real time on an instance-wide and per-session basis. These are cumulative statistics since the instance was started or since the session began. These statistics cover many interesting areas such as CPU utilization, physical and logical I/O, network traffic, sorts, and full table scans.

Each statistic maintained by Oracle is identified by a unique number, and v$statname shows the names and unique identifiers for all statistics. Each row in v$sysstat contains the name and identifier for one statistic, along with the value of that statistic for the instance as a whole. Each row in v$sesstat, meanwhile, contains a session identifier and a statistic identifier, along with the value of that statistic for the session.

Figures 3, 4, and 5 show the useful columns that make up these three v$ views and Figure 6 lists some of the interesting statistics available. Note that the statistic names and unique identifiers can vary from one release of Oracle to the next. Also note that statistics pertaining to CPU or elapsed time will have a value of zero unless timed statistics have been enabled. Enabling timed statistics will be discussed in the next section.

Column

Description

statistic#

The unique identifier for the statistic

name

The name of the statistic

Figure 3: Useful columns in v$statname

Column

Description

statistic#

The unique identifier for the statistic

name

The name of the statistic

value

The value of the statistic for the instance as a whole

Figure 4: Useful columns in v$sysstat

Column

Description

sid

The unique identifier for the session—join to v$session

statistic#

The unique identifier for the statistic—join to v$statname

value

The value of the statistic for the session

Figure 5: Useful columns in v$sesstat

Number

Name

12

CPU used by this session

39

Physical reads

40

physical writes

119

table scans (long tables)

123

table scan rows gotten

129

parse time cpu

131

parse count

133

bytes sent via SQL*Net to client

134

bytes received via SQL*Net from client

135

SQL*Net roundtrips to/from client

140

sorts (disk)

141

sorts (rows)

Figure 6: Some of the statistics maintained in v$sysstat and v$sesstat

By monitoring v$sysstat you can assess the activity in real time on the database as a whole. By monitoring v$sesstat in conjunction with v$session and v$statname you can analyze the activity of an individual session. Because these statistics are cumulative since the instance was started, you can query a statistic, perform an exercise, query the statistic again, and compute the delta to determine what activity occurred during the exercise.

These v$ views give you a lot of capability to peer into a black box and figure out what is happening. Without reviewing source code and application design, you can determine how an application is utilizing system resources. This can allow you to detect a variety of performance problems and identify the characteristics of individual bottlenecks.

Example #3: A developer could not understand why sometimes his internal rate of return calculator function compute_irr would appear to hang. For some input data the function returned a correct figure very quickly, but for other input data the function appeared to hang. The developer felt the problem was a locking issue that should be researched by a DBA.

First we monitored v$session in order to determine the correct sid for the test session, and then we monitored v$sesstat to learn what the session was doing while the function appeared to hang. We found that the session performed no full table scans, no sorts, not even any logical I/O. What the session did consume was CPU time, and a lot of it. One CPU on the server appeared to be pegged by compute_irr.

Armed with this information we reviewed the source code of the compute_irr function. We skipped past all of the SQL, since this would involve I/Os, and noticed a loop. Apparently, the function used an algorithm of successive approximations to compute internal rate of return. Due to the precision declared for the local variables, it was possible for an infinite loop to occur because of rounding behavior. Declaring variables with greater precision allowed the loop to always reach a conclusion and the "hanging" function no longer hung. Our query against v$sesstat looked somewhat like this:

SELECT A.name, B.value

FROM v$statname A, v$sesstat B

WHERE B.statistic# IN (11, 12, 37, 38, 39, 119, 120, 123, 139, 140, 141)

AND B.sid = 26

AND A.statistic# = B.statistic#

Example #4: A software development company built a vertical application that worked well in a demo database with a small set of sample data but quickly became I/O bound when data volumes were increased. Unix monitoring tools showed that large amounts of disk I/O were taking place even when no users were signed on to the application.

By monitoring v$sesstat we discovered that one session was responsible for virtually all of the I/O on the system. This session was also performing one full table scan every 15 seconds. Joining to v$session we discovered that this session was opened by a daemon program written in Pro*C. It turned out that the daemon was checking a transaction table four times per minute in search of transactions flagged for processing by the daemon. Because the flag was not an indexed column, a full table scan was necessary each time. Adding an index to the transaction table allowed the daemon to quickly find the flagged rows without a full table scan.

Deploy SQL Trace and Enable Timed Statistics

The Oracle Server includes a tracing facility for analyzing SQL processing at the statement level. Oracle also has the capability of keeping detailed time statistics while tracing SQL and while maintaining the v$ views. When timed statistics are disabled, time data in SQL Trace files and v$ views show as zero.

SQL Trace and timed statistics can be invaluable for tuning an application, especially when used in conjunction with TKPROF. For background information on SQL Trace and timed statistics, see the Oracle Server Tuning manual. While turning tracing on and off is very easy, the real trick is tracing only what needs to be traced so that you have a manageable amount of trace information to examine.

Both SQL Trace and timed statistics may be turned on at the session level or the instance level. Enabling SQL Trace incurs a significant amount of processing overhead because verbose log files are generated. Also, it is easier to make sense of trace files when you are not using the multi-threaded server—if multi-threaded server is enabled on your database you should connect to the database with a dedicated server before enabling SQL Trace.

Enabling Tracing in Your Application

If you own the source code to the application you wish to trace, it might be feasible for you to modify the code directly to enable SQL Trace. This can be advantageous because you can enable SQL Trace at precisely the point in your application where you have a doubt. This can greatly narrow the scope of your tracing exercise, allowing you to focus efforts on the bottleneck itself.

Depending on the tools used to build your application, you may be able to enable SQL Trace with the simple command:

ALTER SESSION SET SQL_TRACE = TRUE;

If you need to enable SQL Trace from within a PL/SQL block, you can call:

dbms_session.set_sql_trace (TRUE);

SQL Trace output is most valuable when timed statistics are enabled. You can instantly enable timed statistics for all sessions by logging on to the database as a DBA and executing the command:

ALTER SYSTEM SET TIMED_STATISTICS = TRUE;

If you are concerned about the overhead this causes, you may instead modify the application code to enable timed statistics for the current session instead of the entire instance. You can do this with the command:

ALTER SESSION SET TIMED_STATISTICS = TRUE;

To enable timed statistics from within a PL/SQL block, you must use the dbms_sql package to build and execute a dynamic SQL statement:

DECLARE

c INTEGER; i INTEGER;

BEGIN

c := dbms_sql.open_cursor;

dbms_sql.parse (c, 'ALTER SESSION SET TIMED_STATISTICS = TRUE',

dbms_sql.native);

i := dbms_sql.execute (c);

dbms_sql.close_cursor (c);

END;

Example #5: A software development company standardized on a certain third party reporting tool for the generation of all reports within the application. The reporting tool was one designed to support all major database vendors and not just Oracle. Unfortunately, most of the reports ran very slowly and the development team did not know where to begin their tuning efforts.

We compiled a special version of one report that included a call to dbms_session at the beginning so that SQL Trace would be enabled during the report run. We also enabled timed statistics for all sessions with the ALTER SYSTEM command. We ran the special report and ran TKPROF on the resulting trace file. By looking at the TKPROF output we learned that the third party reporting tool did not use bind variables. The report engine apparently replaced variable names with constant values before submitting SQL queries to Oracle for processing. This caused each SQL statement to be unique, forcing Oracle to parse each statement. Many of the application’s reports were very brief reports intended to be run hundreds or even thousands of times each day. The parsing was taking longer than the actual execution of the queries. The solution was to work with the reporting tool vendor in order to enhance the reporting tool to support bind variables.

Tracing a "Black Box" Application

Often you do not have the luxury of being able to modify application source code in order to embed calls to enable SQL Trace. Purchased applications rarely include source code, and often it isn’t even feasible to embed calls within code developed in-house due to procedural or political reasons.

In these situations you can log on to the database in SQL*Plus as a DBA and fire off a PL/SQL call that tells Oracle to enable SQL Trace within the session you wish to trace. To do this, you query v$session to determine the sid and serial# of the session you wish to trace. Then you call:

sys.dbms_system.set_sql_trace_in_session (<sid>, <serial#>, TRUE);

This will enable SQL Trace in the specified session. You can change the last argument to FALSE to disable tracing once the desired event has been traced. This method allows you to trace virtually anything, but of course you don’t get precise control over when tracing begins and ends.

Example #6: Still another software development firm built a complex PowerBuilder application that performed well on an in-house test database loaded with large volumes of data. However, a flagship customer complained that queries were unacceptably slow. The customer would not allow us to install a test version of the application on their server, so all of our evaluation had to be done using the officially released software.

We worked closely with a power user. First we had the user sign on to the application and pause so that we could determine the sid and serial# of her session. Then we enabled tracing for her session just when she initiated the slow query. After the query completed, we immediately disabled tracing for her session and ran TKPROF on the trace file. What we found actually became the subject matter of an entire article in the April, 1998 issue of SELECT Magazine. It had to do with inconsistencies between the different versions of Oracle with respect to how the IN operator is processed.

Consider Using GUI Tools

There are many GUI tools available that let you explore the techniques described so far without having to type tedious and repetitive SQL statements at a SQL*Plus or Server Manager prompt. Many DBAs prefer the sense of control and the scripting capabilities that come with the command line. But as GUI tools mature and become more sophisticated, point and click options become more appealing.

We’ll take a look at Enterprise Manager, the GUI offering from Oracle, as well as a few of the better freeware tools floating around the Internet.

Enterprise Manager

Enterprise Manager is Oracle’s system management tool. It provides a framework and a set of packages for monitoring and managing multiple databases. There are a standard set of packages provided, including Instance Manager, Schema Manager, Storage Manager, and Backup Manager. There is also an optional performance pack, which includes packages such as Performance Manager, TopSessions, Expert, and Trace. The most useful packages for this discussion are Performance Manager, TopSessions and Expert. We’ll briefly look at each.

Performance Manager provides a set of predefined charts that display database statistics broken down into seven categories: Contention, Database Instance, I/O, Load, Memory, Overview, and Parallel Server. These predefined charts are good for quickly obtaining a high level view of the various database parameters—such as overall buffer hit ratio or proportion of sorts completed in memory. One way of obtaining a more granular view is to drill down from a particular chart. This will launch the corresponding TopSessions display. One strength of the Performance Manager is that user-defined charts can be easily created. For example, the chart shown in Figure 7 is a useful custom one that displays the statements with high ratios of disk reads per execution or buffer gets per execution from v$sqlarea.

Figure 7

Figure 7: Custom performance chart showing high resource statements

Charts can be set up to refresh at predefined intervals and the data generated can be recorded for later replay or analysis. Recording chart data periodically throughout the day yields a good record of how the database load and performance varies with the daily workload pattern.

TopSessions provides a session-based view of the performance data which complements the high level information obtained from the predefined charts in Performance Manager. As the name suggests, this package displays a user configurable number (which defaults to 10) of sessions ordered by the statistic of interest. The statistics are grouped together and filtered in different ways, such as Predefined, User, and Cache. Each session can then be investigated more closely.

Session information and statistics are available, taken from v$session and v$sesstat. Cursors can also be viewed and EXPLAIN PLANs generated for them. Further, locks can be examined. Unfortunately, there is no way to customize the filter conditions for the sessions or record the data. Figure 8 illustrates the TopSessions display of open cursors in a session.

Figure 8

Figure 8: TopSessions examination of open cursors in a session

The Expert tool is the most comprehensive of the three performance pack tools we are discussing. It is difficult to do justice to its depth of functionality in the space available here, so we will focus on an overview and a few examples. Expert makes recommendations based on data sets that can be supplied through files, user input, or obtained directly from a database. There are five types of input data, called classes within Expert. These are Database, Instance, Schema, Environment and Workload.

Database and Instance data include the various database parameters, Oracle version information and the contents of the relevant v$ views. Schema data includes all the schema objects, their sizes, and detailed information about data distributions (such as within an index). Expert can either obtain this by the ANALYZE command or via SQL statements. The environment settings record memory and CPU configuration and disk information, while the workload data includes the various SQL statements and their relative importance and frequency.

From this data—ideally collected several times in order to wash out statistical oddities—Expert can suggest all kinds of changes. For example, it can suggest changes to parameter settings; highlight missing, duplicate or non-selective indexes; suggest changes to index type; provide alerts to unnecessary or performance-impacting full table scans; and point out OFA non-compliance. Expert can also generate scripts based on this analysis to carry out its suggestions. A very nice feature of this tool is that it provides explanations for its recommendations that allow a sufficiently skilled DBA to decide if the basis of the suggestion is good. Figure 9 illustrates a situation where Expert suggests creating a new index on the employees table.

In summary, Enterprise Manager provides good performance monitoring tools with varying levels of functionality. If you have access to the performance pack, it is definitely worth checking out.

Figure 9

Figure 9: A recommendation by Expert to add a new index

Freeware Alternatives

There are many GUI tools available for download from the Internet. GUI tools for performance monitoring should be readily available, inexpensive or free, easily installed, and have a small footprint. Although there isn’t much out there with the depth of Enterprise Manager, there are several tools worth looking at. We will consider four such freeware tools—the first three are described in more detail in Guy Harrison’s book High Performance SQL Tuning, and can be downloaded from http://www.oraworld.com. The fourth, "Toad", can be downloaded or evaluated via http://www.toadsoft.com. A commercial version of Toad is also available.

As we have discussed, analysis of v$sqlarea is helpful for identifying bottlenecks. One tool that makes this easy is "shrpool". Once connected to an instance shrpool provides a window that can be used to filter statements. By default it will retrieve the 10 worst statements based on buffer gets per execution. From there it takes a few mouse clicks to display the statistics and to generate the execution plan for a particular statement. An analysis of all the statements in the shared pool can also be produced. Other helpful views can be obtained when looking to identify bottlenecks, such as a check that the most frequently executed statements are as efficient as possible. Certain statements fail to EXPLAIN properly—namely ones where the full text is not available in v$sqlarea. These need to be dealt with outside of the tool.

Having identified which statements are problematic, the next step is to take a closer look at them. Although shrpool provides execution plans, it cannot be used to tune statements. To keep this presentation brief we are not focusing on tuning here, but will mention a simple tool called "Xplain" since it is so useful. Statements can be cut from shrpool and pasted directly into Xplain, which will confirm their execution plan and also generate statistics for the statement. For example, Xplain calculates the differences in the cumulative session values, allowing you to see statistics for individual statement executions. This is a good environment in which to evaluate various plans quickly to confirm the bottleneck and generate a more efficient alternative.

"Monet" is another useful tool. It is similar in concept to the Enterprise Manager Performance Monitor and TopSessions combination. It provides various instance-wide parameters, but also allows drill downs into the session statistics for each session.

Toad is a generally useful tool that provides an object browser, SQL worksheet, and PL/SQL development environment. Toad offers a Tuning menu that provides "server" and "trace/optimization" options. The server option leads to various tabs showing instance information such as locks, waits, latches, and session information (including session statistics). The "analysis" tab provides suggestions based on the various hit ratios (buffer cache, library cache, etc.). The trace/optimization option provides an analysis of contents of the shared pool, including options for sorting the display and filtering based on username or statement text.

In Summary

Users seem to have an insatiable appetite for speed. No matter how fast an application runs, it never seems to be fast enough. Today’s applications are too complex for developers and DBAs to apply tuning tactics uniformly to all code modules. By exploiting the techniques we’ve discussed here, you can find the performance bottlenecks in your applications and focus your tuning efforts where they will give you the best return for your time.

The authors of this presentation have collectively over 20 years of experience with Oracle technology, both as application developers and DBAs. You may contact Ian Jones at ijones@dbspecialists.com and Roger Schrag at rschrag@dbspecialists.com. Ian and Roger both work for Database Specialists, a small consulting group specializing in business solutions based on Oracle technology. You may visit the company’s website at http://www.dbspecialists.com.