You are on page 1of 873

1

Objectives
This lesson provides an overview of the Oracle Database server architecture. You learn about
physical and logical structures and about the various components.

Oracle Database 11g: SQL Tuning Workshop 1 - 2

Oracle Database Server Architecture: Overview


An Oracle Database server consists of an Oracle Database and one or more Oracle Database
instances. An instance consists of memory structures and background processes. Every time an
instance is started, a shared memory area called the System Global Area (SGA) is allocated and the
background processes are started.
The SGA contains data and control information for one Oracle Database instance.
The background processes consolidate functions that would otherwise be handled by multiple Oracle
Database server programs running for each user process. They asynchronously perform input/output
(I/O) and monitor other Oracle Database processes to provide increased parallelism for better
performance and reliability.
The database consists of physical files and logical structures discussed later in this lesson. Because
the physical and logical structures are separate, the physical storage of data can be managed without
affecting access to the logical storage structures.
Note: Oracle Real Application Clusters (Oracle RAC) comprises two or more Oracle Database
instances running on multiple clustered computers that communicate with each other by means of an
interconnect and access the same Oracle Database.

Oracle Database 11g: SQL Tuning Workshop 1 - 3

Connecting to the Database Instance


When users connect to an Oracle Database server, they are connected to an Oracle Database instance.
The database instance services those users by allocating other memory areas in addition to the SGA,
and starting other processes in addition to the Oracle Database background processes:
User processes sometimes called client or foreground processes are created to run the software
code of an application program. Most environments have separate machines for the client
processes. A user process also manages communication with a corresponding server process
through a program interface.
Oracle Database server creates server processes to handle requests from connected user
processes. A server process communicates with the user process and interacts with the instance
and the database to carry out requests from the associated user process.
An Oracle Database instance can be configured to vary the number of user processes for each server
process. In a dedicated server configuration, a server process handles requests for a single user
process.
A shared server configuration enables many user processes to share a small number of shared server
processes, minimizing the number of server processes and maximizing the use of available system
resources. One or more dispatcher processes are then used to queue user process requests in the SGA
and dequeue shared server responses.

Oracle Database 11g: SQL Tuning Workshop 1 - 4

Connecting to the Database Instance (continued)


The Oracle Database server runs a listener that is responsible for handling network connections. The
application connects to the listener that creates a dedicated server process or handles the connection
to a dispatcher.
Connections and sessions are closely related to user processes, but are very different in meaning:
A connection is a communication pathway between a user process and an Oracle Database
instance. A communication pathway is established by using available interprocess
communication mechanisms (on a computer that runs both the user process and Oracle
Database) or network software (when different computers run the database application and
Oracle Database, and communicate through a network).
A session represents the state of a current database user login to the database instance. For
example, when a user starts SQL*Plus, the user must provide a valid database username and
password, and then a session is established for that user. A session lasts from the time a user
connects until the user disconnects or exits the database application.
Note: Multiple sessions can be created and exist concurrently for a single Oracle database user using
the same username. For example, a user with the username/password of HR/HR can connect to the
same Oracle Database instance several times.

Oracle Database 11g: SQL Tuning Workshop 1 - 5

Oracle Database Memory Structures: Overview


Oracle Database creates and uses memory structures for various purposes. For example, memory
stores the program code that is run, data that is shared among users, and private data areas for each
connected user. Two basic memory structures are associated with an instance:
System Global Area (SGA): The SGA is shared by all server and background processes. The
SGA includes the following data structures:
- Database buffer cache: Caches blocks of data retrieved from the database files
- Redo log buffer: Caches recovery information before writing it to the physical files
- Shared pool: Caches various constructs that can be shared among sessions
- Large pool: Optional area used for certain operations, such as Oracle backup and recovery
operations, and I/O server processes
- Java pool: Used for session-specific Java code and data in the Java Virtual Machine (JVM)
- Streams pool: Used by Oracle Streams to store information about the capture and apply
processes
Program Global Areas (PGA): Memory regions that contain data and control information
about a server or background process. A PGA is suballocated from the aggregated PGA area.
Note: The Oracle database uses initialization parameters to create and manage memory structures.

Oracle Database 11g: SQL Tuning Workshop 1 - 6

Database Buffer Cache


The database buffer cache is the portion of the SGA that holds copies of data blocks that are read
from data files. All users concurrently connected to the instance share access to the database buffer
cache.
The first time an Oracle Database server process requires a particular piece of data, it searches for the
data in the database buffer cache. If the process finds the data already in the cache (a cache hit), it
can read the data directly from memory. If the process cannot find the data in the cache (a cache
miss), it must copy the data block from a data file on disk into a buffer in the cache before accessing
the data. Accessing data through a cache hit is faster than data access through a cache miss.
The buffers in the cache are managed by a complex algorithm that uses a combination of least
recently used (LRU) lists and touch count. The DBWn (Database Writers) processes are responsible
for writing modified (dirty) buffers in the database buffer cache to disk when necessary.

Oracle Database 11g: SQL Tuning Workshop 1 - 7

Redo Log Buffer


The redo log buffer is a circular buffer in the SGA that holds information about changes made to the
database. This information is stored in redo entries. Redo entries contain the information necessary to
reconstruct (or redo) changes that are made to the database by INSERT, UPDATE, DELETE,
CREATE, ALTER, or DROP operations. Redo entries are used for database recovery, if necessary.
Redo entries are copied by Oracle Database server processes from the users memory space to the
redo log buffer in the SGA. The redo entries take up continuous, sequential space in the buffer. The
LGWR (log writer) background process writes the redo log buffer to the active redo log file (or group
of files) on disk. LGWR is a background process that is capable of asynchronous I/O.
Note: Depending on the number of CPUs on your system, there may be more than one redo log
buffer. They are automatically allocated.

Oracle Database 11g: SQL Tuning Workshop 1 - 8

Shared Pool
The shared pool portion of the SGA contains the following main parts:
The library cache includes the sharable parts of SQL statements, PL/SQL procedures and
packages. It also contains control structures such as locks.
The data dictionary is a collection of database tables containing reference information about the
database. The data dictionary is accessed so often by Oracle Database that two special locations
in memory are designated to hold dictionary data. One area is called the data dictionary cache,
also known as the row cache, and the other area is called the library cache. All Oracle Database
server processes share these two caches for access to data dictionary information.
The result cache is composed of the SQL query result cache and the PL/SQL function result
cache. This cache is used to store results of SQL queries or PL/SQL functions to speed up their
future executions.
Control structures are essentially lock structures.
Note: In general, any item in the shared pool remains until it is flushed according to a modified LRU
algorithm.

Oracle Database 11g: SQL Tuning Workshop 1 - 9

Processing a DML Statement: Example


The steps involved in executing a data manipulation language (DML) statement are as follows:
1. The server process receives the statement and checks the library cache for any shared SQL area
that contains a similar SQL statement. If a shared SQL area is found, the server process checks
the users access privileges to the requested data, and the existing shared SQL area is used to
process the statement. If not, a new shared SQL area is allocated for the statement, so that it can
be parsed and processed.
2. If the data and rollback blocks are not already in the buffer cache, the server process reads them
from the data files into the buffer cache. The server process locks the rows that are to be
modified.
3. The server process records the changes to be made to the data buffers as well as the undo
changes. These changes are written to the redo log buffer before the in-memory data and
rollback buffers are modified. This is called write-ahead logging.
4. The rollback buffers contain values of the data before it is modified. The rollback buffers are
used to store the before image of the data so that the DML statements can be rolled back, if
necessary. The data buffers record the new values of the data.
5. The user gets the feedback from the DML operation (such as how many rows were affected by
the operation).
Note: Any changed blocks in the buffer cache are marked as dirty buffers; that is, the buffers are not
the same as the corresponding blocks on the disk. These buffers are not immediately written to disk
Oracle Database 11g: SQL Tuning Workshop 1 - 10

by the DBWn processes.

Oracle Database 11g: SQL Tuning Workshop 1 - 10

COMMIT Processing: Example


When COMMIT is issued, the following steps are performed:
1. The server process places a commit record, along with the system change number (SCN), in the
redo log buffer. The SCN is a number monotonically incremented and is unique within the
database. It is used by the Oracle Database as an internal time stamp to synchronize data and to
provide read consistency when data is retrieved from the data files. Using the SCN enables the
Oracle Database to perform consistency checks without depending on the date and time of the
operating system.
2. The background LGWR process performs a contiguous write of all the redo log buffer entries up
to and including the commit record to the redo log files. After this point, the Oracle Database
can guarantee that the changes are not lost even if there is an instance failure.
3. The server process provides feedback to the user process about the completion of the transaction.
Note: If not done already, DBWn eventually writes the actual changes back to disk based on its own
internal timing mechanism.

Oracle Database 11g: SQL Tuning Workshop 1 - 11

Large Pool
You can configure an optional memory area called the large pool to provide large memory
allocations for:
Session memory for the shared server, the Oracle XA interface (used where transactions interact
with more than one database), or parallel execution buffers
I/O server processes
Oracle Database backup and restore operations
By allocating the above memory components from the large pool, Oracle Database can use the
shared pool primarily for caching the shared part of SQL and PL/SQL constructs. This was the shared
pool original design to store SQL and PL/SQL constructs. Using the large pool avoids fragmentation
issues associated with having large and small allocations sharing the same memory area. Unlike the
shared pool, the large pool does not have an LRU list.
You should consider configuring a large pool if your instance uses any of the following:
Parallel execution: Parallel query uses shared pool memory to cache parallel execution
message buffers.
Recovery Manager: Recovery Manager uses the shared pool to cache I/O buffers during
backup and restore operations.
Shared server: In a shared server architecture, the session memory for each client process is
included in the shared pool.
Oracle Database 11g: SQL Tuning Workshop 1 - 12

Java Pool and Streams Pool


Java pool memory is used for all session-specific Java code and data in the JVM. Java pool memory
is used in different ways, depending on the mode in which Oracle Database runs.
The Streams pool is used exclusively by Oracle Streams. The Streams pool stores buffered queue
messages, and it provides memory for Oracle Streams capture and apply processes.
Note: A detailed discussion of Java programming and Oracle Streams is beyond the scope of this
course.

Oracle Database 11g: SQL Tuning Workshop 1 - 13

Program Global Area (PGA)


The content of the PGA memory varies, depending on whether or not the instance runs the shared
server option.
Generally, the PGA memory is divided into the following areas:
Session memory is the memory allocated to hold a sessions variables (logon information) and
other information related to the session. For a shared server, the session memory is shared and
not private.
Cursors are handles to private memory structures of specific SQL statements
SQL work areas are allocated to support memory-intensive operators such as the ones listed in
the slide. Generally, bigger work areas can significantly improve the performance of a particular
operator at the cost of higher memory consumption.

Oracle Database 11g: SQL Tuning Workshop 1 - 14

Background Process Roles


The background processes commonly seen in non-RAC, non-ASM environments can include the
following:
Database writer process (DBWn): Asynchronously writes modified (dirty) buffers in the
database buffer cache to disk
Log writer process (LGWR): Writes the recovery information called redo information in the log
buffer to a redo log file on disk
Checkpoint process (CKPT): Records checkpoint information in control files and each data file
header
System Monitor process (SMON): Performs recovery at instance startup and cleans up unused
temporary segments
Process monitor process (PMON): Performs process recovery when a user process fails
Result cache recoverer background process (RCBG): Used to maintain the result cache in the
shared pool
Job queue process (CJQ0): Runs user jobs used in batch processing through the Scheduler
Archiver processes (ARCn): Copies redo log files to a designated storage device after a log
switch has occurred
Queue monitor processes (QMNn): Monitors the Oracle Streams message queues
Manageability monitoring process (MMON): Performs manageability-related background tasks
Memory Manager background process (MMAN): Used to manage SGA and PGA memory
Oracle Database 11g: SQL Tuning Workshop 1 - 15

components automatically

Oracle Database 11g: SQL Tuning Workshop 1 - 15

Automatic Shared Memory Management


In earlier releases of the Oracle Database, you did not have exact control over the total size of the
SGA. The reason was that memory was allocated by Oracle for the fixed SGA and for other internal
metadata allocations over and above the total size of the user-specified SGA parameters. In earlier
releases, you had to manually specify the amount of memory to be allocated for the database buffer
cache, shared pool, Java pool, Streams pool, and large pool. It was often a challenge to optimally size
these components. Undersizing often resulted in poor performance and out-of-memory errors,
whereas oversizing wasted memory.
Since Oracle Database 10g, you can use the Automatic Shared Memory Management (ASMM)
feature to enable the database to automatically determine the size of each of these memory
components within the limits of the total SGA size.
The system uses an SGA size parameter (SGA_TARGET) that includes all the memory in the SGA,
including all the automatically sized components, manually sized components, and any internal
allocations during startup. ASMM simplifies the configuration of the SGA by enabling you to specify
a total memory amount to be used for all SGA components. The Oracle Database then periodically
redistributes memory between the automatically tuned components, according to workload
requirements.
Note: You must set STATISTICS_LEVEL to TYPICAL or ALL to use ASMM.

Oracle Database 11g: SQL Tuning Workshop 1 - 16

Automated SQL Execution Memory Management


This feature provides an automatic mode for allocating memory to working areas in the PGA. You
can use the PGA_AGGREGATE_TARGET parameter to specify the total amount of memory that
should be allocated to the PGA areas of the instances sessions. In automatic mode, working areas
that are used by memory-intensive operators (sorts and hash joins) can be automatically and
dynamically adjusted.
This feature offers several performance and scalability benefits for decision support system (DSS)
workloads and mixed workloads with complex queries. The overall system performance is
maximized, and the available memory is allocated more efficiently among queries to optimize both
throughput and response time. In particular, the savings that are gained from improved use of
memory translate to better throughput at high loads.
Note: In earlier releases of Oracle Database server, you had to manually specify the maximum work
area size for each type of SQL operator, such as sort or hash join. This proved to be very difficult
because the workload changes constantly. Although the current release of Oracle Database supports
this manual PGA memory management method that might be useful for specific sessions, it is
recommended that you leave automatic PGA memory management enabled.

Oracle Database 11g: SQL Tuning Workshop 1 - 17

Automatic Memory Management


As seen already, the size of the various memory areas of the instance directly impacts the speed of
SQL processing. Depending on the database workload, it is difficult to size those components
manually.
With Automatic Memory Management, the system automatically adapts the size of each memorys
components to your workload memory needs.
Set your MEMORY_TARGET initialization parameter for the database instance and the MMAN
background process automatically tunes to the target memory size, redistributing memory as needed
between the internal components of the SGA and between the SGA and the aggregated PGAs.
In more details, the Automatic Shared Memory Management feature uses the SGA memory broker
that is implemented by two background processes Manageability Monitor (MMON) and Memory
Manager (MMAN). Statistics and memory advisory data are periodically captured in memory by
MMON. MMAN coordinates the sizing of the memory components according to MMON decisions.
Note: Currently, this mechanism is only implemented on Linux, Solaris, HP-UX, AIX, and Windows.

Oracle Database 11g: SQL Tuning Workshop 1 - 18

Database Storage Architecture


The files that constitute an Oracle database are organized into the following:
Control files: Contain data about the database itself (that is, physical database structure
information). These files are critical to the database. Without them, you cannot open data files to
access the data in the database.
Data files: Contain the user or application data of the database, as well as metadata and the data
dictionary
Online redo log files: Allow for instance recovery of the database. If the database server crashes
and does not lose any data files, the instance can recover the database with the information in
these files.
The following additional files are important for the successful running of the database:
Parameter file: Is used to define how the instance is configured when it starts up
Password file: Allows sysdba, sysoper, and sysasm to connect remotely to the database
and perform administrative tasks
Backup files: Are used for database recovery. You typically restore a backup file when a media
failure or user error has damaged or deleted the original file.
Archived redo log files: Contain an ongoing history of the data changes (redo) that are
generated by the instance. Using these files and a backup of the database, you can recover a lost
data file. That is, archive logs enable the recovery of restored data files.

Oracle Database 11g: SQL Tuning Workshop 1 - 19

Database Storage Architecture (continued)


Trace files: Each server and background process can write to an associated trace file. When an
internal error is detected by a process, the process dumps information about the error to its trace
file. Some of the information written to a trace file is intended for the developer, whereas other
information is for Oracle Support Services.
Alert log file: These are special trace entries. The alert log of a database is a chronological log
of messages and errors. Each instance has one alert log file. It is recommended that you review
this periodically.

Oracle Database 11g: SQL Tuning Workshop 1 - 20

Logical and Physical Database Structures


The database has logical structures and physical structures.
Tablespaces
A database is divided into logical storage units called tablespaces, which group related logical
structures together. For example, tablespaces commonly group all of an applications objects to
simplify some administrative operations. You may have a tablespace for different applications.
Databases, Tablespaces, and Data Files
The relationship among databases, tablespaces, and data files is illustrated in the slide. Each database
is logically divided into one or more tablespaces. One or more data files are explicitly created for
each tablespace to physically store the data of all logical structures in a tablespace. If it is a
TEMPORARY tablespace instead of a data file, the tablespace has a temporary file.

Oracle Database 11g: SQL Tuning Workshop 1 - 21

Logical and Physical Database Structures (continued)


Schemas
A schema is a collection of database objects that are owned by a database user. Schema objects are
the logical structures that directly refer to the databases data. Schema objects include structures,
such as tables, views, sequences, stored procedures, synonyms, indexes, clusters, and database links.
In general, schema objects include everything that your application creates in the database.
Data Blocks
At the finest level of granularity, an Oracle databases data is stored in data blocks. One data block
corresponds to a specific number of bytes of physical database space on the disk. A data block size is
specified for each tablespace when it is created. A database uses and allocates free database space in
Oracle data blocks.
Extents
The next level of logical database space is an extent. An extent is a specific number of contiguous
data blocks (obtained in a single allocation) that are used to store a specific type of information.
Segments
The level of logical database storage above an extent is called a segment. A segment is a set of
extents that are allocated for a certain logical structure. Different types of segments include:
Data segments: Each nonclustered, non-index-organized table has a data segment, with the
exception of external tables and global temporary tables that have no segments, and partitioned
tables in which each table has one or more segments. All of the tables data is stored in the
extents of its data segment. For a partitioned table, each partition has a data segment. Each
cluster has a data segment. The data of every table in the cluster is stored in the clusters data
segment.
Index segments: Each index has an index segment that stores all of its data. For a partitioned
index, each partition has an index segment.
Undo segments: One UNDO tablespace is created for each database instance. This tablespace
contains numerous undo segments to temporarily store undo information. The information in an
undo segment is used to generate read-consistent database information and, during database
recovery, to roll back uncommitted transactions for users.
Temporary segments: Temporary segments are created by the Oracle Database when a SQL
statement needs a temporary work area to complete execution. When the statement finishes
execution, the temporary segments extents are returned to the instance for future use. Specify
either a default temporary tablespace for every user, or a default temporary tablespace that is
used across the database.
The Oracle Database dynamically allocates space. When the existing extents of a segment are full,
additional extents are added. Because extents are allocated as needed, the extents of a segment may
or may not be contiguous on the disk.

Oracle Database 11g: SQL Tuning Workshop 1 - 22

Segments, Extents, and Blocks


Database objects, such as tables and indexes are stored as segments in tablespaces. Each segment
contains one or more extents. An extent consists of contiguous data blocks, which means that each
extent can exist only in one data file. Data blocks are the smallest unit of I/O in the database.
When the database requests a set of data blocks from the operating system (OS), the OS maps this to
an actual file system or disk block on the storage device. Because of this, you do not need to know
the physical address of any of the data in your database. This also means that a data file can be
striped or mirrored on several disks.
The size of the data block can be set at the time of database creation. The default size of 8 KB is
adequate for most databases. If your database supports a data warehouse application that has large
tables and indexes, a larger block size may be beneficial.
If your database supports a transactional application in which reads and writes are random,
specifying a smaller block size may be beneficial. The maximum block size depends on your OS.
The minimum Oracle block size is 2 KB; it should rarely (if ever) be used.
You can have tablespaces with a nonstandard block size. For details, see the Oracle Database
Administrators Guide.

Oracle Database 11g: SQL Tuning Workshop 1 - 23

SYSTEM and SYSAUX Tablespaces


Each Oracle Database must contain a SYSTEM tablespace and a SYSAUX tablespace, which are
automatically created when the database is created. The system default is to create a smallfile
tablespace. You can also create bigfile tablespaces, which enable the Oracle database to manage ultra
large files (up to 8 exabytes in size).
A tablespace can be online (accessible) or offline (not accessible). The SYSTEM tablespace is always
online when the database is open. It stores tables that support the core functionality of the database,
such as the data dictionary tables.
The SYSAUX tablespace is an auxiliary tablespace to the SYSTEM tablespace. The SYSAUX
tablespace stores many database components, and it must be online for the correct functioning of all
database components.
Note: The SYSAUX tablespace may be taken offline for performing tablespace recovery, whereas this
is not possible in the case of the SYSTEM tablespace. Neither of them may be made read-only.

Oracle Database 11g: SQL Tuning Workshop 1 - 24

Oracle Database 11g: SQL Tuning Workshop 1 - 25

Oracle Database 11g: SQL Tuning Workshop 1 - 26

Objectives
This lesson gives you an understanding of the tuning process and the different components of an
Oracle Database that may require tuning.

Oracle Database 11g: SQL Tuning Workshop 2 - 2

Reasons for Inefficient SQL Performance


SQL statements can perform poorly for a variety of reasons:
Stale optimizer statistics: SQL execution plans are generated by the cost-based optimizer
(CBO). For CBO to effectively choose the most efficient plan, it needs accurate information on
the data volume and distribution of tables and indexes referenced in the queries. Without
accurate optimizer statistics, the CBO can easily be mislead and generate suboptimal execution
plans.
Missing access structures: Absence of access structures, such as indexes, materialized views,
and partitions is a common reason for poor SQL performance. The right set of access structures
can improve SQL performance by several orders of magnitude.
Suboptimal execution plan selection: The CBO can sometimes select a suboptimal execution
plan for a SQL statement. This happens for most part because of incorrect estimates of some
attributes of that SQL statement, such as its cost, cardinality, or predicate selectivity.
Poorly constructed SQL: If the SQL statement is designed poorly, there is not much that the
optimizer can do to improve its performance. A missing join condition leading to a Cartesian
product, or the use of more expensive SQL constructs like UNION in place of UNION ALL, are
just a couple of examples of inefficient SQL design.
These four main causes of poor SQL optimization can have a drastic impact on performance.
Note: Additional reasons for poor performance might be connected with hardware-related issues,
Oracle Database 11g: SQL Tuning Workshop 2 - 3

such as memory, I/Os, CPUs, and so on.

Oracle Database 11g: SQL Tuning Workshop 2 - 3

Inefficient SQL: Examples


The slide shows five examples of possibly poorly constructed SQL that could easily result in
inefficient execution.
1. This is a common business question type. The query determines how many products have list
prices less than 15% above the average cost of the product. This statement has a correlated
subquery, which means that the subquery is run for every row found in the outer query. The
query is better written as:
SELECT COUNT(*) FROM products p,
(SELECT prod_id, AVG(unit_cost) ac FROM costs
GROUP BY prod_id) c
WHERE p.prod_id = c.prod_id AND
p.prod_list_price < 1.15 * c.ac

2. This query applies functions to the join columns, restricting the conditions where indexes can be
used. Use a simple equality, if you can. Otherwise, a function-based index may be necessary.
3. This query has a condition that forces implicit data type conversion; the ORDER_ID_CHAR
column is a character type, and the constant is a numeric type. You should make the literal match
the column type.

Oracle Database 11g: SQL Tuning Workshop 2 - 4

Inefficient SQL: Examples (continued)


4. The fourth query uses a data type conversion function in it to make the data types match in the
comparison. The problem here is that the TO_CHAR function is applied to the column value,
rather than to the constant. This means that the function is called for every row in the table. It
would be better to convert the literal once, and not convert the column. This is better queried
as:
SELECT * FROM employees
WHERE salary = TO_NUMBER(:sal)

5. The UNION operator, as opposed to the UNION ALL operator, ensures that there are no
duplicate rows in the result set. However, this requires an extra step, a unique sort, to eliminate
any duplicates. If you know that there are no rows in common between the two UNIONed
queries, use UNION ALL instead of UNION. This eliminates the unnecessary sort.

Oracle Database 11g: SQL Tuning Workshop 2 - 5

Performance Monitoring Solutions


In addition to the classical reactive tuning capabilities of previous releases, such as Statspack, SQL
trace files, and performance views, Oracle Database 10g introduced new methodologies to monitor
your database in two categories:
Proactive monitoring:
- With Automatic Database Diagnostic Monitor (ADDM): This component is the ultimate
solution for Oracle database tuning. ADDM automatically identifies bottlenecks within the
Oracle Database. Additionally, working with other manageability components, ADDM
makes recommendations on the options available for fixing these bottlenecks.
- Oracle Database 11g further automates the SQL tuning process by identifying problematic
SQL statements, running SQL Tuning Advisor on them, and implementing the resulting
SQL profile recommendations to tune the statement without requiring user intervention.
Automatic SQL Tuning uses the AUTOTASK framework through a new task called
Automatic SQL Tuning that runs every night by default.

Oracle Database 11g: SQL Tuning Workshop 2 - 6

Performance Monitoring Solutions (continued)


Reactive monitoring:
- Server-generated alerts: The Oracle Database can automatically detect problematic
situations. In reaction to a detected problem, the Oracle Database sends you an alert
message with possible remedial action.
- The Oracle Database has powerful new data sources and performance-reporting
capabilities. Enterprise Manager provides an integrated performance management console
that uses all relevant data sources. Using a drill-down method, you can manually identify
bottlenecks with just a few mouse clicks.
New data sources are introduced to capture important information about your databases healthfor
example, new memory statistics (for current diagnostics) as well as statistics history stored in
Automatic Workload Repository (AWR).
Note: Accessing Enterprise Manager or tools discussed here may require additional licenses and
certain privileges generally reserved for database administrators.

Oracle Database 11g: SQL Tuning Workshop 2 - 7

Monitoring and Tuning Tools: Overview


Since Oracle Database 10g, Release 2, you can generate SQL reports from AWR data
($ORACLE_HOME/rdbms/admin/awrsqrpt.sql), basically, the equivalent to
sqrepsql.sql in Statspack.
Note
SPA stands for SQL Performance Analyzer.
ASH stands for Active Session History.
AWR stands for Automatic Workload Repository.
ADDM stands for Automatic Database Diagnostic Monitor.

Oracle Database 11g: SQL Tuning Workshop 2 - 8

EM Performance Pages for Reactive Tuning


There are cases where real-time problem diagnosis must be performed. An irate user calls you, or you
see a sudden spike in the activity of the system on the monitor. The Enterprise Manager (EM)
Performance pages use the same data sources as AWR and ADDM to display information about the
running of the database and the host system in a manner that is easily absorbed and allows for rapid
manual drilldown to the source of the problem.

Oracle Database 11g: SQL Tuning Workshop 2 - 9

Tuning Tools: Overview


Automatic Database Diagnostic Monitor: Continually analyzes the performance data that is collected
from the database instance
SQL Tuning Advisor: Analyzes SQL statements that have been identified as problematic, in an
effort to retune them. By default, this is an automated task. You can also, at any time, run the SQL
Tuning Advisor on a specific SQL workload, to look for ways to improve performance.
SQL Tuning Sets: Serve as a repository for sets of SQL statements. For example, the SQL Tuning
Advisor can run against a workload that is represented by a SQL Tuning Set. They can even be
transported from database to database, to perform analysis on different machines.
SQL Access Advisor: Analyzes a SQL statement, and provides advice on materialized views,
indexes, materialized view logs, and partitions
SQL Performance Analyzer: Automates the process of assessing the overall effect of a change, such
as upgrading a database or adding new indexes, on the full SQL workload by identifying
performance divergence for each statement
SQL Monitoring: The real-time SQL monitoring feature of Oracle Database enables you to monitor
the performance of SQL statements while they execute.
Note: SQL Plan Management can be used to control execution plan evolution. This topic is not
covered in this course. Refer to Oracle Database 11g: Performance Tuning for more information.
Oracle Database 11g: SQL Tuning Workshop 2 - 10

SQL Tuning Tasks: Overview


Many SQL tuning tasks should be performed on a regular basis. You may see a way to rewrite a
WHERE clause, but it may depend on a new index being built. This list of tasks gives you a
background of some important tasks that must be performed, and gives you an idea of what
dependencies you may have as you tune SQL:
Identifying high-load SQL statements is one of the most important tasks you should perform.
The ADDM is the ideal tool for this particular task.
By default, the Oracle Database gathers optimizer statistics automatically. For this, a job is
scheduled to run in the maintenance windows.
Operating system statistics provide information about the usage and performance of the main
hardware components as well as the performance of the operating system itself.
Often, there is a beneficial impact on performance by rebuilding indexes. For example,
removing nonselective indexes to speed the data manipulation language (DML), or adding
columns to the index to improve selectivity.
You can maintain the existing execution plan of SQL statements over time by using stored
statistics, outlines, or SQL plan baselines.

Oracle Database 11g: SQL Tuning Workshop 2 - 11

CPU and Wait Time Tuning Dimensions


When you tune your system, it is important that you compare the CPU time with the wait time of
your system. By comparing CPU time with wait time, you can determine how much of the response
time is spent on useful work and how much on waiting for resources potentially held by other
processes.
As a general rule, the systems where CPU time is dominant usually need less tuning than the ones
where wait time is dominant. On the other hand, high CPU usage can be caused by badly-written
SQL statements.
Although the proportion of CPU time to wait time always tends to decrease as load on the system
increases, steep increases in wait time are a sign of contention and must be addressed for good
scalability.
Adding more CPUs to a node, or nodes to a cluster, would provide very limited benefit under
contention. Conversely, a system where the proportion of CPU time does not decrease significantly
as load increases can scale better, and would most likely benefit from adding CPUs or Real
Application Clusters (RAC) instances if needed.
Note: AWR reports display CPU time together with wait time in the Top 5 Timed Events section, if
the CPU time portion is among the top five events.

Oracle Database 11g: SQL Tuning Workshop 2 - 12

Scalability with Application Design, Implementation, and Configuration


Poor application design, implementation, and configuration have a significant impact on scalability.
This results in:
Poor SQL and index design, resulting in a higher number of logical input/output (I/O) for the
same number of rows returned
Reduced availability because database objects take longer to maintain
However, design is not the only problem. The physical implementation of the application can be the
weak link, as in the following examples:
Systems can move to production environments with poorly written SQL that cause high I/O.
Infrequent transaction COMMITs or ROLLBACKs can cause long locks on resources.
The production environment can use different execution plans than those generated in testing.
Memory-intensive applications that allocate a large amount of memory without much thought
for freeing the memory can cause excessive memory fragmentation.
Inefficient memory usage places high stress on the operating virtual memory subsystem. This
affects performance and availability.

Oracle Database 11g: SQL Tuning Workshop 2 - 13

Common Mistakes on Customer Systems


1. Bad connection management: The application connects and disconnects for each database
interaction. This problem is common with stateless middleware in application servers. It has
over two orders of magnitude impact on performance, and is totally unscalable.
2. Bad use of cursors and the shared pool: Not using cursors results in repeated parses. If bind
variables are not used, there may be hard parsing of all similar SQL statements. This has an
order of magnitude impact on performance, and it is totally unscalable. Use cursors with bind
variables that open the cursor and execute it many times. Be suspicious of applications
generating dynamic SQL.
3. Bad SQL: Bad SQL is SQL that uses more resources than appropriate for the application. This
can be a decision support system (DSS) query that runs for more than 24 hours or a query from
an online application that takes more than a minute. SQL that consumes significant system
resources should be investigated for potential improvement. ADDM identifies high-load SQL
and the SQL Tuning Advisor can be used to provide recommendations for improvement.

Oracle Database 11g: SQL Tuning Workshop 2 - 14

Common Mistakes on Customer Systems (continued)


4. Use of nonstandard initialization parameters: These might have been implemented based on
poor advice or incorrect assumptions. Most systems give acceptable performance using only the
set of basic parameters. In particular, undocumented optimizer features can cause a great deal of
problems that may require considerable investigation. Likewise, optimizer parameters set in the
initialization parameter file can override proven optimal execution plans. For these reasons,
schemas, schema statistics, and optimizer settings should be managed together as a group to
ensure consistency of performance.
5. Getting database I/O wrong: Many sites lay out their databases poorly over the available disks.
Other sites specify the number of disks incorrectly because they configure disks by disk space
and not by I/O bandwidth.
6. Redo log setup problems: Many sites run with too small redo log files. Small redo logs cause
system checkpoints to continuously put a high load on the buffer cache and the I/O system. If
there are very few redo logs, the archive cannot keep up, and the database waits for the archive
process to catch up.
7. Excessive serialization: Serialization of data blocks in the buffer cache due to shortage of undo
segments is particularly common in applications with large numbers of active users and a few
undo segments. Use Automatic Segment Space Management (ASSM) and automatic undo
management to solve this problem.
8. Long full table scans: Long full table scans for high-volume or interactive online operations
could indicate poor transaction design, missing indexes, or poor SQL optimization. Long table
scans, by nature, are I/O-intensive and unscalable.
9. High amounts of recursive (SYS) SQL: Large amounts of recursive SQL executed by SYS
could indicate that space management activities, such as extent allocations, take place. This is
unscalable and impacts user response time. Use locally managed tablespaces to reduce recursive
SQL due to extent allocation. Recursive SQL executed under another user ID is probably SQL
and PL/SQL, so this is not a problem.
10. Deployment and migration errors: In many cases, an application uses too many resources
because the schema owning the tables has not been successfully migrated from the development
environment or from an older implementation. Examples of this are missing indexes or incorrect
statistics. These errors can lead to suboptimal execution plans and poor interactive user
performance. When migrating applications of known performance, export the schema statistics
to maintain plan stability using the DBMS_STATS package.
Although these errors are not directly detected by ADDM, ADDM highlights the resulting high-load
SQL.

Oracle Database 11g: SQL Tuning Workshop 2 - 15

Proactive Tuning Methodology


Tuning usually implies fixing a performance problem. However, tuning should be part of the life
cycle of an application, through the analysis, design, coding, production, and maintenance stages.
The tuning phase is often left until the system is in production. At that time, tuning becomes a
reactive exercise, where the most important bottleneck is identified and fixed.
The slide lists some of the issues that affect performance and that should be tuned proactively instead
of reactively. These are discussed in more detail in the following slides.

Oracle Database 11g: SQL Tuning Workshop 2 - 16

Simplicity in Application Design


Applications are no different from any other designed and engineered product. If the design looks
right, it probably is right. This principle should always be kept in mind when building applications.
Consider some of the following design issues:
If the table design is so complicated that nobody can fully understand it, the table is probably
designed badly.
If SQL statements are so long and involved that it would be impossible for any optimizer to
effectively optimize it in real time, there is probably a bad statement, underlying transaction, or
table design.
If there are many indexes on a table and the same columns are repeatedly indexed, there is
probably a bad index design.
If queries are submitted without suitable qualification (the WHERE clause) for rapid response for
online users, there is probably a bad user interface or transaction design.

Oracle Database 11g: SQL Tuning Workshop 2 - 17

Data Modeling
Data modeling is important in successful relational application design. This should be done in a way
that quickly and accurately represents the business practices. Apply your greatest modeling efforts to
those entities affected by the most frequent business transactions. Use of modeling tools can then
rapidly generate schema definitions and can be useful when a fast prototype is required.
Normalizing data prevents duplication. When data is normalized, you have a clear picture of the keys
and relationships. It is then easier to perform the next step of creating tables, constraints, and indexes.
A good data model ultimately means that your queries are written more efficiently.

Oracle Database 11g: SQL Tuning Workshop 2 - 18

Table Design
Table design is largely a compromise between flexibility and performance of core transactions. To
keep the database flexible and able to accommodate unforeseen workloads, the table design should
be very similar to the data model, and it should be normalized to at least third normal form. However,
certain core transactions can require selective denormalization for performance purposes.
Use the features supplied with Oracle Database to simplify table design for performance, such as
storing tables prejoined in clusters, adding derived columns and aggregate values, and using
materialized views or partitioned tables. Additionally, create check constraints and columns with
default values to prevent bad data from getting into the tables.
Design should be focused on business-critical tables so that good performance can be achieved in
areas that are the most used. For noncritical tables, shortcuts in design can be adopted to enable a
more rapid application development. If, however, a noncore table becomes a performance problem
during prototyping and testing, remedial design efforts should be applied immediately.

Oracle Database 11g: SQL Tuning Workshop 2 - 19

Index Design
Index design is also a largely iterative process based on the SQL that is generated by application
designers. However, it is possible to make a sensible start by building indexes that enforce foreign
key constraints (to reduce response time on joins between primary key tables and foreign key tables)
and creating indexes on frequently accessed data, such as a persons name. Primary keys and unique
keys are automatically indexed except for the DISABLE VALIDATE and DISABLE
NOVALIDATE RELY constraints. As the application evolves and testing is performed on realistic
sizes of data, certain queries need performance improvements, for which building a better index is a
good solution.
The following indexing design ideas should be considered when building a new index.
Appending Columns to an Index or Using Index-Organized Tables
One of the easiest ways to speed up a query is to reduce the number of logical I/Os by eliminating a
table scan from the execution plan. This can be done by appending to index all the columns of the
table referenced by the query. These columns are the select list columns and any required join or sort
columns. This technique is particularly useful in speeding up an online applications response times
when time-consuming I/Os are reduced. This is best applied when testing the application with
properly-sized data for the first time. The most aggressive form of this technique is to build an indexorganized table (IOT).

Oracle Database 11g: SQL Tuning Workshop 2 - 20

Using Views
Views can speed up and simplify application design. A simple view definition can mask data model
complexity from the programmers whose priorities are to retrieve, display, collect, and store data.
However, though views provide clean programming interfaces, they can cause suboptimal, resourceintensive queries when nested too deeply. The worst type of view use is creating joins on views that
reference other views, which in turn reference other views. In many cases, developers can satisfy the
query directly from the table without using a view. Because of their inherent properties, views
usually make it difficult for the optimizer to generate the optimal execution plan.

Oracle Database 11g: SQL Tuning Workshop 2 - 21

SQL Execution Efficiency


An application that is designed for SQL execution efficiency must support the following
characteristics:
Good database connection management: Connecting to the database is an expensive operation that
is highly unscalable. Therefore, the number of concurrent connections to the database should be
minimized as much as possible. A simple system, where a user connects at application initialization,
is ideal. However, in a Web-based or multitiered application, where application servers are used to
multiplex database connections to users, this can be difficult. With these types of applications, design
efforts should ensure that database connections are pooled and not reestablished for each user
request.
Good cursor usage and management: Maintaining user connections is equally important for
minimizing the parsing activity on the system. Parsing is the process of interpreting a SQL statement
and creating an execution plan for it. This process has many phases, including syntax checking,
security checking, execution plan generation, and loading shared structures into the shared pool.

Oracle Database 11g: SQL Tuning Workshop 2 - 22

SQL Execution Efficiency (continued)


There are two types of parse operations:
Hard parsing: A SQL statement is submitted for the first time, and no match is found in the
shared pool. Hard parses are the most resource-intensive and unscalable because they perform
all the operations involved in a parse.
Soft parsing: A SQL statement is submitted for the first time, and a match is found in the shared
pool. The match can be the result of previous execution by another user. The SQL statement is
shared, which is good for performance. However, soft parses are not ideal because they still
require syntax and security checking, which consume system resources.
Because parsing should be minimized as much as possible, application developers should design
their applications to parse SQL statements once and execute them many times. This is done through
cursors. Experienced SQL programmers should be familiar with the concept of opening and
reexecuting cursors.
Application developers must also ensure that SQL statements are shared within the shared pool. To
do this, bind variables to represent the parts of the query that change from execution to execution. If
this is not done, the SQL statement is likely to be parsed once and never reused by other users. To
ensure that SQL is shared, use bind variables and do not use string literals with SQL statements.

Oracle Database 11g: SQL Tuning Workshop 2 - 23

Writing SQL to Share Cursors


For Oracle to share cursors, the code must be written in the same way character wise (This allows the
system to recognize that two statements are the same and thus can be shared.), unless you use some
special initialization parameters, such as CURSOR_SHARING discussed later in the lesson titled
Using Bind Variables. You should, therefore, develop coding conventions for SQL statements in ad
hoc queries, SQL scripts, and Oracle Call Interface (OCI) calls.
Use generic shared code:
Write and store procedures that can be shared across applications.
Use database triggers.
Write referenced triggers and procedures when using application development tools.
Write library routines and procedures in other environments.
Write to format standards:
Develop format standards for all statements, including those in PL/SQL code.
Develop rules for the use of uppercase and lowercase characters.
Develop rules for the use of white space (spaces, tabs, returns).
Develop rules for the use of comments (preferably keeping them out of the SQL statements
themselves).
Use the same names to refer to identical database objects. If possible, prefix each object with a
schema name.
Oracle Database 11g: SQL Tuning Workshop 2 - 24

Performance Checklist
Set the minimal number of initialization parameters. Ideally, most initialization parameters
should be left at default. If there is more tuning to perform, this shows up when the system is
under load. Set storage options for tables and indexes in appropriate tablespaces.
Verify that all SQL statements are optimal and understand their resource usage.
Validate that middleware and programs that connect to the database are efficient in their
connection management and do not log on and log off repeatedly.
Validate that the SQL statements use cursors efficiently. Each SQL statement should be parsed
once and then executed multiple times. This does not happen mostly because bind variables are
not used properly and the WHERE clause predicates are sent as string literals.
Validate that all schema objects are correctly migrated from the development environment to the
production database. This includes tables, indexes, sequences, triggers, packages, procedures,
functions, Java objects, synonyms, grants, and views. Ensure that any modifications made in
testing are made to the production system.
As soon as the system is rolled out, establish a baseline set of statistics from the database and
operating system. This first set of statistics validates or corrects any assumptions made in the
design and rollout process.

Oracle Database 11g: SQL Tuning Workshop 2 - 25

Oracle Database 11g: SQL Tuning Workshop 2 - 26

Oracle Database 11g: SQL Tuning Workshop 2 - 27

Oracle Database 11g: SQL Tuning Workshop 3 - 2

Structured Query Language


SQL is the language with which all programs and users access data in an Oracle Database.
Application programs and Oracle tools often allow users to access the database without using SQL
directly, but these applications in turn must use SQL when executing user requests. The system
strives to comply with industry-accepted standards and participates actively in SQL standards
committees (ANSI and ISO). The latest SQL standard was adopted in July 2003 and is often called
SQL:2003. You can categorize SQL statements into six main sets:
Data manipulation language (DML) statements manipulate or query data in existing schema
objects.
Data definition language (DDL) statements define, alter the structure of, and drop schema
objects.
Transaction control statements (TCS) manage the changes made by DML statements and group
DML statements into transactions.
System Control Statements change the properties of the Oracle Database instance.
Session Control Statements manage the properties of a particular users session.
Embedded SQL statements incorporate DDL, DML, and TCS within a procedural language
program, such as PL/SQL and Oracle precompilers. This incorporation is done using the
statements listed in the slide under the ESS category.
Note: SELECT statements are the most used statements. Although the rest of this course focuses
mainly on queries, it is important to note that any type of SQL statement is subject to optimization.
Oracle Database 11g: SQL Tuning Workshop 3 - 3

SQL Statement Representation


Oracle Database represents each SQL statement it runs with a shared SQL area and a private SQL
area. Oracle Database recognizes when two users execute the same SQL statement and reuses the
shared SQL area for those users. However, each user must have a separate copy of the statements
private SQL area.
A shared SQL area contains all optimization information necessary to execute the statement whereas
a private SQL area contains all run-time information related to a particular execution of the
statement.
Oracle Database saves memory by using one shared SQL area for SQL statements run multiple times,
which often happens when many users run the same application.
Note: In evaluating whether statements are similar or identical, Oracle Database considers SQL
statements issued directly by users and applications, as well as recursive SQL statements issued
internally by a DDL statement.

Oracle Database 11g: SQL Tuning Workshop 3 - 4

SQL Statement Implementation


Oracle Database creates and uses memory structures for various purposes. For example, memory
stores program codes that are run, data that is shared among users, and private data areas for each
connected user.
Oracle Database allocates memory from the shared pool when a new SQL statement is parsed, to
store in the shared SQL area. The size of this memory depends on the complexity of the statement. If
the entire shared pool has already been allocated, Oracle Database can deallocate items from the pool
using a modified least recently used (LRU) algorithm until there is enough free space for the new
statements shared SQL area. If Oracle Database deallocates a shared SQL area, the associated SQL
statement must be reparsed and reassigned to another shared SQL area at its next execution.

Oracle Database 11g: SQL Tuning Workshop 3 - 5

SQL Statement Processing: Overview


The graphic in the slide shows all the steps involved in query execution and these steps can be found
in Oracle Database Concepts 11g Release 1 (11.1).

Oracle Database 11g: SQL Tuning Workshop 3 - 6

SQL Statement Processing: Steps


Note that not all statements require all these steps. For example, nonparallel DDL statements are
required in only two steps: Create and Parse.
Parallelizing the statement involves deciding that it can be parallelized as opposed to actually
building parallel execution structures.

Oracle Database 11g: SQL Tuning Workshop 3 - 7

Step 1: Create a Cursor


A cursor can be thought of as an association between a cursor data area in a client program and
Oracle servers data structures. Most Oracle tools hide much of cursor handling from the user, but
Oracle Call Interface (OCI) programs need the flexibility to be able to process each part of query
execution separately. Therefore, precompilers allow explicit cursor declaration. Most of this can also
be done using the DBMS_SQL package as well.
A handle is similar to the handle on a mug. When you have a hold of the handle, you have a hold of
the cursor. It is a unique identifier for a particular cursor that can only be obtained by one process at a
time.
Programs must have an open cursor to process a SQL statement. The cursor contains a pointer to the
current row. The pointer moves as rows are fetched until there are no more rows left to process.
The following slides use the DBMS_SQL package to illustrate cursor management. This may be
confusing to people unfamiliar with it; however, it is more friendly than PRO*C or OCI. It is slightly
problematic in that it performs FETCH and EXECUTE together, so the execute phase cannot be
separately identified in the trace.

Oracle Database 11g: SQL Tuning Workshop 3 - 8

Step 2: Parse the Statement


During parsing, the SQL statement is passed from the user process to the Oracle instance, and a
parsed representation of the SQL statement is loaded into a shared SQL area.
Translation and verification involve checking if the statement already exists in the library cache.
For distributed statements, check for the existence of database links.
Typically, the parse phase is represented as the stage where the query plan is generated.
The parse step can be deferred by the client software to reduce network traffic. What this means is
that the PARSE is bundled with the EXECUTE, so there are fewer round-trips to the server.
Note: When checking if statements are identical, they must be identical in every way including case
and spacing.

Oracle Database 11g: SQL Tuning Workshop 3 - 9

Steps 3 and 4: Describe and Define


Step 3: Describe
The describe stage is necessary only if the characteristics of a querys result are not known, for
example, when a query is entered interactively by a user. In this case, the describe stage determines
the characteristics (data types, lengths, and names) of a querys result. Describe tells the application
what select list items are required. If, for example, you enter a query such as:
SQL> select * from employees;,
information about the columns in the employees table is required.
Step 4: Define
In the define stage, you specify the location, size, and data type of variables defined to receive each
fetched value. These variables are called define variables. Oracle Database performs data type
conversion, if necessary.
These two steps are generally hidden from users in tools such as SQL*Plus. However, with
DBMS_SQL or OCI, it is necessary to tell the client what the output data is and which the setup areas
are.

Oracle Database 11g: SQL Tuning Workshop 3 - 10

Steps 5 and 6: Bind and Parallelize


Step 5: Bind
At this point, Oracle Database knows the meaning of the SQL statement, but still does not have
enough information to run the statement. Oracle Database needs values for any variables listed in the
statement. The process of obtaining these values is called binding variables.
Step 6: Parallelize
Oracle Database can parallelize the execution of SQL statements (such as SELECT, INSERT,
UPDATE, MERGE, DELETE), and some DDL operations, such as index creation, creating a table with
a subquery, and operations on partitions. Parallelization causes multiple server processes to perform
the work of the SQL statement, so it can complete faster.
Parallelization involves dividing the work of a statement among a number of slave processes.
Parsing has already identified if a statement can be parallelized or not and has built the appropriate
parallel plan. At execution time, this plan is then implemented if sufficient resource is available.

Oracle Database 11g: SQL Tuning Workshop 3 - 11

Steps 7 Through 9
At this point, Oracle Database has all the necessary information and resources, so the statement is
run. If the statement is a query (without the FOR UPDATE clause) statement, no rows need to be
locked because no data is changed. If the statement is an UPDATE or a DELETE statement, however,
all rows that the statement affects are locked until the next COMMIT, ROLLBACK, or SAVEPOINT
for the transaction. This ensures data integrity.
For some statements, you can specify a number of executions to be performed. This is called array
processing. Given n number of executions, the bind and define locations are assumed to be the
beginning of an array of size n.
In the fetch stage, rows are selected and ordered (if requested by the query), and each successive
fetch retrieves another row of the result until the last row has been fetched.
The final stage of processing a SQL statement is closing the cursor.

Oracle Database 11g: SQL Tuning Workshop 3 - 12

SQL Statement Processing PL/SQL: Example


This example summarizes the various steps discussed previously.
Note: In this example, you do not show the fetch operation. It is also possible to combine both the
EXECUTE and FETCH operations in EXECUTE_AND_FETCH to perform EXECUTE and FETCH
together in one call. This may reduce the number of network round-trips when used against a remote
database.

Oracle Database 11g: SQL Tuning Workshop 3 - 13

SQL Statement Parsing: Overview


Parsing is one stage in the processing of a SQL statement. When an application issues a SQL
statement, the application makes a parse call to Oracle Database. During the parse call, Oracle
Database performs the following actions:
Checks the statement for syntactic and semantic validity
Determines whether the process issuing the statement has the privileges to run it
Allocates a private SQL area for the statement
Determines whether or not there is an existing shared SQL area containing the parsed
representation of the statement in the library cache. If so, the user process uses this parsed
representation and runs the statement immediately. If not, Oracle Database generates the parsed
representation of the statement, and the user process allocates a shared SQL area for the
statement in the library cache and stores its parsed representation there.
Note the difference between an application making a parse call for a SQL statement and Oracle
Database actually parsing the statement.
A parse call by the application associates a SQL statement with a private SQL area. After a
statement has been associated with a private SQL area, it can be run repeatedly without your
application making a parse call.

Oracle Database 11g: SQL Tuning Workshop 3 - 14

SQL Statement Parsing: Overview (continued)


A parse operation by Oracle Database allocates a shared SQL area for a SQL statement. After a
shared SQL area has been allocated for a statement, it can be run repeatedly without being
reparsed.
Both parse calls and parsing can be expensive relative to execution, so perform them as rarely as
possible.
Note: Although parsing a SQL statement validates that statement, parsing only identifies errors that
can be found before statement execution. Thus, some errors cannot be caught by parsing. For
example, errors in data conversion or errors in data (such as an attempt to enter duplicate values in a
primary key) and deadlocks are all errors or situations that can be encountered and reported only
during the execution stage.

Oracle Database 11g: SQL Tuning Workshop 3 - 15

Why Do You Need an Optimizer?


The optimizer should always return the correct result as quickly as possible.
The query optimizer tries to determine which execution plan is most efficient by considering
available access paths and by factoring in information based on statistics for the schema objects
(tables or indexes) accessed by the SQL statement.
The query optimizer performs the following steps:
1. The optimizer generates a set of potential plans for the SQL statement based on available access
paths.
2. The optimizer estimates the cost of each plan based on statistics in the data dictionary for the
data distribution and storage characteristics of the tables, and indexes accessed by the statement.
3. The optimizer compares the costs of the plans and selects the one with the lowest cost.
Note: Because of the complexity of finding the best possible execution plan for a particular query,
the optimizers goal is to find a good plan that is generally called the best cost plan.

Oracle Database 11g: SQL Tuning Workshop 3 - 16

Why Do You Need an Optimizer? (continued)


The example in the slide shows you that if statistics change, the optimizer adapts its execution plan.
In this case, statistics show that 80 percent of the employees are managers. In the hypothetical case, a
full table scan is probably a better solution than using the index.

Oracle Database 11g: SQL Tuning Workshop 3 - 17

Optimization During Hard Parse Operation


The optimizer creates the execution plan for a SQL statement.
SQL queries submitted to the system first run through the parser, which checks syntax and analyzes
semantics. The result of this phase is called a parsed representation of the statement, and is
constituted by a set of query blocks. A query block is a self-contained DML against a table. A query
block can be a top-level DML or a subquery. This parsed representation is then sent to the optimizer,
which handles three main functionalities: Transformation, estimation, and execution plan generation.
Before performing any cost calculation, the system may transform your statement into an equivalent
statement and calculate the cost of the equivalent statement. Depending on the version of Oracle
Database, there are transformations that cannot be done, some that are always done, and some that
are done, costed, and discarded.
The input to the query transformer is a parsed query, which is represented by a set of interrelated
query blocks. The main objective of the query transformer is to determine if it is advantageous to
change the structure of the query so that it enables generation of a better query plan. Several query
transformation techniques are employed by the query transformer, such as transitivity, view merging,
predicate pushing, subquery unnesting, query rewrite, star transformation, and OR expansion.

Oracle Database 11g: SQL Tuning Workshop 3 - 18

Transformer: OR Expansion Example


If a query contains a WHERE clause with multiple conditions combined with OR operators, the
optimizer transforms it into an equivalent compound query that uses the UNION ALL set operator, if
this makes the query execute more efficiently.
For example, if each condition individually makes an index access path available, the optimizer can
make the transformation. The optimizer selects an execution plan for the resulting statement that
accesses the table multiple times using the different indexes and then puts the results together. This
transformation is done if the cost estimation is better than the cost of the original statement.
In the example in the slide, it is assumed that there are indexes on both the JOB and DEPTNO
columns. Then, the optimizer might transform the original query into the equivalent transformed
query shown in the slide. When the cost-based optimizer (CBO) decides whether to make a
transformation, the optimizer compares the cost of executing the original query using a full table
scan with that of executing the resulting query.

Oracle Database 11g: SQL Tuning Workshop 3 - 19

Transformer: Subquery Unnesting Example


To unnest a query, the optimizer may choose to transform the original query into an equivalent JOIN
statement, and then optimize the JOIN statement.
The optimizer may do this transformation only if the resulting JOIN statement is guaranteed to
return exactly the same rows as the original statement. This transformation allows the optimizer to
take advantage of the join optimizer techniques.
In the example in the slide, if the CUSTNO column of the customers table is a primary key or has a
UNIQUE constraint, the optimizer can transform the complex query into the shown JOIN statement
that is guaranteed to return the same data.
If the optimizer cannot transform a complex statement into a JOIN statement, it selects execution
plans for the parent statement and the subquery as though they were separate statements. The
optimizer then executes the subquery and uses the rows returned to execute the parent query.
Note: Complex queries whose subqueries contain aggregate functions such as AVG cannot be
transformed into JOIN statements.

Oracle Database 11g: SQL Tuning Workshop 3 - 20

Transformer: View Merging Example


To merge the views query into a referencing query block in the accessing statement, the optimizer
replaces the name of the view with the names of its base tables in the query block and adds the
condition of the views querys WHERE clause to the accessing query blocks WHERE clause.
This optimization applies to select-project-join views, which contain only selections, projections, and
joins. That is, views that do not contain set operators, aggregate functions, DISTINCT, GROUP BY,
CONNECT BY, and so on.
The view in this example is of all employees who work in department 10.
The query that follows the views definition in the slide accesses the view. The query selects the IDs
greater than 7800 of employees who work in department 10.
The optimizer may transform the query into the equivalent transformed query shown in the slide that
accesses the views base table.
If there are indexes on the DEPTNO or EMPNO columns, the resulting WHERE clause makes them
available.

Oracle Database 11g: SQL Tuning Workshop 3 - 21

Transformer: Predicate Pushing Example


The optimizer can transform a query block that accesses a nonmergeable view by pushing the query
blocks predicates inside the views query.
In the example in the slide, the two_emp_tables view is the union of two employee tables. The
view is defined with a compound query that uses the UNION set operator.
The query that follows the views definition in the slide accesses the view. The query selects the IDs
and names of all employees in either table who work in department 20.
Because the view is defined as a compound query, the optimizer cannot merge the views query into
the accessing query block. Instead, the optimizer can transform the accessing statement by pushing
its predicate, the WHERE clause condition deptno = 20, into the views compound query. The
equivalent transformed query is shown in the slide.
If there is an index in the DEPTNO column of both tables, the resulting WHERE clauses make them
available.

Oracle Database 11g: SQL Tuning Workshop 3 - 22

Transformer: Transitivity Example


If two conditions in the WHERE clause involve a common column, the optimizer sometimes can infer
a third condition, using the transitivity principle. The optimizer can then use the inferred condition to
optimize the statement.
The inferred condition can make available an index access path that was not made available by the
original conditions.
This is demonstrated with the example in the slide. The WHERE clause of the original query contains
two conditions, each of which uses the EMP.DEPTNO column. Using transitivity, the optimizer
infers the following condition: dept.deptno = 20
If an index exists in the DEPT.DEPTNO column, this condition makes access paths available using
that index.
Note: The optimizer only infers conditions that relate columns to constant expressions, rather than
columns to other columns.

Oracle Database 11g: SQL Tuning Workshop 3 - 23

Cost-Based Optimizer
The combination of the estimator and plan generator code is commonly called the cost-based
optimizer (CBO).
The estimator generates three types of measures: selectivity, cardinality, and cost. These measures are
related to each other. Cardinality is derived from selectivity and often the cost depends on cardinality.
The end goal of the estimator is to estimate the overall cost of a given plan. If statistics are available,
the estimator uses these to improve the degree of accuracy when computing the measures.
The main function of the plan generator is to try out different possible plans for a given query and
pick the one that has the lowest cost. Many different plans are possible because of the various
combinations of different access paths, join methods, and join orders that can be used to access and
process data in different ways and produce the same result. The number of possible plans for a query
block is proportional to the number of join items in the FROM clause. This number rises
exponentially with the number of join items.
The optimizer uses various pieces of information to determine the best path: WHERE clause,
statistics, initialization parameters, supplied hints, and schema information.

Oracle Database 11g: SQL Tuning Workshop 3 - 24

Estimator: Selectivity
Selectivity represents a fraction of rows from a row set. The row set can be a base table, a view, or
the result of a join or a GROUP BY operator. The selectivity is tied to a query predicate, such as
last_name = 'Smith', or a combination of predicates, such as last_name = 'Smith'
AND job_type = 'Clerk'. A predicate acts as a filter that filters a certain number of rows
from a row set. Therefore, the selectivity of a predicate indicates the percentage of rows from a row
set that passes the predicate test. Selectivity lies in a value range from 0.0 to 1.0. A selectivity of 0.0
means that no rows are selected from a row set, and a selectivity of 1.0 means that all rows are
selected.
If no statistics are available, the optimizer either uses dynamic sampling or an internal default value,
depending on the value of the OPTIMIZER_DYNAMIC_SAMPLING initialization parameter. When
statistics are available, the estimator uses them to estimate selectivity. For example, for an equality
predicate (last_name = 'Smith'), selectivity is set to the reciprocal of the number n of distinct
values of LAST_NAME because the query selects rows that contain one out of n distinct values. Thus,
even distribution is assumed. If a histogram is available in the LAST_NAME column, the estimator
uses it instead of the number of distinct values. The histogram captures the distribution of different
values in a column, so it yields better selectivity estimates.
Note: It is important to have histograms in columns that contain values with large variations in the
number of duplicates (data skew).
Oracle Database 11g: SQL Tuning Workshop 3 - 25

Estimator: Cardinality
The cardinality of a particular operation in the execution plan of a query represents the estimated
number of rows retrieved by that particular operation. Most of the time, the row source can be a base
table, a view, or the result of a join or GROUP BY operator.
When costing a join operation, it is important to know the cardinality of the driving row source. With
nested loops join, for example, the driving row source defines how often the system probes the inner
row source.
Because sort costs are dependent on the size and number of rows to be sorted, cardinality figures are
also vital for sort costing.
In the example in the slide, based on assumed statistics, the optimizer knows that there are 203
different values in the DEV_NAME column, and that the total number of rows in the COURSES table
is 1018. Based on this assumption, the optimizer deduces that the selectivity of the
DEV_NAME='ANGEL' predicate is 1/203 (assuming there are no histograms), and also deduces the
cardinality of the query to be (1/203)*1018. This number is then rounded off to the nearest integer, 6.

Oracle Database 11g: SQL Tuning Workshop 3 - 26

Estimator: Cost
The cost of a statement represents the optimizers best estimate of the number of standardized
inputs/outputs (I/Os) it takes to execute that statement. Basically, the cost is a normalized value in
terms of a number of single block random reads
The standard cost metric measured by the optimizer is in terms of number of single block random
reads, so one cost unit corresponds to one single block random read. The formula shown in the slide
combines three different cost units:
Estimated time to do all the single-block random reads
Estimated time to do all the multiblock reads
Estimated time for the CPU to process the statement into one standard cost unit
The model includes CPU costing because in most cases CPU utilization is as important as I/O; often
it is the only contribution to the cost (in cases of in-memory sort, hash, predicate evaluation, and
cached I/O).
This model is straightforward for serial execution. For parallel execution, necessary adjustments are
made while computing estimates for #SRds, #MRds, and #CPUCycles.
Note: #CPUCycles includes CPU cost of query processing (pure CPU cost) and CPU cost of data
retrieval (CPU cost of the buffer cache get).

Oracle Database 11g: SQL Tuning Workshop 3 - 27

Plan Generator
The plan generator explores various plans for a query block by trying out different access paths, join
methods, and join orders. Ultimately, the plan generator delivers the best execution plan for your
statement. The slide shows you an extract of an optimizer trace file generated for the select
statement. As you can see from the trace, the plan generator has six possibilities, or six different
plans to test: Two join orders, and for each, three different join methods. It is assumed that there are
no indexes in this example.
To retrieve the rows, you can start to join the DEPARTMENTS table to the EMPLOYEES table. For
that particular join order, you can use three possible join mechanisms that the optimizer knows:
Nested Loop, Sort Merge, or Hash Join. For each possibility, you have the cost of the corresponding
plan. The best plan is the one shown at the end of the trace.
The plan generator uses an internal cutoff to reduce the number of plans it tries when finding the one
with the lowest cost. The cutoff is based on the cost of the current best plan. If the current best cost is
large, the plan generator tries harder (in other words, explores more alternate plans) to find a better
plan with lower cost. If the current best cost is small, the plan generator ends the search swiftly
because further cost improvement is not significant. The cutoff works well if the plan generator starts
with an initial join order that produces a plan with a cost close to optimal. Finding a good initial join
order is a difficult problem.
Note: Access path, join methods, and plan are discussed in more detail in the lessons titled
Oracle Database 11g: SQL Tuning Workshop 3 - 28

Optimizer Operators and Interpreting Execution Plans.

Oracle Database 11g: SQL Tuning Workshop 3 - 28

Controlling the Behavior of the Optimizer


These parameters control the optimizer behavior:
CURSOR_SHARING determines what kind of SQL statements can share the same cursors:
- FORCE: Forces statements that may differ in some literals, but are otherwise identical, to
share a cursor, unless the literals affect the meaning of the statement
- SIMILAR: Causes statements that may differ in some literals, but are otherwise identical,
to share a cursor, unless the literals affect either the meaning of the statement or the degree
to which the plan is optimized. Forcing cursor sharing among similar (but not identical)
statements can have unexpected results in some decision support system (DSS)
applications, or applications that use stored outlines.
- EXACT: Only allows statements with identical text to share the same cursor. This is the
default.
DB_FILE_MULTIBLOCK_READ_COUNT is one of the parameters you can use to minimize
I/O during table scans or index fast full scan. It specifies the maximum number of blocks read in
one I/O operation during a sequential scan. The total number of I/Os needed to perform a full
table scan or an index fast full scan depends on factors, such as the size of the segment, the
multiblock read count, and whether parallel execution is being utilized for the operation. As of
Oracle Database 10g, Release 2, the default value of this parameter is a value that corresponds to
the maximum I/O size that can be performed efficiently. This value is platform-dependent and is
1 MB for most platforms.
Oracle Database 11g: SQL Tuning Workshop 3 - 29

Controlling the Behavior of the Optimizer (continued)


Because the parameter is expressed in blocks, it automatically computes a value that is equal to
the maximum I/O size that can be performed efficiently divided by the standard block size. Note
that if the number of sessions is extremely large, the multiblock read count value is decreased to
avoid the buffer cache getting flooded with too many table scan buffers. Even though the default
value may be a large value, the optimizer does not favor large plans if you do not set this
parameter. It would do so only if you explicitly set this parameter to a large value. Basically, if
this parameter is not set explicitly (or is set is 0), the optimizer uses a default value of 8 when
costing full table scans and index fast full scans. Online transaction processing (OLTP) and
batch environments typically have values in the range of 4 to 16 for this parameter. DSS and
data warehouse environments tend to benefit most from maximizing the value of this parameter.
The optimizer is more likely to select a full table scan over an index, if the value of this
parameter is high.
PGA_AGGREGATE_TARGET specifies the target aggregate PGA memory available to all server
processes attached to the instance. Setting PGA_AGGREGATE_TARGET to a nonzero value has
the effect of automatically setting the WORKAREA_SIZE_POLICY parameter to AUTO. This
means that SQL working areas used by memory-intensive SQL operators (such as sort, group-by,
hash-join, bitmap merge, and bitmap create) are automatically sized. A nonzero value for this
parameter is the default since, unless you specify otherwise, the system sets it to 20% of the
SGA or 10 MB, whichever is greater. Setting PGA_AGGREGATE_TARGET to 0 automatically
sets the WORKAREA_SIZE_POLICY parameter to MANUAL. This means that SQL work areas
are sized using the *_AREA_SIZE parameters. The system attempts to keep the amount of
private memory below the target specified by this parameter by adapting the size of the work
areas to private memory. When increasing the value of this parameter, you indirectly increase
the memory allotted to work areas. Consequently, more memory-intensive operations are able to
run fully in memory and a less number of them work their way over to disk. When setting this
parameter, you should examine the total memory on your system that is available to the Oracle
instance and subtract the SGA. You can assign the remaining memory to
PGA_AGGREGATE_TARGET.
STAR_TRANSFORMATION_ENABLED determines whether a cost-based query transformation
is applied to star queries. This optimization is explained in the lesson titled Case Study: Star
Transformation.
The query optimizer manages the result cache mechanism depending on the settings of the
RESULT_CACHE_MODE parameter in the initialization parameter file. You can use this
parameter to determine whether or not the optimizer automatically sends the results of queries to
the result cache. The possible parameter values are MANUAL, and FORCE:
- When set to MANUAL (the default), you must specify, by using the RESULT_CACHE hint,
that a particular result is to be stored in the cache.
- When set to FORCE, all results are stored in the cache. For the FORCE setting, if the
statement contains a [NO_]RESULT_CACHE hint, the hint takes precedence over the
parameter setting.

Oracle Database 11g: SQL Tuning Workshop 3 - 30

Controlling the Behavior of the Optimizer (continued)


The memory size allocated to the result cache depends on the memory size of the SGA as well
as the memory management system. You can change the memory allocated to the result cache by
setting the RESULT_CACHE_MAX_SIZE parameter. The result cache is disabled if you set its
value to 0. The value of this parameter is rounded to the largest multiple of 32 KB that is not
greater than the specified value. If the rounded value is 0, the feature is disabled.
Use the RESULT_CACHE_MAX_RESULT parameter to specify the maximum amount of cache
memory that can be used by any single result. The default value is 5%, but you can specify any
percentage value between 1 and 100.
Use the RESULT_CACHE_REMOTE_EXPIRATION parameter to specify the time (in number
of minutes) for which a result that depends on remote database objects remains valid. The
default value is 0, which implies that results using remote objects should not be cached. Setting
this parameter to a nonzero value can produce stale answers, for example, if the remote table
used by a result is modified at the remote database.

Oracle Database 11g: SQL Tuning Workshop 3 - 31

Controlling the Behavior of the Optimizer (continued)


OPTIMIZER_INDEX_CACHING: This parameter controls the costing of an index probe in
conjunction with a nested loop or an inlist iterator. The range of values 0 to 100 for
OPTIMIZER_INDEX_CACHING indicates percentage of index blocks in the buffer cache,
which modifies the optimizers assumptions about index caching for nested loops and inlist
iterators. A value of 100 infers that 100% of the index blocks are likely to be found in the buffer
cache and the optimizer adjusts the cost of an index probe or nested loop accordingly. The
default for this parameter is 0, which results in default optimizer behavior. Use caution when
using this parameter because execution plans can change in favor of index caching.
OPTIMIZER_INDEX_COST_ADJ lets you tune optimizer behavior for access path selection to
be more or less index friendly. That is, to make the optimizer more or less prone to selecting an
index access path over a full table scan. The range of values is 1 to 10000. The default for this
parameter is 100 percent, at which the optimizer evaluates index access paths at the regular cost.
Any other value makes the optimizer evaluate the access path at that percentage of the regular
cost. For example, a setting of 50 makes the index access path look half as expensive as normal.
OPTIMIZER_FEATURES_ENABLED acts as an umbrella parameter for enabling a series of
optimizer features based on an Oracle release number.

Oracle Database 11g: SQL Tuning Workshop 3 - 32

Controlling the Behavior of the Optimizer (continued)


For example, if you upgrade your database from release 10.1 to release 11.1, but you want
to keep the release 10.1 optimizer behavior, you can do so by setting this parameter to
10.1.0. At a later time, you can try the enhancements introduced in releases up to and
including release 11.1 by setting the parameter to 11.1.0.6. However, it is not
recommended to explicitly set the OPTIMIZER_FEATURES_ENABLE parameter to an
earlier release. To avoid possible SQL performance regression that may result from
execution plan changes, consider using SQL plan management instead.
OPTIMIZER_MODE establishes the default behavior for selecting an optimization approach for
either the instance or your session. The possible values are:
- ALL_ROWS: The optimizer uses a cost-based approach for all SQL statements in the session
regardless of the presence of statistics and optimizes with a goal of best throughput
(minimum resource use to complete the entire statement). This is the default value.
- FIRST_ROWS_n: The optimizer uses a cost-based approach, regardless of the presence of
statistics, and optimizes with a goal of best response time to return the first n number of
rows; n can equal 1, 10, 100, or 1000.
- FIRST_ROWS: The optimizer uses a mix of cost and heuristics to find a best plan for fast
delivery of the first few rows. Using heuristics sometimes leads the query optimizer to
generate a plan with a cost that is significantly larger than the cost of a plan without
applying the heuristic. FIRST_ROWS is available for backward compatibility and plan
stability; use FIRST_ROWS_n instead.
OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES enables or disables the automatic
recognition of repeatable SQL statements, as well as the generation of SQL plan baselines for
such statements.
OPTIMIZER_USE_SQL_PLAN_BASELINES enables or disables the use of SQL plan
baselines stored in SQL Management Base. When enabled, the optimizer looks for a SQL plan
baseline for the SQL statement being compiled. If one is found in SQL Management Base, the
optimizer costs each of the baseline plans and picks one with the lowest cost.
OPTIMIZER_DYNAMIC_SAMPLING controls the level of dynamic sampling performed by the
optimizer. If OPTIMIZER_FEATURES_ENABLE is set to:
- 10.0.0 or later, the default value is 2
- 9.2.0, the default value is 1
- 9.0.1 or earlier, the default value is 0
OPTIMIZER_USE_INVISIBLE_INDEXES enables or disables the use of invisible indexes.
OPTIMIZER_USE_PENDING_STATISTICS specifies whether or not the optimizer uses
pending statistics when compiling SQL statements.
Note: Invisible indexes, pending statistics, and dynamic sampling are discussed later in this course.

Oracle Database 11g: SQL Tuning Workshop 3 - 33

Optimizer Features and Oracle Database Releases


OPTIMIZER_FEATURES_ENABLED acts as an umbrella parameter for enabling a series of
optimizer features based on an Oracle release number. The table in the slide describes some of the
optimizer features that are enabled depending on the value specified for the
OPTIMIZER_FEATURES_ENABLED parameter.

Oracle Database 11g: SQL Tuning Workshop 3 - 34

Oracle Database 11g: SQL Tuning Workshop 3 - 35

Oracle Database 11g: SQL Tuning Workshop 3 - 36

Objectives
This lesson helps you understand the execution plans.

Oracle Database 11g: SQL Tuning Workshop 4 - 2

Row Source Operations


A row source is an iterative control structure that processes a set of rows in an iterated manner and
produces a row set.
You can classify row sources as follows:
Unary operations: Operations that act on only one input, such as an access path
Binary operations: Operations that act on two inputs, such as joins
N-ary operations: Operations that act on several inputs, such as a relational operator
Access paths are ways in which data is retrieved from the database. In general, index access paths
should be used for statements that retrieve a small subset of table rows, while full scans are more
efficient when accessing a large portion of the table. Online transaction processing (OLTP)
applications, which consist of short-running SQL statements with high selectivity, are often
characterized by the use of index access paths. Decision support systems (DSS), on the other hand,
tend to use partitioned tables and perform full scans of the relevant partitions.

Oracle Database 11g: SQL Tuning Workshop 4 - 3

Main Structures and Access Paths


Any row can be located and retrieved with one of the methods mentioned in the slide.
In general, index access paths should be used for statements that retrieve a small subset of table rows,
while full scans are more efficient when accessing a large portion of the table. To decide on the
alternative, the optimizer gives each alternative (execution plan) a cost. The one with the lower cost
is elected.
There are special types of table access paths including clusters, index-organized tables, and
partitions, which have not been mentioned in the slide.
Clusters are an optional method of storing table data. A cluster is a group of tables that share the
same data blocks because they share common columns and are often used together. For example, the
EMP and DEPT table share the DEPTNO column. When you cluster the EMP and DEPT tables, Oracle
physically stores all rows for each department from both the EMP and DEPT tables in the same data
blocks.
Hash clusters are single-table clusters in which rows with the same hash-key values are stored
together. Oracle uses a mathematical hash function to select the location of a row within the cluster.
All rows with the same key value are stored together on disk.
The special types of access paths are discussed later in this course.

Oracle Database 11g: SQL Tuning Workshop 4 - 4

Full Table Scan


A full table scan sequentially reads all rows from a table and filters out those that do not meet the
selection criteria. During a full table scan, all formatted blocks in the table that are under the highwater mark are scanned even if all the rows have been deleted from the table. Each block is read only
once. The high-water mark indicates the amount of used space, or space that was formatted to receive
data. Each row is examined to determine whether it satisfies the statements WHERE clause using the
applicable filter conditions specified in the query.
You can see the filter conditions in the Predicate Information section of the explain plan. The filter
to be applied returns only rows where EMP.ENAME='King'.
Because a full table scan reads all the formatted blocks in a table, it reads blocks that are physically
adjacent to each other. This means that performance benefits can be reaped by utilizing input/output
(I/O) calls that read multiple blocks at the same time. The size of the read call can range from a
single block to any number of blocks up to the DB_FILE_MULTIBLOCK_READ_COUNT init
parameter.
Note: In Oracle 6, full table scans flooded the buffer cache because there was no difference in the
way blocks were handled between full table scans and reads by the index. Since Oracle V7, blocks
read by full table scans do not flood the buffer cache because the space they are allowed to occupy is
restricted to a small amount. Currently, hash joins and parallel query rely heavily on full table scans
for efficiency.
Oracle Database 11g: SQL Tuning Workshop 4 - 5

Full Table Scans: Use Cases


The optimizer uses a full table scan in any of the following cases:
Lack of index: If the query is unable to use any existing indexes, it uses a full table scan (unless
a ROWID filter or a cluster access path is available). For example, if there is a function used on
the indexed column in the query, the optimizer cannot use the index and instead uses a full table
scan. If you need to use the index for case-independent searches, either do not permit mixedcase data in the search columns or create a function-based index, such as
UPPER(last_name) on the search column.
Large amount of data (low selectivity): If the optimizer thinks that the query accesses enough
blocks in the table, it may use a full table scan even though indexes might be available.
Small table: If a table contains less than DB_FILE_MULTIBLOCK_READ_COUNT blocks
under the high-water mark, a full table scan might be cheaper than an index range scan,
regardless of the fraction of tables being accessed or indexes present.
High degree of parallelism: A high degree of parallelism for a table skews the optimizer
towards full table scans over range scans. Examine the DEGREE column in ALL_TABLES for
the table to determine the degree of parallelism.
Full table scan hints: Use the FULL(table alias) hint to instruct the optimizer to use a
full table scan.

Oracle Database 11g: SQL Tuning Workshop 4 - 6

ROWID Scan
The rowid of a row specifies the data file and data block containing the row and the location of the
row in that block. Locating a row by specifying its rowid is the fastest way to retrieve a single row
because the exact location of the row in the database is specified.
To access a table by rowid, the system first obtains the rowids of the selected rows, either from the
statements WHERE clause or through an index scan of one or more of the tables indexes. The system
then locates each selected row in the table based on its rowid.
Mostly, the optimizer uses rowids after retrieving them from an index (See the Index Scans
slides.). The table access might be required for columns in the statement that are not present in the
index. A table access by rowid does not need to follow every index scan. If the index contains all the
columns needed for the statement, table access by rowid might not occur.
Rowids are the systems internal representation of where data is stored. Accessing data based on
position is not recommended because rows can move around due to row migration and chaining, and
also after export and import.
Note: Due to row migration, a rowid can sometimes point to an address different from the actual row
location, resulting in more than one block being accessed to locate a row. For example, an update to a
row may cause the row to be placed in another block with a pointer in the original block. The rowid,
however, still has only the address of the original block.

Oracle Database 11g: SQL Tuning Workshop 4 - 7

Sample Table Scans


A sample table scan retrieves a random sample of data from a simple table or a complex SELECT
statement, such as a statement involving joins and views. This access path is used when a statements
FROM clause includes the SAMPLE clause or the SAMPLE BLOCK clause. To perform a sample table
scan when sampling by rows with the SAMPLE clause, the system reads a specified percentage of
rows in the table. To perform a sample table scan when sampling by blocks with the SAMPLE
BLOCK clause, the system reads a specified percentage of table blocks.
SAMPLE option: To perform a sample table scan when sampling by rows, the system reads a
specified percentage of rows in the table and examines each of these rows to determine whether
it satisfies the statements WHERE clause.
SAMPLE BLOCK option: To perform a sample table scan when sampling by blocks, the system
reads a specified percentage of the tables blocks and examines each row in the sampled blocks
to determine whether it satisfies the statements WHERE clause.
The sample percent is a number specifying the percentage of the total row or block count to be
included in the sample. The sample value must be in the [0.000001 , 99.999999] range.
This percentage indicates the probability of each row, or each cluster of rows in the case of block
sampling, being selected as part of the sample. It does not mean that the database retrieves exactly
sample_percent of the rows of table.

Oracle Database 11g: SQL Tuning Workshop 4 - 8

Sample Table Scans (continued)


SEED seed_value: Specify this clause to instruct the database to attempt to return the same
sample from one execution to the next. seed_value must be an integer between 0 and
4294967295. If you omit this clause, the resulting sample changes from one execution to the
next.
In row sampling, more blocks need to be accessed given a particular sample size, but the results are
usually more accurate. Block samples are less costly, but may be inaccurate; more so with smaller
samples.
Note: Block sampling is possible only during full table scans or index fast full scans. If a more
efficient execution path exists, Oracle Database does not perform block sampling. If you want to
guarantee block sampling for a particular table or index, use the FULL or INDEX_FFS hint.

Oracle Database 11g: SQL Tuning Workshop 4 - 9

Indexes: Overview
An index is an optional database object that is logically and physically independent of the table data.
Being independent structures, they require storage space. Just as the index of a book helps you locate
information fast, an Oracle Database index provides a faster access path to table data. The Oracle
Database may use an index to access data that is required by a SQL statement, or it may use indexes
to enforce integrity constraints. The system automatically maintains indexes when the related data
changes. You can create and drop indexes at any time. If you drop an index, all applications continue
to work. However, access to previously indexed data might be slower. Indexes can be unique or
nonunique.
A composite index, also called a concatenated index, is an index that you create on multiple columns
(up to 32) in a table. Columns in a composite index can appear in any order and need not be adjacent
in the table.
For standard indexes, the database uses B*-tree indexes that are balanced to equalize access times.
B*-tree indexes can be normal, reverse key, descending, or function based.
B*-tree indexes: They are by far the most common indexes. Similar in construct to a binary
tree, B*-tree indexes provide fast access, by key, to an individual row or range of rows, normally
requiring few reads to find the correct row. However, the B in B*-tree does not stand for
binary, but rather for balanced.

Oracle Database 11g: SQL Tuning Workshop 4 - 10

Indexes: Overview (continued)


Descending indexes: Descending indexes allow for data to be sorted from big to small
(descending) instead of from small to big (ascending) in the index structure.
Reverse key indexes: These are B*-tree indexes whereby the bytes in the key are reversed.
Reverse key indexes can be used to obtain a more even distribution of index entries throughout
an index that is populated with increasing values. For example, if you use a sequence to generate
a primary key, the sequence generate values, such as 987500, 987501, 987502, and so on. With a
reverse key index, the database logically indexes 005789, 105789, 205789, and so on, instead of
987500, 987501, and 987502. Because these reverse keys are now likely to be placed in different
locations this can reduce contention for particular blocks that may otherwise be targets for
contention. However, only equality predicates can benefit from these indexes.
Index key compression: The basic concept behind a compressed key index is that every entry is
broken into twoa prefix and a suffix component. The prefix is built on the leading columns of
the concatenated index and has many repeating values. The suffix is built on the trailing
columns in the index key and is the unique component of the index entry within the prefix. This
is not compression in the same manner that ZIP files are compressed; rather, this is an optional
compression that removes redundancies from concatenated (multicolumn) indexes.
Function-based indexes: These are B*-tree or bitmap indexes that store the computed result of
a function on a rows column or columns, and not the column data itself. You can consider them
as indexes on a virtual (derived or hidden) column. In other words, it is a column that is not
physically stored in the table. You can gather statistics on this virtual column.
Index-organized tables: These are tables stored in a B*-tree structure. While rows of data in a
heap organized table are stored in an unorganized fashion (data goes wherever there is available
space), data in an IOT is stored and sorted by a primary key. IOTs behave like regular tables as
far as your application is concerned.
Bitmap indexes: In a normal B*-tree, there is a one-to-one relationship between an index entry
and a row, that is, an index entry points to a row. With bitmap indexes, a single index entry uses
a bitmap to point to many rows simultaneously. They are appropriate for repetitive data (data
with few distinct values relative to the total number of rows in the table) that is mostly readonly. Bitmap indexes should never be considered in an OLTP database for concurrency-related
issues.
Bitmap join indexes: A bitmap join index is a bitmap index for the join of two or more tables. A
bitmap join index can be used to avoid actual joins of tables, or to greatly reduce the volume of
data that must be joined, by performing restrictions in advance. Queries using bitmap join
indexes can be sped up using bit-wise operations.
Application domain indexes: These are indexes you build with packages and store yourself,
either in the database or even outside the database. You tell the optimizer how selective your
index is and how costly it is to execute, and the optimizer decides whether or not to use your
index based on that information.

Oracle Database 11g: SQL Tuning Workshop 4 - 11

Normal B*-tree Indexes


Each B*-tree index has a root block as a starting point. Depending on the number of entries, there are
multiple branch blocks that can have multiple leaf blocks. The leaf blocks contain all values of the
index plus ROWIDs that point to the rows in the associated data segment.
Previous and next block pointers connect the leaf blocks so that they can be traversed from left to
right (and vice versa).
Indexes are always balanced, and they grow from the top down. In certain situations, the balancing
algorithm can cause the B*-tree height to increase unnecessarily. It is possible to reorganize indexes.
This is done by the ALTER INDEX REBUILD | COALESCE command.
The internal structure of a B*-tree index allows rapid access to the indexed values. The system can
directly access rows after it has retrieved the address (the ROWID) from the index leaf blocks.
Note: The maximum size of a single index entry is approximately one-half of the data block size.

Oracle Database 11g: SQL Tuning Workshop 4 - 12

Index Scans
An index scan can be one of the following types:
A row is retrieved by traversing the index, using the indexed column values specified by the
statements WHERE clause. An index scan retrieves data from an index based on the value of one or
more columns in the index. To perform an index scan, the system searches the index for the indexed
column values accessed by the statement. If the statement accesses only columns of the index, the
system reads the indexed column values directly from the index, rather than from the table.
The index contains not only the indexed value, but also the rowids of rows in the table that have the
value. Therefore, if the statement accesses other columns in addition to the indexed columns, the
system can find the rows in the table by using either a table access by rowid or a cluster scan.
Note: The graphic shows a case where four rows are retrieved from the table using their rowids
obtained by the index scan.

Oracle Database 11g: SQL Tuning Workshop 4 - 13

Index Unique Scan


An index unique scan returns, at most, a single ROWID. The system performs a unique scan if a
statement contains a UNIQUE or a PRIMARY KEY constraint that guarantees that only a single row
is accessed. This access path is used when all the columns of a unique (B*-tree) index are specified
with equality conditions.
Key values and ROWIDs are obtained from the index, and table rows are obtained using ROWIDs.
You can look for access conditions in the Predicate Information section of the execution plan (The
execution plan is dealt with in detail in the lesson titled Interpreting Execution Plans.). Here the
system accesses only matching rows for which EMPNO=9999.
Note: Filter conditions filter rows after the fetch operation and output the filtered rows.

Oracle Database 11g: SQL Tuning Workshop 4 - 14

Index Range Scan


An index range scan is a common operation for accessing selective data. It can be bounded (on both
sides) or unbounded (on one or both sides). Data is returned in the ascending order of index columns.
Multiple rows with identical values are sorted in the ascending order by ROWID.
The optimizer uses a range scan when it finds one or more leading columns of an index specified in
conditions (the WHERE clause), such as col1 = :b1, col1 < :b1, col1 > :b1, and any
combination of the preceding conditions.
Wildcard searches (col1 like '%ASD') should not be in a leading position, as this does not
result in a range scan.
Range scans can use unique or nonunique indexes. Range scans can avoid sorting when index
columns constitute the ORDER BY/GROUP BY clause and the indexed columns are NOT NULL as
otherwise they are not considered.
An index range scan descending is identical to an index range scan, except that the data is returned in
the descending order. The optimizer uses index range scan descending when an order by descending
clause can be satisfied by an index.
In the example in the slide, using index I_DEPTNO, the system accesses rows for which
EMP.DEPTNO=10. It gets their ROWIDs, fetches other columns from the EMP table, and finally,
applies the EMP.SAL >1000 filter from these fetched rows to output the final result.

Oracle Database 11g: SQL Tuning Workshop 4 - 15

Index Range Scan: Descending


In addition to index range scans in ascending order, which are described in the previous slide, the
system is also able to scan indexes in the reverse order as illustrated by the graphic in the slide.
The example retrieves rows from the EMP table by descending order on the DEPTNO column. You
can see the DESCENDING operation row source for ID 2 in the execution plan that materialized this
type of index scans.
Note: By default an index range scan is done in the ascending order.

Oracle Database 11g: SQL Tuning Workshop 4 - 16

Descending Index Range Scan


A descending index range scan is identical to an index range scan, except that the data is returned in
descending order. Descending indexes allow for data to be sorted from big to small (descending)
instead of small to big (ascending) in the index structure. Usually, this scan is used when ordering
data in a descending order to return the most recent data first, or when seeking a value less than a
specified value as in the example in the slide.
The optimizer uses descending index range scan when an order by descending clause can be satisfied
by a descending index.
The INDEX_DESC(table_alias index_name) hint can be used to force this access path if
possible.
Note: The system treats descending indexes as function-based indexes. The columns marked DESC
are stored in a special descending order in the index structure that is reversed again using the
SYS_OP_UNDESCEND function.

Oracle Database 11g: SQL Tuning Workshop 4 - 17

Index Range Scan: Function-Based


A function-based index can be stored as B*-tree or bitmap structures. These indexes include columns
that are either transformed by a function, such as the UPPER function, or included in an expression,
such as col1 + col2. With a function-based index, you can store computation-intensive
expressions in the index.
Defining a function-based index on the transformed column or expression allows that data to be
returned using the index when that function or expression is used in a WHERE clause or an ORDER
BY clause. This allows the system to bypass computing the value of the expression when processing
SELECT and DELETE statements. Therefore, a function-based index can be beneficial when
frequently-executed SQL statements include transformed columns, or columns in expressions, in a
WHERE or ORDER BY clause.
For example, function-based indexes defined with the UPPER(column_name) or
LOWER(column_name) keywords allow non-case-sensitive searches, such as shown in the slide.

Oracle Database 11g: SQL Tuning Workshop 4 - 18

Index Full Scan


A full scan is available if a predicate references one of the columns in the index. The predicate does
not need to be an index driver (leading column). A full scan is also available when there is no
predicate, if both the conditions are met:
All the columns in the table referenced in the query are included in the index.
At least one of the index columns is not null.
A full scan can be used to eliminate a sort operation because the data is ordered by the index key.
Note: An index full scan reads index using single-block input/output (I/O) (unlike a fast full index
scan).

Oracle Database 11g: SQL Tuning Workshop 4 - 19

Index Fast Full Scan


Index fast full scans are an alternative to full table scans when the index contains all the columns that
are needed for the query and at least one column in the index key has a NOT NULL constraint. A fast
full scan accesses the data in the index itself without accessing the table.
It cannot be used to eliminate a sort operation because the data is not ordered by the index key. It can
be used for the min/avg/sum aggregate functions. In this case, the optimizer must know that all
table rows are represented in the index; at least one NOT NULL column.
This operation reads the entire index using multiblock reads (unlike a full index scan). Fast full index
scans cannot be performed against bitmap indexes. A fast full scan is faster than a normal full index
scan because it can use multiblock I/O just as a table scan.
You can specify fast full index scans with the OPTIMIZER_FEATURES_ENABLE initialization
parameter or the INDEX_FFS hint as shown in the slide example.
Note: Index fast full scans are used against an index when it is rebuilt offline.

Oracle Database 11g: SQL Tuning Workshop 4 - 20

Index Skip Scan


Index skip scans improve index scans by skipping blocks that could never contain keys matching the
filter column values. Scanning index blocks is often faster than scanning table data blocks. Skip
scanning can happen when the initial (leading) column of the composite index is not specified in a
query. Suppose that there is a concatenated index on the GENDER and AGE columns in the
EMPLOYEES table. This example illustrates how skip scanning is processed to answer the query in
the slide.
The system starts from the root of the index [R] and proceeds to the left branch block [B1]. From
there, the system identifies a first entry to be F16, goes to the left leaf [L1], and starts to scan it
because it could contain A25 (that is, where the gender is before F in the alphabet). The server
identifies that this is not possible because the first entry is F10. It is thus not possible to find an entry
such as A25 in this leaf, so it can be skipped.
Backtracking to the first branch block [B1], the server identifies that the next subtree (F16) does not
need to be scanned because the next entry in [B1] is F20. Because the server is certain that it is not
possible to find a 25 between F16 and F20, the second leaf block [L2] can be skipped.
Returning to [B1], the server finds that the next two entries have a common prefix of F2. This
identifies possible subtrees to scan. The system knows that these subtrees are ordered by age.

Oracle Database 11g: SQL Tuning Workshop 4 - 21

Index Skip Scan (continued)


So the third and fourth leaf blocks [L3L4] are scanned and some values are retrieved. By looking at
the fourth entry in the first branch block [B1], the system determines that it is no longer possible to
find an F2x entry. Thus, it is not necessary to scan that subtree [L5].
The same process continues with the right part of this index. Note that out of a total of 10 leaf blocks,
only five are scanned.

Oracle Database 11g: SQL Tuning Workshop 4 - 22

Index Skip Scan: Example


The example in the slide finds employees who have salary less than 1500 using an index skip scan.
It is assumed that there is a concatenated index on the DEPTNO and SAL columns.
As you can see, the query does not have a predicate on the DEPTNO leading column. This leading
column only has some discrete values, that is, 10, 20 and 30.
Skip scanning lets a composite index be split logically into smaller subindexes. The number of
logical subindexes is determined by the number of distinct values in the initial column.
The system pretends that the index is really three little index structures hidden inside one big one. In
the example, it is three index structures:
where deptno = 10
where deptno = 20
where deptno = 30
The output is ordered by DEPTNO.
Note: Skip scanning is advantageous if there are few distinct values in the leading column of the
composite index and many distinct values in the nonleading key of the index.

Oracle Database 11g: SQL Tuning Workshop 4 - 23

Index Join Scan


An index join is a hash join of several indexes that together contain all the table columns that are
referenced in the query. If an index join is used, no table access is needed because all the relevant
column values can be retrieved from the indexes. An index join cannot be used to eliminate a sort
operation.
The index join is not a real join operation (note that the example is a single table query), but is built
using index accesses followed by a join operation on rowid. The example in the slide assumes that
you have two separate indexes on the ENAME and SAL columns of the EMP table.
Note: You can specify an index join with the INDEX_JOIN hint as shown in the example.

Oracle Database 11g: SQL Tuning Workshop 4 - 24

The AND-EQUAL Operation


The AND-EQUAL operation allows the system to merge the scans on several single-column indexes
before accessing the table.
This is illustrated using the example in the slide.
Basically, this operation works when there are two (or more, up to five in theory) equality conditions
in the WHERE clause, and each column is indexed with a single column index. The idea is then to
search indexes for matching ROWIDs. If a ROWID occurs in all relevant indexes, it satisfies the
conditions, and the matching row from the table can be retrieved.
Note: To force the AND-EQUAL operation, you used the AND_EQUAL hint.

Oracle Database 11g: SQL Tuning Workshop 4 - 25

B*-tree Indexes and Nulls


It is a common mistake to forget about nulls when dealing with B*-tree indexes. Single-column
B*-tree indexes do not store null values and so indexes on nullable columns cannot be used to drive
queries unless there is something that eliminates the null values from the query.
In the slide example, you create a table containing a nullable column called COL1, and COL2, which
cannot have null values. One index is built on top of each column.
The first query, retrieves all COL1 values. Because COL1 is nullable, the index cannot be used
without a predicate. Hinting the index on COL1 (nullind1) to force index utilization makes no
difference because COL1 is nullable. Because you only search for COL1 values, there is no need to
read the table itself.
However, with the second query, the effect of the predicate against COL1 is to eliminate nulls from
the data returned from the column. This allows the index to be used.
The third query can directly use the index because the corresponding column is declared NOT NULL
at table-creation time.
Note: The index could also be used by forcing the column to return only NOT NULL values using
the COL1 IS NOT NULL predicate.

Oracle Database 11g: SQL Tuning Workshop 4 - 26

Using Indexes: Considering Nullable Columns


Some queries look as if they should use an index to compute a simple count of rows in the table. This
is typically more efficient that scanning the table. But the index to be used must not be built on a
column that can contain null values. Single-column B*-tree indexes never store null values, so the
rows are not represented in the index, and thus, do not contribute to the COUNT being computed, for
example.
In the example in the slide, there is a unique index on the SSN column of the PERSON table. The
SSN column is defined as allowing null values, and creating a unique index on it does nothing to
change that. This index is not used when executing the count query in the slide. Any rows with null
for SSN are not represented in the index, so the count across the index is not necessarily accurate.
This is one reason why it is better to create a primary key rather than a unique index. A primary key
column cannot contain null values. In the slide, after the unique index is dropped in the place of
designating a primary key, the index is used to compute the row count.
Note: The PRIMARY KEY constraints combine a NOT NULL constraint and a unique constraint in a
single declaration.

Oracle Database 11g: SQL Tuning Workshop 4 - 27

Index-Organized Tables
An index-organized table (IOT) is a table physically stored in a concatenated index structure. The
key values (for the table and the B*-tree index) are stored in the same segment. An IOT contains:
Primary key values
Other (nonkey) column values for the row
The B*-tree structure, which is based on the primary key of the table, is organized in the same way as
an index. The leaf blocks in this structure contain the rows instead of the ROWIDs. This means that
the rows in the IOT are always maintained in the order of the primary key.
You can create additional indexes on IOTs. The primary key can be a composite key. Because large
rows of an IOT can destroy the dense and efficient storage of the B*-tree structure, you can store part
of the row in another segment, which is called an overflow area.
Index-organized tables provide fast key-based access to table data for queries involving exact match
and range searches. Changes to the table data result only in updating the index structure. Also,
storage requirements are reduced because key columns are not duplicated in the table and index. The
remaining non-key columns are stored in the index structure. IOTs are particularly useful when you
use applications that must retrieve data based on a primary key and have only a few, relatively short
nonkey columns.
Note: The descriptions mentioned here are correct if no overflow segments exist. Overflow segments
should be used with long rows.
Oracle Database 11g: SQL Tuning Workshop 4 - 28

Index-Organized Table Scans


In the example in the slide, it is assumed that IOEMP is an index-organized table defined with the
following statement:
create table iotemp
( empno number(4) primary key, ename varchar2(10) not null,
job varchar2(9), mgr number(4), hiredate date,
sal number(7,2) not null, comm number(7,2), deptno number(2))
organization index;

Index-organized tables are just like indexes. They use the same access paths that you saw for normal
indexes.
The major difference from a heap-organized table is that there is no need to access both an index and
a table to retrieve indexed data.
Note: SYS_IOT_TOP_75664 is the system-generated name of the segment used to store the IOT
structure. You can retrieve the link between the table name and the segment from USER_INDEXES
with these columns: INDEX_NAME, INDEX_TYPE, TABLE_NAME.

Oracle Database 11g: SQL Tuning Workshop 4 - 29

Bitmap Indexes
In a B*-tree, there is a one-to-one relationship between an index entry and a row; an index entry
points to a row. A bitmap index is organized as a B*-tree index but, with bitmap indexes, a single
index entry uses a bitmap to point to many rows simultaneously. If a bitmap index involves more
than one column, there is a bitmap for every possible combination. Each bitmap header stores start
and end ROWIDs. Based on these values, the system uses an internal algorithm to map bitmaps onto
ROWIDs. This is possible because the system knows the maximum possible number of rows that can
be stored in a system block. Each position in a bitmap maps to a potential row in the table even if
that row does not exist. The contents of that position in the bitmap for a particular value indicate
whether that row has that value in the bitmap columns. The value stored is 1 if the row values match
the bitmap condition; otherwise it is 0. Bitmap indexes are widely used in data warehousing
environments. These environments typically have large amounts of data and ad hoc queries, but no
concurrent data manipulation language (DML) transactions because when locking a bitmap, you lock
many rows in the table at the same time. For such applications, bitmap indexing provides reduced
response time for large classes of ad hoc queries, reduced storage requirements compared to other
indexing techniques, dramatic performance gains even on hardware with a relatively small number of
CPUs or a small amount of memory, and efficient maintenance during parallel DML and loads.
Note: Unlike most other types of indexes, bitmap indexes include rows that have NULL values.
Indexing of nulls can be useful for some types of SQL statements, such as queries with the aggregate
function COUNT. The IS NOT NULL predicate can also benefit from bitmap indexes. Although
Oracle Database 11g: SQL Tuning Workshop 4 - 30

bitmaps are compressed internally, they are split in multiple leaves if the number of rows
increases.

Oracle Database 11g: SQL Tuning Workshop 4 - 30

Bitmap Index Access: Examples


The examples in the slide illustrate two possible access paths for bitmap indexesBITMAP INDEX
SINGLE VALUE and BITMAP INDEX RANGE SCANdepending on the type of predicate you
use in the queries.
The first query scans the bitmap for country FR for positions containing a 1. Positions with a 1
are converted into ROWIDs and have their corresponding rows returned for the query.
In some cases (such as a query counting the number of rows with COUNTRY FR), the query might
simply use the bitmap itself and count the number of 1s (not needing the actual rows). This is
illustrated in the following example:
SELECT count(*) FROM PERF_TEAM WHERE country>'FR';
----------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
----------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
3 |
2
(0)|
|
1 | SORT AGGREGATE
|
|
1 |
3 |
|
|
2 |
BITMAP CONVERSION COUNT |
|
1 |
3 |
2
(0)|
|
3 |
BITMAP INDEX RANGE SCAN| IX_B2 |
|
|
|
----------------------------------------------------------------------------Predicate: 3 - access("COUNTRY">'FR') filter("COUNTRY">'FR')

Oracle Database 11g: SQL Tuning Workshop 4 - 31

Combining Bitmap Indexes: Examples


Bitmap indexes are the most effective for queries that contain multiple conditions in the WHERE
clause. Rows that satisfy some, but not all, conditions are filtered out before the table itself is
accessed. This improves response time, often dramatically. As the bitmaps from bitmap indexes can
be combined quickly, it is usually best to use single-column bitmap indexes.
Due to fast bit-and, bit-minus, and bit-or operations, bitmap indexes are efficient when:
Using IN (value_list)
Predicates are combined with AND or OR

Oracle Database 11g: SQL Tuning Workshop 4 - 32

Combining Bitmap Index Access Paths


Bitmap indexes can be used efficiently when a query combines several possible values for a column
or when two separately-indexed columns are used.
In some cases, a WHERE clause might reference several separately indexed columns as in the
examples shown in the slide.
If both the COUNTRY and GENDER columns have bitmap indexes, a bit-and operation on the two
bitmaps quickly locates the rows that you look for. The more complex the compound WHERE clauses
become, the more benefit you get from bitmap indexing.

Oracle Database 11g: SQL Tuning Workshop 4 - 33

Bitmap Operations
The slide summarizes all the possible bitmap operations. The following operations have not been
explained so far:
BITMAP CONVERSION FROM ROWID: B*-tree index converted by the optimizer into
BITMAP (cost is lower than other methods) to make these efficient bitmaps comparison
operations available. After the bitmap comparison has been done, the resultant bitmap is
converted back into ROWIDs (BITMAP CONVERSION TO ROWIDS) to perform the data
lookup.
BITMAP MERGE merges several bitmaps resulting from a range scan into one bitmap.
BITMAP MINUS is a dual operator that takes the second bitmap operation and negates it by
changing ones to zeros, and zeros to ones. The bitmap minus operation can then be performed as
a BITMAP AND operation using this negated bitmap. This would typically be the case with the
following combination of predicates: C1=2 and C2<>6.
BITMAP KEY ITERATION takes each row from a table row source and finds the
corresponding bitmap from a bitmap index. This set of bitmaps is then merged into one bitmap
in a BITMAP MERGE operation.

Oracle Database 11g: SQL Tuning Workshop 4 - 34

Bitmap Join Index


In addition to a bitmap index on a single table, you can create a bitmap join index. A bitmap join
index is a bitmap index for the join of two or more tables. A bitmap join index is a space-efficient
way of reducing the volume of data that must be joined by performing restrictions in advance.
Here, you create a new bitmap join index named cust_sales_bji on the SALES table. The key
of this index is the CUST_CITY column of the CUSTOMERS table. This example assumes that there
is an enforced primary key constraint on CUSTOMERS to ensure that what is stored in the bitmap
reflects the reality of the data in the tables. The CUST_ID column is the primary key of
CUSTOMERS, but is also a foreign key inside SALES, although not required.
The FROM and WHERE clause in the CREATE statement allow the system to make the link between
the two tables. They represent the natural join condition between the two tables. The middle part of
the graphic shows you a theoretical implementation of this bitmap join index. Each entry or key in
the index represents a possible city found in the CUSTOMERS table. A bitmap is then associated to
one particular key. Each bit in a bitmap corresponds to one row in the SALES table. In the first key in
the slide (Rognes), you see that the first row in the SALES table corresponds to a product sold to a
Rognes customer, while the second bit is not a product sold to a Rognes customer. By storing the
result of a join, the join can be avoided completely for SQL statements using a bitmap join index.
Note: Bitmap join indexes are much more efficient in storage than materialized join views. For a row
source example, see the lesson titled Case Study: Star Transformation.
Oracle Database 11g: SQL Tuning Workshop 4 - 35

Composite Indexes
A composite index is also referred to as a concatenated index because it concatenates column values
together to form the index key value. In the illustration in the slide, the MAKE and MODEL columns
are concatenated together to form the index. It is not required that the columns in the index are
adjacent. And, you can include up to 32 columns in the index, unless it is a bitmap composite index,
in which case the limit is 30.
Composite indexes can provide additional advantages over single-column indexes:
Improved selectivity: Sometimes two or more columns or expressions, each with poor
selectivity, can be combined to form a composite index with higher selectivity.
Reduced I/O: If all columns selected by a query are in a composite index, the system can return
these values from the index without accessing the table.
A composite index is mainly useful when you often have a WHERE clause that references all, or the
leading portion of the columns in the index. If some keys are used in WHERE clauses more
frequently, and you decided to create a composite index, be sure to create the index so that the more
frequently selected keys constitute a leading portion for allowing the statements that use only these
keys to use the index.
Note: It is also possible for the optimizer to use a concatenated index even though your query does
not reference a leading part of that index. This is possible since index skip scans and fast full scans
were implemented.
Oracle Database 11g: SQL Tuning Workshop 4 - 36

Invisible Index: Overview


An invisible index is an index that is ignored by the optimizer unless you explicitly set the
OPTIMIZER_USE_INVISIBLE_INDEXES initialization parameter to TRUE at the session or
system level. The default value for this parameter is FALSE.
Making an index invisible is an alternative to making it unusable or dropping it. Using invisible
indexes, you can perform the following actions:
Test the removal of an index before dropping it.
Use temporary index structures for certain operations or modules of an application without
affecting the overall application.
Unlike unusable indexes, an invisible index is maintained during DML statements.

Oracle Database 11g: SQL Tuning Workshop 4 - 37

Invisible Indexes: Examples


When an index is invisible, the optimizer selects plans that do not use the index. If there is no
discernible drop in performance, you can drop the index. You can also create an index initially as
invisible, perform testing, and then determine whether to make the index visible.
You can query the VISIBILITY column of the *_INDEXES data dictionary views to determine
whether the index is VISIBLE or INVISIBLE.
Note: For all the statements given in the slide, it is assumed that
OPTIMIZER_USE_INVISIBLE_INDEXES is set to FALSE.

Oracle Database 11g: SQL Tuning Workshop 4 - 38

Guidelines for Managing Indexes


Create indexes after inserting table data: Data is often inserted or loaded into a table using
either the SQL*Loader or an import utility. It is more efficient to create an index for a table after
inserting or loading the data.
Index the correct tables and columns: Use the following guidelines for determining when to
create an index:
- Create an index if you frequently want to retrieve less than 15% of the rows in a large table.
- To improve performance on joins of multiple tables, index the columns used for joins.
- Small tables do not require indexes.
Columns suitable for indexing: Some columns are strong candidates for indexing:
- Values are relatively unique in the column.
- There is a wide range of values (good for regular indexes).
- There is a small range of values (good for bitmap indexes).
- The column contains many nulls, but queries often select all rows having a value.
Columns not suitable for indexing:
- There are many nulls in the column and you do not search on the not null values.
- The LONG and LONG RAW columns cannot be indexed.
- Virtual columns: You can create unique or nonunique indexes on virtual columns.

Oracle Database 11g: SQL Tuning Workshop 4 - 39

Guidelines for Managing Indexes (continued)


Order index columns for performance: The order of columns in the CREATE INDEX
statement can affect query performance. In general, specify the most frequently used columns
first.
Limit the number of indexes for each table: A table can have any number of indexes.
However, the more indexes there are, the more overhead is incurred as the table is modified.
Thus, there is a trade-off between the speed of retrieving data from a table and the speed of
updating the table.
Drop indexes that are no longer required.
Specify the tablespace for each index: If you use the same tablespace for a table and its index,
it can be more convenient to perform database maintenance, such as tablespace backup.
Consider parallelizing index creation: You can parallelize index creation, just as you can
parallelize table creation. This speeds up index creation. However, an index created with an
INITIAL value of 5M and a parallel degree of 12 consumes at least 60 MB of storage during
index creation.
Consider creating indexes with NOLOGGING: You can create an index and generate minimal
redo log records by specifying NOLOGGING in the CREATE INDEX statement. Because
indexes created using NOLOGGING are not archived, perform a backup after you create the
index. Note that NOLOGGING is the default in a NOARCHIVELOG database.
Consider costs and benefits of coalescing or rebuilding indexes: Improper sizing or increased
growth can produce index fragmentation. To eliminate or reduce fragmentation, you can rebuild
or coalesce the index. But before you perform either task, weigh the costs and benefits of each
option, and select the one that works best for your situation.
Consider cost before disabling or dropping constraints: Because unique and primary keys
have associated indexes, you should factor in the cost of dropping and creating indexes when
considering whether to disable or drop a UNIQUE or PRIMARY KEY constraint. If the
associated index for a UNIQUE key or PRIMARY KEY constraint is extremely large, you can
save time by leaving the constraint enabled rather than dropping and re-creating the large index.
You also have the option of explicitly specifying that you want to keep or drop the index when
dropping or disabling a UNIQUE or PRIMARY KEY constraint.

Oracle Database 11g: SQL Tuning Workshop 4 - 40

Investigating Index Usage


You may often run a SQL statement expecting a particular index to be used, and it is not. This can be
because the optimizer is unaware of some information, or because it should not use the index.
Functions
If you apply a function to the indexed column in the WHERE clause, the index cannot be used; the
index is based on column values without the effect of the function. For example, the following
statement does not use an index on the salary column:
SELECT * FROM employees WHERE 1.10*salary > 10000

If you want an index to be used in this case, you can create a function-based index. Function-based
indexes were covered under Index Range Scan: Function-Based earlier in this lesson.
Data Type Mismatch
If there is a data type mismatch between the indexed column and the compared value, the index is
not used. This is due to the implicit data type conversion. For example, if the SSN column is of the
VARCHAR2 type, the following does not use the index on SSN:
SELECT * FROM person WHERE SSN = 123456789

Oracle Database 11g: SQL Tuning Workshop 4 - 41

Investigating Index Usage (continued)


Old Statistics
The statistics are considered by the optimizer when deciding whether to use an index. If they are
outdated, they may influence the optimizer to make poor decisions about indexes.
Null Columns
If a column can contain nulls, it may prevent the use of an index on that column. This is covered later
in this lesson.
Slower Index
Sometimes the use of an index is not efficient. This is covered later in this lesson.

Oracle Database 11g: SQL Tuning Workshop 4 - 42

Oracle Database 11g: SQL Tuning Workshop 4 - 43

Clusters
Clusters are an optional method for storing table data. A cluster is a group of tables that share the
same data blocks because they share common columns and are often used together. For example, the
ORDERS and ORDER_ITEMS table share the ORDER_ID column. When you cluster the ORDERS
and ORDER_ITEMS tables, the system physically stores all rows for each order from both the
ORDERS and ORDER_ITEMS tables in the same data blocks.
Cluster index: A cluster index is an index defined specifically for a cluster. Such an index contains
an entry for each cluster key value. To locate a row in a cluster, the cluster index is used to find the
cluster key value, which points to the data block associated with that cluster key value. Therefore, the
system accesses a given row with a minimum of two I/Os.
Hash clusters: Hashing is an optional way of storing table data to improve the performance of data
retrieval. To use hashing, you create a hash cluster and load tables into the cluster. The system
physically stores the rows of a table in a hash cluster and retrieves them according to the results of a
hash function. The key of a hash cluster (just as the key of an index cluster) can be a single column
or composite key. To find or store a row in a hash cluster, the system applies the hash function to the
rows cluster key value; the resulting hash value corresponds to a data block in the cluster, which the
system then reads or writes on behalf of the issued statement.
Note: Hash clusters are a better choice than using an indexed table or index cluster when a table is
queried frequently with equality queries.

Oracle Database 11g: SQL Tuning Workshop 4 - 44

When Are Clusters Useful?


Index clusters allow row data from one or more tables that share a cluster key value to be stored
in same block. You can locate these rows using a cluster index, which has one entry per cluster
key value and not for each row. Therefore, the index is smaller and less costly to access for
finding multiple rows. The rows with the same key are in a small group of blocks. This means
that in an index cluster the clustering factor is very good and provides clustering for data from
multiple tables sharing the same join key. The smaller index and smaller group of blocks reduce
the cost of access by reducing block visits to the buffer cache. Index clusters are useful when the
size of the tables is not known in advance (For example: Creating a new table rather than
converting an existing one whose size is stable) because a cluster bucket is only created after a
cluster key value is used. They are also useful for all filter operations or searches. Note that full
table scans do not perform well on a table in a multiple table cluster as it has more blocks than
the table would have if created as a heap table.
Hash clusters allow row data from one or more tables that share a cluster key value to be stored
in same block. You can locate these rows using a system-provided or user-provided hashing
function or using the cluster key value assuming that this is already evenly distributed making
the access to a row faster than using index clusters. Table rows with the same cluster key values
hash into the same cluster buckets and can be stored in the same block or small group of blocks.

Oracle Database 11g: SQL Tuning Workshop 4 - 45

When Are Clusters Useful? (continued)


This means that in a hash cluster the clustering factor is also very good and a row may be
accessed by its key with one block visit only and without needing an index. Hash clusters
allocate all the storage for all the hash buckets when the cluster is created, so they may waste
space. They also do not perform well other than on equality searches or nonequality searches.
Like index clusters if they contain multiple tables, full scans are more expensive for the same
reason.
Single-table hash clusters are similar to a hash cluster, but are optimized in the block structures
for access to a single table, thereby providing the fastest possible access to a row other than by
using a rowid filter. As they only have one table, full scans, if they happen, cost as much as they
would in a heap table.
Sorted hash clusters are designed to reduce costs of accessing ordered data by using a hashing
algorithm on the hash key. Accessing the first row matching the hash key may be less costly than
using an IOT for a large table because it saves the cost of a B*-tree probe. All the rows that
match on a particular hash key (For example: Account number) are stored in the cluster in the
order of the sort key or keys (For example: Phone calls), thereby, eliminating the need for a sort
to process the order by clause. These clusters are very good for batch reporting, billing, and so
on.

Oracle Database 11g: SQL Tuning Workshop 4 - 46

Cluster Access Path: Examples


The example in the slide shows you two different cluster access paths.
In the top one, a hash scan is used to locate rows in a hash cluster, based on a hash value. In a hash
cluster, all rows with the same hash value are stored in the same data block. To perform a hash scan,
the system first obtains the hash value by applying a hash function to a cluster key value specified by
the statement. The system then scans the data blocks containing rows with that hash value.
The second one assumes that a cluster index was used to cluster both the EMP and DEPT tables. In
this case, a cluster scan is used to retrieve, from a table stored in an indexed cluster, all rows that
have the same cluster key value. In an indexed cluster, all rows with the same cluster key value are
stored in the same data block. To perform a cluster scan, the system first obtains the ROWID of one of
the selected rows by scanning the cluster index. The system then locates the rows based on this
ROWID.
Note: You see examples of how to create clusters in the labs for this lesson.

Oracle Database 11g: SQL Tuning Workshop 4 - 47

Sorting Operators
Sort operations result when users specify an operation that requires a sort. Commonly encountered
operations include the following:
SORT AGGREGATE does not involve a sort. It retrieves a single row that is the result of
applying a group function to a group of selected rows. Operations such as COUNT and MIN are
shown as SORT AGGREGATE.
SORT UNIQUE sorts output rows to remove duplicates. It occurs if a user specifies a
DISTINCT clause or if an operation requires unique values for the next step.
SORT JOIN happens during a sort-merge join, if the rows need to be sorted by the join key.
SORT GROUP BY is used when aggregates are computed for different groups in the data. The
sort is required to separate the rows into different groups.
SORT ORDER BY is required when the statement specifies an ORDER BY that cannot be
satisfied by one of the indexes.
HASH GROUP BY hashes a set of rows into groups for a query with a GROUP BY clause.
HASH UNIQUE hashes a set of rows to eliminate duplicates. It occurs if a user specifies a
DISTINCT clause or if an operation requires unique values for the next step. This is similar to
SORT UNIQUE.
Note: Several SQL operators cause implicit sorts (or hashes since Oracle Database 10g, Release 2),
such as DISTINCT, GROUP BY, UNION, MINUS, and INTERSECT. However, do not rely on these
SQL operators to return ordered rows. If you want to have rows ordered, use the ORDER BY clause.
Oracle Database 11g: SQL Tuning Workshop 4 - 48

Buffer Sort Operator


The BUFFER SORT operator uses a temporary table or a sort area in memory to store intermediate
data. However, the data is not necessarily sorted.
This is needed if there is an operation that needs all the input data before it can start (See Cartesian
Join.).
So BUFFER SORT uses the buffering mechanism of a traditional sort, but it does not do the sort
itself. The system simply buffers the data, in the User Global Area (UGA) or Program Global Area
(PGA), to avoid multiple table scans against real data blocks.
The whole sort mechanism is reused, including the swap to disk when not enough sort area memory
is available, but without sorting the data.
The difference between a temporary table and a buffer sort is as follows:
A temporary table uses System Global Area (SGA).
A buffer sort uses UGA.
Both of these can be swapped in the temporary files. The biggest difference is that the temporary
table has to go through the buffer cache, which introduces latching effects that do not appear for
buffer sorts.

Oracle Database 11g: SQL Tuning Workshop 4 - 49

Inlist Iterator
It is used when a query contains an IN clause with values or multiple equality predicates on the same
column linked with ORs.
The INLIST ITERATOR operator iterates over the enumerated value list, and every value is
executed separately.
The execution plan is identical to the result of a statement with an equality clause instead of IN,
except for one additional step. The extra step occurs when INLIST ITERATOR feeds the equality
clause with unique values from the list.
You can view this operator as a FOR LOOP statement in PL/SQL. In the example in the slide, you
iterate the index probe over two values: 1 and 2.
Also, it is a function that uses an index, which is scanned for each value in the list. An alternative
handling is UNION ALL of each value or a FILTER of the values against all the rows, this is
significantly more efficient.
The optimizer uses an inlist iterator when an IN clause is specified with values, and the optimizer
finds a selective index for that column. If there are multiple OR clauses using the same index, the
optimizer selects this operation rather than CONCATENATION or UNION ALL, because it is more
efficient.

Oracle Database 11g: SQL Tuning Workshop 4 - 50

View Operator
Each query produces a variable set of data in the form of a table. A view simply gives a name to this
set of data.
When views are referenced in a query, the system can handle them in two ways. If a number of
conditions are met, they can be merged into the main query. This means that the view text is
rewritten as a join with the other tables in the query. Views can also be left as standalone views and
selected from directly as in the case of a table. Predicates can also be pushed into or pulled out of the
views as long as certain conditions are met.
When a view is not merged, you can see the VIEW operator. The view operation is executed
separately. All rows from the view are returned, and the next operation can be done.
Sometimes a view cannot be merged and must be executed independently in a separate query block.
In this case, you can also see the VIEW operator in the explain plan. The VIEW keyword indicates
that the view is executed as a separate query block. For example, views containing GROUP BY
functions cannot be merged.
The second example in the slide shows a nonmergeable inline view. An inline view is basically a
query within the FROM clause of your statement.
Basically, this operator collects all rows from a query block before they can be processed by higher
operations in the plan.

Oracle Database 11g: SQL Tuning Workshop 4 - 51

Count Stop Key Operator


COUNT STOPKEY limits the number of rows returned. The limitation is expressed by the ROWNUM
expression in the WHERE clause. It terminates the current operation when the count is reached.
Note: The cost of this operator depends on the number of occurrences of the values you try to
retrieve. If the value appears very frequently in the table, the count is reached quickly. If the value is
very infrequent, and there are no indexes, the system has to read most of the tables blocks before
reaching the count.

Oracle Database 11g: SQL Tuning Workshop 4 - 52

Min/Max and First Row Operators


FIRST ROW retrieves only the first row selected by a query. It stops accessing the data after the first
value is returned. This is an optimization introduced in Oracle 8i and it works with the index range
scan and the index full scan.
In the example in the slide, it is assumed that there is an index on the ID column.

Oracle Database 11g: SQL Tuning Workshop 4 - 53

Join Methods
A row source is a set of data that can be accessed in a query. It can be a table, an index, a
nonmergeable view, or even the result set of a join tree consisting of many different objects.
A join predicate is a predicate in the WHERE clause that combines the columns of two of the tables in
the join.
A nonjoin predicate is a predicate in the WHERE clause that references only one table.
A join operation combines the output from two row sources (such as tables or views) and returns one
resulting row source (data set). The optimizer supports different join methods such as the following:
Nested loop join: Useful when small subsets of data are being joined and if the join condition is
an efficient way of accessing the second table
Sort-merge join: Can be used to join rows from two independent sources. Hash joins generally
perform better than sort-merge joins. On the other hand, sort-merge joins can perform better than
hash joins if one or two row sources are already sorted.
Hash join: Used for joining large data sets. The optimizer uses the smaller of two tables or data
sources to build a hash table on the join key in memory. It then scans the larger table, probing
the hash table to find the joined rows. This method is best used when the smaller table fits in the
available memory. The cost is then limited to a single read pass over the data for the two tables.
Note: The slide shows you the same query using both the American National Standards Institute
(ANSI) and non-ANSI join syntax.
Oracle Database 11g: SQL Tuning Workshop 4 - 54

Nested Loops Join


One of the two tables is defined as the outer table, or the driving table, or the left-hand side. The
other table is called the inner table, or the right-hand side.
For each row in the outer (driving) table that matches the single table predicates, all rows in the inner
table that satisfy the join predicate (matching rows) are retrieved.
Any nonjoin predicates on the inner table are considered after this initial retrieval, unless a composite
index combining both the join and the nonjoin predicate is used.
Based on the example in the slide, the code to emulate a nested loop join might look as follows:
for r1 in (select rows from EMP that match single table predicate ) loop
for r2 in (select rows from DEPT that match current row from EMP) loop
output values from current row of EMP and current row of DEPT
end loop
end loop

The optimizer uses nested loop joins when joining small number of rows, with a good driving
condition between the two tables. You drive from the outer loop to the inner loop, so the order of
tables in the execution plan is important. Therefore, you should use other join methods when two
independent row sources are joined.

Oracle Database 11g: SQL Tuning Workshop 4 - 55

Nested Loops Join: Prefetching


Oracle 9iR2 introduced a mechanism called nested loops prefetching. The idea is to improve I/O
utilization, therefore response time, of index access with table lookup by batching rowid lookups into
parallel block reads.
This change to the plan output is not considered a different execution plan. It does not affect the join
order, join method, access method, or parallelization scheme.
This optimization is only available when the inner access path is index range scan and not if the inner
access path is index unique scan.
The prefetching mechanism is used by table lookup. When an index access path is chosen and the
query cannot be satisfied by the index alone, the data rows indicated by the ROWID also must be
fetched. This ROWID to data row access (table lookup) is improved using data block prefetching,
which involves reading an array of blocks which are pointed at by an array of qualifying ROWIDs.
Without data block prefetching, accessing a large number of rows using a poorly clustered B*-tree
index could be expensive. Each row accessed by the index would likely be in a separate data block
and thus would require a separate I/O operation.
With data block prefetching, the system delays data blocks reads until multiple rows specified by the
underlying index are ready to be accessed and then retrieves multiple data blocks simultaneously,
rather than reading a single data block at a time.

Oracle Database 11g: SQL Tuning Workshop 4 - 56

Nested Loops Join: 11g Implementation


Oracle Database 11g introduces a new way of performing joins with NESTED LOOPS operators.
With this NESTED LOOPS implementation, the system first performs a NESTED LOOPS join
between the other table and the index. This produces a set of ROWIDs that you can use to look up the
corresponding rows from the table with the index. Instead of going to the table for each ROWID
produced by the first NESTED LOOPS join, the system batches up the ROWIDs and performs a
second NESTED LOOPS join between the ROWIDs and the table. This ROWID batching technique
improves performance as the system only reads each block in the table once.

Oracle Database 11g: SQL Tuning Workshop 4 - 57

Sort Merge Join


In a sort merge join, there is no concept of a driving table. A sort merge join is executed as follows:
1. Get the first data set, using any access and filter predicates, and sort it on the join columns.
2. Get the second data set, using any access and filter predicates, and sort it on the join columns.
3. For each row in the first data set, find the start point in the second data set and scan until you
find a row that does not join.
The merge operation combines the two sorted row sources to retrieve every pair of rows that contain
matching values for the columns used in the join predicate.
If one row source has already been sorted in a previous operation (there is an index on the join
column, for example), the sort merge operation skips the sort on that row source. When you perform
a merge join, you must fetch all rows from the two row sources before to return the first row to the
next operation. Sorting could make this join technique expensive, especially if sorting cannot be
performed in memory. The optimizer can select a sort merge join over a hash join for joining large
amounts of data if any of the following conditions are true:
The join condition between two tables is not an equijoin.
Sorts already required by previous operations.
Note: Sort merge joins are useful when the join condition between two tables is an inequality
condition (but not a nonequality), such as <, <=, >, or >=.

Oracle Database 11g: SQL Tuning Workshop 4 - 58

Hash Join
To perform a hash join between two row sources, the system reads the first data set and builds an
array of hash buckets in memory. A hash bucket is little more than a location that acts as the starting
point for a linked list of rows from the build table. A row belongs to a hash bucket if the bucket
number matches the result that the system gets by applying an internal hashing function to the join
column or columns of the row.
The system starts to read the second set of rows, using whatever access mechanism is most
appropriate for acquiring the rows, and uses the same hash function on the join column or columns to
calculate the number of the relevant hash bucket. The system then checks to see if there are any rows
in that bucket. This is known as probing the hash table.
If there are no rows in the relevant bucket, the system can immediately discard the row from the
probe table.
If there are some rows in the relevant bucket, the system does an exact check on the join column or
columns to see if there is a proper match. Any rows that survive the exact check can immediately be
reported (or passed on to the next step in the execution plan). So, when you perform a hash join, you
must fetch all rows from the smallest row source to return the first row to next operation.
Note: Hash joins are performed only for equijoins, and are most useful when joining large amount of
data.

Oracle Database 11g: SQL Tuning Workshop 4 - 59

Cartesian Join
A Cartesian join is used when one or more of the tables does not have any join conditions to any
other tables in the statement. The optimizer joins every row from one data source with every row
from the other data source, creating the Cartesian product of the two sets.
A Cartesian join can be seen as a nested loop with no elimination; the first row source is read and
then for every row, all the rows are returned from the other row source.
Note: Cartesian join is generally not desirable. However, it is perfectly acceptable to have one with
single-row row source (guaranteed by an unique index for example) joined to some other table.

Oracle Database 11g: SQL Tuning Workshop 4 - 60

Join Types
Join operation types include the following:
Join (equijoin and nonequijoin): Returns rows that match predicate join
Outer join: Returns rows that match predicate join and row when no match is found
Semi join: Returns rows that match the EXISTS subquery. Find one match in the inner table,
then stop search.
Anti join: Returns rows with no match in the NOT IN subquery. Stop as soon as one match is
found.
Star join: This is not a join type, but just a name for an implementation of a performance
optimization to better handle the fact and dimension model.
Antijoin and semijoin are considered to be join types, even though the SQL constructs that cause
them are subqueries. Antijoin and semijoin are internal optimizations algorithms used to flatten
subquery constructs in such a way that they can be resolved in a join-like way.

Oracle Database 11g: SQL Tuning Workshop 4 - 61

Equijoins and Nonequijoins


The join condition determines whether a join is an equijoin or a nonequijoin. An equijoin is a join
with a join condition containing an equality operator. When a join condition relates two tables by an
operator other than equality, it is a nonequijoin.
Equijoins are the most commonly used. An example each of an equijoin and a nonequijoin are shown
in the slide. Nonequijoins are less frequently used.
To improve SQL efficiency, use equijoins whenever possible. Statements that perform equijoins on
untransformed column values are the easiest to tune.
Note: If you have a nonequijoin, a hash join is not possible.

Oracle Database 11g: SQL Tuning Workshop 4 - 62

Outer Joins
The simple join is the most commonly used within the system. Other joins open up extra
functionality, but have much more specialized uses. The outer join operator is placed on the deficient
side of the query. In other words, it is placed against the table that has the missing join information.
Consider EMP and DEPT. There may be a department that has no employees. If EMP and DEPT are
joined together, this particular department would not appear in the output because there is no row that
matches the join condition for that department. By using the outer join, the missing department can
be displayed.
Outer join with nested loops: The left/driving table is always the table whose rows are being
preserved (DEPT in the example). For each row from DEPT, look for a matching rows in EMP. If
none is found, output DEPT values with null values for the EMP columns. If rows are found,
output DEPT values with these EMP values.
Hash Outer joins: The left/outer table whose rows are being preserved is used to build the hash
table, and the right/inner table is used to probe the hash table. When a match is found, the row is
output and the entry in the hash table is marked as matched to a row. After the inner table is
exhausted, the hash table is read over once again, and any rows that are not marked as matched
are output with null values for the EMP columns. With Oracle Database 10g, HASH JOIN
RIGHT OUTER is available. The system hashes the table whose rows are not being preserved,
and then reads the table whose rows are being preserved, probing the hash table to see whether
there was a row to join to.
Oracle Database 11g: SQL Tuning Workshop 4 - 63

Note: You can also use the ANSI syntax for full, left, and right outer joins (not shown in the
slide).

Oracle Database 11g: SQL Tuning Workshop 4 - 63

Semijoins
Semijoins return a result when you hit the first joining record. A semijoin is an internal way of
transforming an EXISTS subquery into a join. However, you cannot see this occur anywhere.
Semijoins return rows that match an EXISTS subquery without duplicating rows from the left side of
the predicate when multiple rows on the right side satisfy the criteria of the subquery.
In the above diagram, for each DEPT record, only the first matching EMP record is returned as a join
result. This prevents scanning huge numbers of duplicate rows in a table when all you are interested
in is if there are any matches.
When the subquery is not unnested, a similar result could be achieved by using a FILTER operation
and scanning a row source until a match is found, then returning it.
Note: The optimizer can use nested loops, merge-join or hash-join algorithms to perform semijoins.
In addition, the new HASH JOIN RIGHT SEMI optimization is also possible.

Oracle Database 11g: SQL Tuning Workshop 4 - 64

Antijoins
Antijoins return rows that fail to match (NOT IN) the subquery at the right side. For example, an
antijoin can select a list of departments which do not have any employee.
The optimizer uses a nested loops algorithm for NOT IN subqueries by default. However, if the
MERGE_AJ, HASH_AJ, or NL_AJ hints are used and various required conditions are met, the NOT
IN uncorrelated subquery can be changed into a sort merge or hash antijoin. Another possible
optimization that was introduced in Oracle Database 11g is to perform a HASH [RIGTH] ANTI NA
(NA for Null Aware) operation.
Although antijoins are mostly transparent to the user, it is useful to know that these join types exist
and could help explain unexpected performance changes between releases.

Oracle Database 11g: SQL Tuning Workshop 4 - 65

Oracle Database 11g: SQL Tuning Workshop 4 - 66

Filter Operations
A FILTER operation is any operation that discards rows returned by another step, but is not involved
in retrieving the rows itself. All sorts of operations can be filters, including subqueries and single
table predicates.
In the example at the left, FILTER applies to the groups that are created by the GROUP BY
operation.
In the example at the right, FILTER is almost used in the same way as NESTED LOOPS. DEPT is
accessed once, and for each row from DEPT, EMP is accessed by its index on DEPTNO (See
operation 3.)
This operation is done as many times as the number of rows in DEPT.
The FILTER operation is applied, for each row, after DEPT rows are fetched. The FILTER discards
rows for which operation 3 returned at least one row (NOT EXIST (select 0 from emp
where emp.deptno=:B1) is TRUE.

Oracle Database 11g: SQL Tuning Workshop 4 - 67

Concatenation Operation
CONCATENATION concatenates the rows returned by two or more row sets. This works like UNION
ALL and does not remove duplicate rows.
It is used with OR expansions. However, OR does not return duplicate rows, so for each component
after the first, it appends a negation of the previous components (LNNVL):
CONCATENATION
- BRANCH 1 - SAL=2
- BRANCH 2 - DEPTNO = 1 AND NOT row in Branch 1

The LNNVL function is generated by the OR clause to process this negation.


The LNNVL() function returns TRUE, if the predicate is NULL or FALSE.
So filter (LNNVL(SAL=2)) returns all rows for which SAL != 2 or SAL is NULL.

Oracle Database 11g: SQL Tuning Workshop 4 - 68

UNION [ALL], INTERSECT, MINUS


SQL handles duplicate rows with an ALL or DISTINCT modifier in different places in the language.
ALL preserves duplicates and DISTINCT removes them. Here is a quick description of the possible
SQL set operations:
INTERSECTION: Operation accepting two sets of rows and returning the intersection of the
sets, eliminating duplicates. Subrow sources are executed or optimized individually. This is very
similar to sort-merge-join processing: Full rows are sorted and matched.
MINUS: Operation accepting two sets of rows and returning rows appearing in the first set, but
not in the second, eliminating duplicates. Subrow sources are executed or optimized
individually. Similar to INTERSECT processing. However, instead of match-and-return, it is
match-and-exclude.
UNION: Operation accepting two sets of rows and returning the union of the sets, eliminating
duplicates. Subrow sources are executed or optimized individually. Rows retrieved are
concatenated and sorted to eliminate duplicate rows.
UNION ALL: Operation accepting two sets of rows and returning the union of the sets, and not
eliminating duplicates. The expensive sort operation is not necessary. Use UNION ALL if you
know you do not have to deal with duplicates.

Oracle Database 11g: SQL Tuning Workshop 4 - 69

Result Cache Operator


The SQL query result cache enables explicit caching of query result sets and query fragments in
database memory. A dedicated memory buffer stored in the shared pool can be used for storing and
retrieving the cached results. The query results stored in this cache become invalid when data in the
database objects that are accessed by the query is modified. Although the SQL query cache can be
used for any query, good candidate statements are the ones that need to access a very high number of
rows to return only a fraction of them. This is mostly the case for data warehousing applications.
If you want to use the query result cache and the RESULT_CACHE_MODE initialization parameter is
set to MANUAL, you must explicitly specify the RESULT_CACHE hint in your query. This introduces
the ResultCache operator into the execution plan for the query. When you execute the query, the
ResultCache operator looks up the result cache memory to check whether the result for the query
already exists in the cache. If it exists, the result is retrieved directly out of the cache. If it does not
yet exist in the cache, the query is executed, the result is returned as output, and is also stored in the
result cache memory.
If the RESULT_CACHE_MODE initialization parameter is set to FORCE, and you do not want to store
the result of a query in the result cache, you must then use the NO_RESULT_CACHE hint in your
query.

Oracle Database 11g: SQL Tuning Workshop 4 - 70

Oracle Database 11g: SQL Tuning Workshop 4 - 71

Oracle Database 11g: SQL Tuning Workshop 4 - 72

Oracle Database 11g: SQL Tuning Workshop 5 - 2

What Is an Execution Plan?


An execution plan is the output of the optimizer and is presented to the execution engine for
implementation. It instructs the execution engine about the operations it must perform for retrieving
the data required by a query most efficiently.
The EXPLAIN PLAN statement gathers execution plans chosen by the Oracle optimizer for the
SELECT, UPDATE, INSERT, and DELETE statements. The steps of the execution plan are not
performed in the order in which they are numbered. There is a parent-child relationship between
steps. The row source tree is the core of the execution plan. It shows the following information:
An ordering of the tables referenced by the statement
An access method for each table mentioned in the statement
A join method for tables affected by join operations in the statement
Data operations, such as filter, sort, or aggregation
In addition to the row source tree (or data flow tree for parallel operations), the plan table contains
information about the following:
Optimization, such as the cost and cardinality of each operation
Partitioning, such as the set of accessed partitions
Parallel execution, such as the distribution method of join inputs
The EXPLAIN PLAN results help you determine whether the optimizer selects a particular
execution plan, such as nested loops join.
Oracle Database 11g: SQL Tuning Workshop 5 - 3

Where to Find Execution Plans?


There are many ways to retrieve execution plans inside the database. The most well-known ones are
listed in the slide:
The EXPLAIN PLAN command enables you to view the execution plan that the optimizer
might use to execute a SQL statement. This command is very useful because it outlines the plan
that the optimizer may use and inserts it in a table called PLAN_TABLE without executing the
SQL statement.
V$SQL_PLAN provides a way to examine the execution plan for cursors that were recently
executed. Information in V$SQL_PLAN is very similar to the output of an EXPLAIN PLAN
statement. However, while EXPLAIN PLAN shows a theoretical plan that can be used if this
statement was executed, V$SQL_PLAN contains the actual plan used.
V$SQL_PLAN_MONITOR displays plan-level monitoring statistics for each SQL statement
found in V$SQL_MONITOR. Each row in V$SQL_PLAN_MONITOR corresponds to an
operation of the execution plan that is monitored.
The Automatic Workload Repository (AWR) infrastructure and statspack reports store plans
from top SQLs. Plans are recorded into DBA_HIST_SQL_PLAN and STATS$SQL_PLAN.
Plan and row source operations are dumped in trace files generated by DBMS_MONITOR.

Oracle Database 11g: SQL Tuning Workshop 5 - 4

Where to Find Execution Plan? (continued)


The SQL management base (SMB) is a part of the data dictionary that resides in the SYSAUX
tablespace. It stores statement log, plan histories, and SQL plan baselines, as well as SQL
profiles.
The event 10053, which is used to dump cost-based optimizer (CBO) computations may include
a plan.
Starting with Oracle Database 10g, Release 2, when you dump process state (or errorstack from
a process), execution plans are included in the trace file that is generated.

Oracle Database 11g: SQL Tuning Workshop 5 - 5

Viewing Execution Plans


The DBMS_XPLAN package supplies five table functions:
DISPLAY: To format and display the contents of a plan table
DISPLAY_AWR: To format and display the contents of the execution plan of a stored SQL
statement in the AWR
DISPLAY_CURSOR: To format and display the contents of the execution plan of any loaded
cursor
DISPLAY_SQL_PLAN_BASELINE: To display one or more execution plans for the SQL
statement identified by SQL handle
DISPLAY_SQLSET: To format and display the contents of the execution plan of statements
stored in a SQL tuning set

Oracle Database 11g: SQL Tuning Workshop 5 - 6

The EXPLAIN PLAN Command


The EXPLAIN PLAN command is used to generate the execution plan that the optimizer uses to
execute a SQL statement. It does not execute the statement, but simply produces the plan that may be
used, and inserts this plan into a table. If you examine the plan, you can see how the Oracle Server
executes the statement.
Using EXPLAIN PLAN
First use the EXPLAIN PLAN command to explain a SQL statement.
Then retrieve the plan steps by querying PLAN_TABLE.
PLAN_TABLE is automatically created as a global temporary table to hold the output of an
EXPLAIN PLAN statement for all users. PLAN_TABLE is the default sample output table into
which the EXPLAIN PLAN statement inserts rows describing execution plans.
Note: You can create your own PLAN_TABLE using the
$ORACLE_HOME/rdbms/admin/utlxplan.sql script if you want to keep the execution plan
information for a long term.

Oracle Database 11g: SQL Tuning Workshop 5 - 7

The EXPLAIN PLAN Command (continued)


This command inserts a row in the plan table for each step of the execution plan.
In the syntax diagram in the slide, the fields in italics have the following meanings:

Field

Meaning

text

This is an optional identifier for the statement. You should


enter a value to identify each statement so that you can later
specify the statement that you want explained. This is
especially important when you share the plan table with
others, or when you keep multiple execution plans in the
same plan table.

schema.table

This is the optional name for the output table. The default is
PLAN_TABLE.

statement

This is the text of the SQL statement.

Oracle Database 11g: SQL Tuning Workshop 5 - 8

The EXPLAIN PLAN Command: Example


This command inserts the execution plan of the SQL statement in the plan table and adds the optional
demo01 name tag for future reference. You can also use the following syntax:
EXPLAIN PLAN
FOR
SELECT e.last_name, d.department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id =d.department_id;

Oracle Database 11g: SQL Tuning Workshop 5 - 9

PLAN_TABLE
There are various available methods to gather execution plans. Now, you are introduced only to the
EXPLAIN PLAN statement. This SQL statement gathers the execution plan of a SQL statement
without executing it, and outputs its result in the PLAN_TABLE table. Whatever the method to gather
and display the explain plan, the basic format and goal are the same. However, PLAN_TABLE just
shows you a plan that might not be the one chosen by the optimizer. PLAN_TABLE is automatically
created as a global temporary table and is visible to all users. PLAN_TABLE is the default sample
output table into which the EXPLAIN PLAN statement inserts rows describing execution plans.
PLAN_TABLE is organized in a tree-like structure and you can retrieve that structure by using both
the ID and PARENT_ID columns with a CONNECT BY clause in a SELECT statement. While a
PLAN_TABLE table is automatically set up for each user, you can use the utlxplan.sql SQL
script to manually create a local PLAN_TABLE in your schema and use it to store the results of
EXPLAIN PLAN. The exact name and location of this script depends on your operating system. On
UNIX, it is located in the $ORACLE_HOME/rdbms/admin directory. It is recommended that you
drop and rebuild your local PLAN_TABLE table after upgrading the version of the database because
the columns might change. This can cause scripts to fail or cause TKPROF to fail, if you are
specifying the table.
Note: If you want an output table with a different name, first create PLAN_TABLE manually with
the utlxplan.sql script, and then rename the table with the RENAME SQL statement.

Oracle Database 11g: SQL Tuning Workshop 5 - 10

Displaying from PLAN_TABLE: Typical


In the example in the slide, the EXPLAIN PLAN command inserts the execution plan of the SQL
statement in PLAN_TABLE and adds the optional demo01 name tag for future reference. It is
important to note that the SELECT statement itself is not executed, but is simply optimized to
determine the best execution plan in that context. The DISPLAY function of the DBMS_XPLAN
package can be used to format and display the last statement stored in PLAN_TABLE. You can also
use the following syntax to retrieve the same result: SELECT * FROM
TABLE(dbms_xplan.display('plan_table','demo01','typical',null));

The output is the same as shown in the slide. In this example, you can substitute the name of another
plan table instead of PLAN_TABLE and demo01 represents the statement ID. TYPICAL displays
the most relevant information in the plan: operation ID, name and option, number of rows, bytes, and
optimizer cost. The last parameter for the DISPLAY function is the one corresponding to
filter_preds. This parameter represents a filter predicate or predicates to restrict the set of rows
selected from the table where the plan is stored. When value is null (the default), the plan displayed
corresponds to the last executed explain plan. This parameter can reference any column of the table
where the plan is stored and can contain any SQL construct, for example, subquery or function calls.
Note: Alternatively, you can run the utlxpls.sql (or utlxplp.sql for parallel queries) script
(located in the ORACLE_HOME/rdbms/admin/ directory) to display the execution plan stored in
PLAN_TABLE for the last statement explained. This script uses the DISPLAY table function from the
Oracle Database 11g: SQL Tuning Workshop 5 - 11

DBMS_XPLAN package.

Oracle Database 11g: SQL Tuning Workshop 5 - 11

Displaying from PLAN_TABLE: ALL


Here you use the same EXPLAIN PLAN command example as in the previous slide. The ALL
option used with the DISPLAY function allows you to output the maximum user level information. It
includes information displayed with the TYPICAL level, with additional information such as
PROJECTION, ALIAS, and information about REMOTE SQL, if the operation is distributed.
For finer control on the display output, the following keywords can be added to the format parameter
to customize its default behavior. Each keyword either represents a logical group of plan table
columns (such as PARTITION) or logical additions to the base plan table output (such as
PREDICATE). Format keywords must be separated by either a comma or a space:
ROWS: If relevant, shows the number of rows estimated by the optimizer
BYTES: If relevant, shows the number of bytes estimated by the optimizer
COST: If relevant, shows optimizer cost information
PARTITION: If relevant, shows partition pruning information
PARALLEL: If relevant, shows PX information (distribution method and table queue
information)
PREDICATE: If relevant, shows the predicate section
PROJECTION: If relevant, shows the projection section
ALIAS: If relevant, shows the Query Block Name/Object Alias section

Oracle Database 11g: SQL Tuning Workshop 5 - 12

Displaying from PLAN_TABLE: ALL (continued)

REMOTE: If relevant, shows the information for the distributed query (for example, remote from
serial distribution and remote SQL)
NOTE: If relevant, shows the note section of the explain plan

If the target plan table also stores plan statistics columns (for example, it is a table used to capture the
content of the fixed view V$SQL_PLAN_STATISTICS_ALL), additional format keywords can be
used to specify which class of statistics to display when using the DISPLAY function. These
additional format keywords are IOSTATS, MEMSTATS, ALLSTATS and LAST.
Note: Format keywords can be prefixed with the - sign to exclude the specified information. For
example, -PROJECTION excludes projection information.

Oracle Database 11g: SQL Tuning Workshop 5 - 13

Displaying from PLAN_TABLE: ADVANCED


The ADVANCED format is available only from Oracle Database 10g, Release 2 and later versions.
This output format includes all sections from the ALL format plus the outline data that represents a
set of hints to reproduce that particular plan.
This section may be useful if you want to reproduce a particular execution plan in a different
environment.
This is the same section, which is displayed in the trace file for event 10053.
Note: When the ADVANCED format is used with V$SQL_PLAN, there is one more section called
Peeked Binds (identified by position).

Oracle Database 11g: SQL Tuning Workshop 5 - 14

AUTOTRACE
When running SQL statements under SQL*Plus, you can automatically get a report on the execution
plan and the statement execution statistics. The report is generated after successful SQL DML (that
is, SELECT, DELETE, UPDATE and INSERT) statements. It is useful for monitoring and tuning the
performance of these statements.
To use this feature, you must have a PLAN_TABLE available in your schema and then have the
PLUSTRACE role granted to you. Database administrator (DBA) privileges are required to grant the
PLUSTRACE role. The PLUSTRACE role is created and granted to the DBA role by running the
supplied $ORACLE_HOME/sqlplus/admin/plustrce.sql script.
On some versions and platforms, this is run by the database creation scripts. If this is not the case on
your platform, connect as SYSDBA and run the plustrce.sql script.
The PLUSTRACE role contains the select privilege on three V$ views. These privileges are necessary
to generate AUTOTRACE statistics.
AUTOTRACE is an excellent diagnostic tool for SQL statement tuning. Because it is purely
declarative, it is easier to use than EXPLAIN PLAN.
Note: The system does not support EXPLAIN PLAN for statements performing implicit type
conversion of date bind variables. With bind variables in general, the EXPLAIN PLAN output might
not represent the real execution plan.

Oracle Database 11g: SQL Tuning Workshop 5 - 15

The AUTOTRACE Syntax


You can enable AUTOTRACE in various ways using the syntax shown in the slide. The command
options are as follows:
OFF: Disables autotracing SQL statements
ON: Enables autotracing SQL statements
TRACE or TRACE[ONLY]: Enables autotracing SQL statements and suppresses statement
output
EXPLAIN: Displays execution plans, but does not display statistics
STATISTICS: Displays statistics, but does not display execution plans
Note: If both the EXPLAIN and STATISTICS command options are omitted, execution plans and
statistics are displayed by default.

Oracle Database 11g: SQL Tuning Workshop 5 - 16

AUTOTRACE: Examples
You can control the report by setting the AUTOTRACE system variable. The following are some
examples:
SET AUTOTRACE ON: The AUTOTRACE report includes both the optimizer execution plan
and the SQL statement execution statistics.
SET AUTOTRACE TRACEONLY EXPLAIN: The AUTOTRACE report shows only the
optimizer execution path without executing the statement.
SET AUTOTRACE ON STATISTICS: The AUTOTRACE report shows the SQL statement
execution statistics and rows.
SET AUTOTRACE TRACEONLY: This is similar to SET AUTOTRACE ON, but it suppresses
the printing of the users query output, if any. If STATISTICS is enabled, the query data is still
fetched, but not printed.
SET AUTOTRACE OFF: No AUTOTRACE report is generated. This is the default.

Oracle Database 11g: SQL Tuning Workshop 5 - 17

AUTOTRACE: Statistics
The statistics are recorded by the server when your statement executes and indicate the system
resources required to execute your statement. The results include the following statistics:
recursive calls is the number of recursive calls generated at both the user and system
level. Oracle Database maintains tables used for internal processing. When Oracle Database
needs to make a change to these tables, it internally generates an internal SQL statement, which
in turn generates a recursive call.
db block gets is the number of times a CURRENT block was requested.
consistent gets is the number of times a consistent read was requested for a block.
physical reads is the total number of data blocks read from disk. This number equals the
value of physical reads direct plus all reads into buffer cache.
redo size is the total amount of redo generated in bytes.
bytes sent via SQL*Net to client is the total number of bytes sent to the client
from the foreground processes.
bytes received via SQL*Net from client is the total number of bytes received
from the client over Oracle Net.
SQL*Net roundtrips to/from client is the total number of Oracle Net messages
sent to and received from the client.
Note: The statistics printed by AUTOTRACE are retrieved from V$SESSTAT.

Oracle Database 11g: SQL Tuning Workshop 5 - 18

AUTOTRACE: Statistics (continued)


sorts (memory) is the number of sort operations that were performed completely in
memory and did not require any disk writes.
sorts (disk) is the number of sort operations that required at least one disk write.
rows processed is the number of rows processed during the operation.
The client referred to in the statistics is SQL*Plus. Oracle Net refers to the generic process
communication between SQL*Plus and the server, regardless of whether Oracle Net is installed. You
cannot change the default format of the statistics report.
Note: db block gets indicates reads of the current block from the database. consistent
gets are reads of blocks that must satisfy a particular system change number (SCN). physical
reads indicates reads of blocks from disk. db block gets and consistent gets are the
two statistics that are usually monitored. These should be low compared to the number of rows
retrieved. Sorts should be performed in memory rather than on disk.

Oracle Database 11g: SQL Tuning Workshop 5 - 19

Using the V$SQL_PLAN View


This view provides a way of examining the execution plan for cursors that are still in the library
cache. The information in this view is very similar to the information in PLAN_TABLE. However,
EXPLAIN PLAN shows a theoretical plan that can be used if this statement were to be executed,
whereas V$SQL_PLAN contains the actual plan used. The execution plan obtained by the EXPLAIN
PLAN statement can be different from the execution plan used to execute the cursor. This is because
the cursor might have been compiled with different values of session parameters.
V$SQL_PLAN shows the plan for a cursor rather than for all cursors associated with a SQL
statement. The difference is that a SQL statement can have more than one cursor associated with it,
with each cursor further identified by a CHILD_NUMBER. For example, the same statement executed
by different users has different cursors associated with it if the object that is referenced is in a
different schema. Similarly, different hints can cause different cursors. The V$SQL_PLAN table can
be used to see the different plans for different child cursors of the same statement.
Note: Another useful view is V$SQL_PLAN_STATISTICS, which provides the execution statistics
of each operation in the execution plan for each cached cursor. Also, the
V$SQL_PLAN_STATISTICS_ALL view concatenates information from V$SQL_PLAN with
execution statistics from V$SQL_PLAN_STATISTICS and V$SQL_WORKAREA.

Oracle Database 11g: SQL Tuning Workshop 5 - 20

The V$SQL_PLAN Columns


The view contains almost all the PLAN_TABLE columns, in addition to others. The columns that are
also present in PLAN_TABLE have the same values:
ADDRESS
HASH_VALUE
The ADDRESS and HASH_VALUE columns can be used to join with V$SQLAREA to add the cursorspecific information.
The ADDRESS, HASH_VALUE, and CHILD_NUMBER columns can be used to join with V$SQL to
add the child cursorspecific information.
The PLAN_HASH VALUE column is a numerical representation of the SQL plan for the cursor. By
comparing one PLAN_HASH_VALUE with another, you can easily identify whether the two plans are
the same or not (rather than comparing the two plans line-by-line).
Note: Since Oracle Database 10g, SQL_HASH_VALUE in V$SESSION has been complemented
with SQL_ID, which you retrieve in many other V$ views. SQL_HASH_VALUE is a 32-bit value
and deemed to not be unique enough for large repositories of AWR data.
SQL_ID is a 64-bit hash value, which is more unique, the bottom 32 bits of which are
SQL_HASH_VALUE. It is normally represented as a character string to make it more manageable.

Oracle Database 11g: SQL Tuning Workshop 5 - 21

The V$SQL_PLAN_STATISTICS View


The V$SQL_PLAN_STATISTICS view provides the actual execution statistics for every operation
in the plan, such as the number of output rows, and elapsed time. All statistics, except the number of
output rows, are cumulative. For example, the statistics for a join operation also include the statistics
for its two inputs. The statistics in V$SQL_PLAN_STATISTICS are available for cursors that have
been compiled with the STATISTICS_LEVEL initialization parameter set to ALL or using the
GATHER_PLAN_STATISTICS hint.
The V$SQL_PLAN_STATISTICS_ALL view contains memory-usage statistics for row sources
that use SQL memory (sort or hash join). This view concatenates information in V$SQL_PLAN with
execution statistics from V$SQL_PLAN_STATISTICS and V$SQL_WORKAREA.

Oracle Database 11g: SQL Tuning Workshop 5 - 22

Links Between Important Dynamic Performance Views


V$SQLAREA displays statistics on shared SQL areas and contains one row per SQL string. It
provides statistics on SQL statements that are in memory, parsed, and ready for execution:
SQL_ID is the SQL identifier of the parent cursor in the library cache.
VERSION_COUNT is the number of child cursors that are present in the cache under this parent.
V$SQL lists statistics on shared SQL areas and contains one row for each child of the original SQL
text entered:
ADDRESS represents the address of the handle to the parent for this cursor.
HASH_VALUE is the value of the parent statement in the library cache.
SQL_ID is the SQL identifier of the parent cursor in the library cache.
PLAN_HASH_VALUE is a numeric representation of the SQL plan for this cursor. By comparing
one PLAN_HASH_VALUE with another, you can easily identify if the two plans are the same or
not (rather than comparing the two plans line-by-line).
CHILD_NUMBER is the number of this child cursor.
Statistics displayed in V$SQL are normally updated at the end of query execution. However, for
long-running queries, they are updated every five seconds. This makes it easy to see the impact of
long-running SQL statements while they are still in progress.

Oracle Database 11g: SQL Tuning Workshop 5 - 23

Links Between Important Dynamic Performance Views (continued)


V$SQL_PLAN contains the execution plan information for each child cursor loaded in the library
cache. The ADDRESS, HASH_VALUE, and CHILD_NUMBER columns can be used to join with
V$SQL to add the child cursor-specific information.
V$SQL_PLAN_STATISTICS provides execution statistics at the row source level for each child
cursor. The ADDRESS and HASH_VALUE columns can be used to join with V$SQLAREA to locate
the parent cursor. The ADDRESS, HASH_VALUE, and CHILD_NUMBER columns can be used to join
with V$SQL to locate the child cursor using this area.
V$SQL_PLAN_STATISTICS_ALL contains memory usage statistics for row sources that use SQL
memory (sort or hash join). This view concatenates information in V$SQL_PLAN with execution
statistics from V$SQL_PLAN_STATISTICS and V$SQL_WORKAREA.
V$SQL_WORKAREA displays information about work areas used by SQL cursors. Each SQL
statement stored in the shared pool has one or more child cursors that are listed in the V$SQL view.
V$SQL_WORKAREA lists all work areas needed by these child cursors. V$SQL_WORKAREA can be
joined with V$SQLAREA on (ADDRESS, HASH_VALUE) and with V$SQL on (ADDRESS,
HASH_VALUE, CHILD_NUMBER).
You can use this view to find answers to the following questions:
What are the top 10 work areas that require the most cache area?
For work areas allocated in the AUTO mode, what percentage of work areas run using maximum
memory?
V$SQLSTATS displays basic performance statistics for SQL cursors, with each row representing the
data for a unique combination of SQL text and optimizer plan (that is, unique combination of
SQL_ID and PLAN_HASH_VALUE). The column definitions for columns in V$SQLSTATS are
identical to those in the V$SQL and V$SQLAREA views. However, the V$SQLSTATS view differs
from V$SQL and V$SQLAREA in that it is faster, more scalable, and has a greater data retention (the
statistics may still appear in this view, even after the cursor has been aged out of the shared pool).
Note that V$SQLSTATS contains a subset of columns that appear in V$SQL and V$SQLAREA.

Oracle Database 11g: SQL Tuning Workshop 5 - 24

Querying V$SQL_PLAN
You can query V$SQL_PLAN using the DBMS_XPLAN.DISPLAY_CURSOR() function to display
the current or last executed statement (as shown in the example). You can pass the value of SQL_ID
for the statement as a parameter to obtain the execution plan for a given statement. SQL_ID is the
SQL_ID of the SQL statement in the cursor cache. You can retrieve the appropriate value by
querying the SQL_ID column in V$SQL or V$SQLAREA. Alternatively, you could select the
PREV_SQL_ID column for a specific session out of V$SESSION. This parameter defaults to null
in which case the plan of the last cursor executed by the session is displayed. To obtain SQL_ID,
execute the following query:
SELECT e.last_name, d.department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id =d.department_id;
SELECT SQL_ID, SQL_TEXT FROM V$SQL
WHERE SQL_TEXT LIKE '%SELECT e.last_name,%' ;
13saxr0mmz1s3
47ju6102uvq5q

select SQL_id, sql_text from v$SQL


SELECT e.last_name, d.department_name

CHILD_NUMBER is the child number of the cursor to display. If not supplied, the execution plan of
all cursors matching the supplied SQL_ID parameter are displayed. CHILD_NUMBER can be
specified only if SQL_ID is specified.
Oracle Database 11g: SQL Tuning Workshop 5 - 25

Querying V$SQL_PLAN (continued)


The FORMAT parameter controls the level of detail for the plan. In addition to the standard values
(BASIC, TYPICAL, SERIAL, ALL, and ANDVANCED), there are additional supported values to
display run-time statistics for the cursor:
IOSTATS: Assuming that the basic plan statistics are collected when SQL statements are
executed (either by using the GATHER_PLAN_STATISTICS hint or by setting the
statistics_level parameter to ALL), this format shows I/O statistics for ALL (or only for
LAST) executions of the cursor.
MEMSTATS: Assuming that the Program Global Area (PGA) memory management is enabled
(that is, the pga_aggregate_target parameter is set to a nonzero value), this format
allows to display memory management statistics (for example, execution mode of the operator,
how much memory was used, number of bytes spilled to disk, and so on). These statistics only
apply to memory-intensive operations, such as hash joins, sort or some bitmap operators.
ALLSTATS: A shortcut for 'IOSTATS MEMSTATS'
LAST: By default, plan statistics are shown for all executions of the cursor. The LAST keyword
can be specified to see only the statistics for the last execution.

Oracle Database 11g: SQL Tuning Workshop 5 - 26

Automatic Workload Repository (AWR)


The AWR is part of the intelligent infrastructure introduced with Oracle Database 10g. This
infrastructure is used by many components, such as Automatic Database Diagnostic Monitor
(ADDM) for analysis. The AWR automatically collects, processes, and maintains systemperformance statistics for problem-detection and self-tuning purposes and stores the statistics
persistently in the database.
The statistics collected and processed by the AWR include:
Object statistics that determine both access and usage statistics of database segments
Time-model statistics based on time usage for activities, displayed in the
V$SYS_TIME_MODEL and V$SESS_TIME_MODEL views
Some of the system and session statistics collected in the V$SYSSTAT and V$SESSTAT views
SQL statements that produce the highest load on the system, based on criteria, such as elapsed
time, CPU time, buffer gets, and so on
ASH statistics, representing the history of recent sessions

Oracle Database 11g: SQL Tuning Workshop 5 - 27

Automatic Workload Repository (continued)


The database automatically generates snapshots of the performance data once every hour and collects
the statistics in the workload repository. The data in the snapshot interval is then analyzed by ADDM.
The ADDM compares the differences between snapshots to determine which SQL statements to
capture based on the effect on the system load. This reduces the number of SQL statements that need
to be captured over time.
Note: By using PL/SQL packages, such as DBMS_WORKLOAD_REPOSITORY or Oracle Enterprise
Manager, you can mange the frequency and retention period of SQL that is stored in the AWR.

Oracle Database 11g: SQL Tuning Workshop 5 - 28

Managing AWR with PL/SQL


Although the primary interface for managing the AWR is Enterprise Manager, monitoring functions
can be managed with procedures in the DBMS_WORKLOAD_REPOSITORY package.
Snapshots are automatically generated for an Oracle Database; however, you can use
DBMS_WORKLOAD_REPOSITORY procedures to manually create, drop, and modify the snapshots
and baselines that are used by the ADDM. Snapshots and baselines are sets of historical data for
specific time periods that are used for performance comparisons. To invoke these procedures, a user
must be granted the DBA role.
Creating Snapshots
You can manually create snapshots with the CREATE_SNAPSHOT procedure if you want to capture
statistics at times different than those of the automatically generated snapshots. Here is an example:
Exec DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT ('ALL');
In this example, a snapshot for the instance is created immediately with the flush level specified to
the default flush level of TYPICAL. You can view this snapshot in the DBA_HIST_SNAPSHOT
view.

Oracle Database 11g: SQL Tuning Workshop 5 - 29

Managing AWR with PL/SQL (continued)


Dropping Snapshots
You can drop a range of snapshots using the DROP_SNAPSHOT_RANGE procedure. To view a list of
the snapshot IDs along with database IDs, check the DBA_HIST_SNAPSHOT view. For example,
you can drop the following range of snapshots:
Exec DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_RANGE - (low_snap_id =>
22, high_snap_id => 32, dbid => 3310949047);
In the example, the range of snapshot IDs to drop is specified from 22 to 32. The optional database
identifier is 3310949047. If you do not specify a value for dbid, the local database identifier is
used as the default value.
ASH data that belongs to the time period specified by the snapshot range is also purged when the
DROP_SNAPSHOT_RANGE procedure is called.
Modifying Snapshot Settings
You can adjust the interval and retention of snapshot generation for a specified database ID.
However, note that this can affect the precision of the Oracle diagnostic tools.
The INTERVAL setting specifies how often (in minutes) snapshots are automatically generated. The
RETENTION setting specifies how long (in minutes) snapshots are stored in the workload repository.
To adjust the settings, use the MODIFY_SNAPSHOT_SETTINGS procedure, as in the following
example:
Exec DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS( -retention
=> 43200, interval => 30, dbid => 3310949047);
In this example, the retention period is specified as 43,200 minutes (30 days), and the interval
between each snapshot is specified as 30 minutes. If NULL is specified, the existing value is
preserved. The optional database identifier is 3310949047. If you do not specify a value for dbid,
the local database identifier is used as the default value. You can check the current settings for your
database instance with the DBA_HIST_WR_CONTROL view.

Oracle Database 11g: SQL Tuning Workshop 5 - 30

Important AWR Views


You can view the AWR data on Oracle Enterprise Manager screens or in AWR reports. However, you
can also view the statistics directly from the following views:
V$ACTIVE_SESSION_HISTORY: This view displays active database session activity, sampled
once every second.
V$ metric views provide metric data to track the performance of the system. The metric views are
organized into various groups, such as event, event class, system, session, service, file, and
tablespace metrics. These groups are identified in the V$METRICGROUP view.
The DBA_HIST views contain historical data stored in the database. This group of views includes:
DBA_HIST_ACTIVE_SESS_HISTORY displays the history of the contents of the sampled inmemory active session history for recent system activity.
DBA_HIST_BASELINE displays information about the baselines captured in the system.
DBA_HIST_DATABASE_INSTANCE displays information about the database environment.
DBA_HIST_SNAPSHOT displays information about snapshots in the system.
DBA_HIST_SQL_PLAN displays SQL execution plans.
DBA_HIST_WR_CONTROL displays the settings for controlling AWR.

Oracle Database 11g: SQL Tuning Workshop 5 - 31

Querying the AWR


You can use the DBMS_XPLAN.DISPLAY_AWR() function to display all stored plans in the AWR.
In the example in the slide, you pass in a SQL_ID as an argument. SQL_ID is the SQL_ID of the
SQL statement in the cursor cache. The DISPLAY_AWR() function also takes the
PLAN_HASH_VALUE, DB_ID, and FORMAT parameters.
The steps to complete this example are as follows:
1. Execute the SQL statement:
SQL> select /* example */ * from hr.employees natural
join hr.departments;
2. Query V$SQL_TEXT to obtain the SQL_ID:
SQL> select sql_id, sql_text from v$SQL
where sql_text
like '%example%';
SQL_ID
SQL_TEXT
------------- ------------------------------------------F8tc4anpz5cdb select sql_id, sql_text from v$SQL
454rug2yva18w select /* example */ * from

3. Using the SQL_ID, verify that this statement has been captured in the DBA_HIST_SQLTEXT
dictionary view. If the query does not return rows, it indicates that the statement has not yet been
loaded in the AWR.
Oracle Database 11g: SQL Tuning Workshop 5 - 32

Querying the AWR (continued)


SQL> SELECT SQL_ID, SQL_TEXT FROM dba_hist_sqltext WHERE SQL_ID ='
454rug2yva18w';
no rows selected
You can take a manual AWR snapshot rather than wait for the next snapshot (which occurs every
hour). Then check to see if it has been captured in DBA_HIST_SQLTEXT:
SQL> exec dbms_workload_repository.create_snapshot;
PL/SQL procedure successfully completed.
SQL> SELECT SQL_ID, SQL_TEXT FROM dba_hist_sqltext WHERE SQL_ID
=' 454rug2yva18w';
SQL_ID
SQL_TEXT
-------------- ------------------------------454rug2yva18w
select /* example */ * from
4. Use the DBMS_XPLAN.DISPLAY_AWR () function to retrieve the execution plan:
SQL>SELECT PLAN_TABLE_OUTPUT FROM TABLE
(DBMS_XPLAN.DISPLAY_AWR('454rug2yva18w));

Oracle Database 11g: SQL Tuning Workshop 5 - 33

Generating SQL Reports from AWR Data


Since Oracle Database 10g, Release 2, it is possible to generate SQL reports from AWR data,
basically, the equivalent to sqrepsql.sql with Statspack. In 10.1.0.4.0, the equivalent to
sprepsql.sql is not available in AWR. However in 10gR2, the equivalent of sprepsql.sql
is available. In 10gR2, the AWR SQL report can be generated by calling the
$ORACLE_HOME/rdbms/admin/awrsqrpt.sql file.
You can display the plan information in AWR by using the display_awr table function in the
dbms_xplan PL/SQL package.
For example, this displays the plan information for a SQL_ID in AWR:
select * from table(dbms_xplan.display_awr('6g1p4s9ra6ag8'));
You can retrieve the appropriate value for the SQL statement of interest by querying the SQL_ID in
DBA_HIST_SQLTEXT column.

Oracle Database 11g: SQL Tuning Workshop 5 - 34

SQL Monitoring: Overview


The SQL monitoring feature is enabled by default when the STATISTICS_LEVEL initialization
parameter is either set to ALL or TYPICAL (the default value).
Additionally, the CONTROL_MANAGEMENT_PACK_ACCESS parameter must be set to
DIAGNOSTIC+TUNING (the default value) because SQL monitoring is a feature of the Oracle
Database Tuning Pack.
By default, SQL monitoring is automatically started when a SQL statement runs parallel, or when it
has consumed at least five seconds of the CPU or I/O time in a single execution.
As mentioned, SQL monitoring is active by default. However, two statement-level hints are available
to force or prevent a SQL statement from being monitored. To force SQL monitoring, use the
MONITOR hint. To prevent the hinted SQL statement from being monitored, use the NO_MONITOR
hint.
You can monitor the statistics for SQL statement execution using the V$SQL_MONITOR and
V$SQL_PLAN_MONITOR views.
After monitoring is initiated, an entry is added to the dynamic performance V$SQL_MONITOR view.
This entry tracks key performance metrics collected for the execution, including the elapsed time,
CPU time, number of reads and writes, I/O wait time, and various other wait times. These statistics
are refreshed in near real time as the statement executes, generally once every second.

Oracle Database 11g: SQL Tuning Workshop 5 - 35

SQL Monitoring: Overview (continued)


After the execution ends, monitoring information is not deleted immediately, but is kept in the
V$SQL_MONITOR view for at least one minute. The entry is eventually deleted so its space can be
reclaimed as new statements are monitored.
The V$SQL_MONITOR and V$SQL_PLAN_MONITOR views can be used in conjunction with the
following views to get additional information about the execution that is monitored:
V$SQL, V$SQL_PLAN, V$ACTIVE_SESSION_HISTORY, V$SESSION_LONGOPS, and
V$SESSION
Instead, you can use the SQL monitoring report to view SQL monitoring data.
The SQL monitoring report is also available in a GUI version through Enterprise Manager.

Oracle Database 11g: SQL Tuning Workshop 5 - 36

SQL Monitoring Report: Example


In this example, it is assumed that you SELECT from SALES from a different session than the one
used to print the SQL monitoring report.
The DBMS_SQLTUNE.REPORT_SQL_MONITOR function accepts several input parameters to
specify the execution, the level of detail in the report, and the report type (TEXT, HTML, or XML). By
default, a text report is generated for the last execution that was monitored if no parameters are
specified as shown in the example in the slide.
After the SELECT statement is started, and while it executes, you print the SQL monitoring report
from a second session.
From the report, you can see that the SELECT statement executes currently.
The Global Information section gives you some important information:
To uniquely identify two executions of the same SQL statement, a composite key called an
execution key is generated. This execution key consists of three attributes, each corresponding to
a column in V$SQL_MONITOR:
- SQL identifier to identify the SQL statement (SQL_ID)
- An internally generated identifier to ensure that this primary key is truly unique
(SQL_EXEC_ID)
- A start execution time stamp (SQL_EXEC_START)
The report also shows you some important statistics calculated so far.
Oracle Database 11g: SQL Tuning Workshop 5 - 37

SQL Monitoring Report: Example (continued)


The report then displays the execution path currently used by your statement. SQL monitoring gives
you the display of the current operation that executes in the plan. This enables you to detect parts of
the plan that are the most time consuming, so that you can focus your analysis on those parts. The
running operation is marked by an arrow in the Id column of the report.
The Time Active(s) column shows how long the operation has been active (the delta in seconds
between the first and the last active time).
The Start Active column shows, in seconds, when the operation in the execution plan started relative
to the SQL statement execution start time. In this report, the table access full operation at Id 2 was
the first to start (+1s Start Active) and ran for the first 23 seconds so far.
The Starts column shows the number of times the operation in the execution plan was executed.
The Rows (Actual) column indicates the number of rows produced, and the Rows (Estim) column
shows the estimated cardinality from the optimizer.
The Activity (percent) and Activity Detail (sample #) columns are derived by joining the
V$SQL_PLAN_MONITOR and V$ACTIVE_SESSION_HISTORY views. Activity (percent) shows
the percentage of database time consumed by each operation of the execution plan. Activity Detail
(sample#) shows the nature of that activity (such as CPU or wait event).

Oracle Database 11g: SQL Tuning Workshop 5 - 38

SQL Monitoring Report: Example (continued)


In this report, the Activity Detail (sample #) column shows that most of the database time, 100%, is
consumed by operation Id 2 (TABLE ACCESS FULL of SALES). So far, this activity consists of
4 samples, which are only attributed to CPU.
The last column, Progress, shows progress monitoring information for the operation from the
V$SESSION_LONGOPS view. In this report, it shows that, so far, the TABLE ACCESS FULL
operation is 74% complete. This column only appears in the report after a certain amount of time,
and only for the instrumented row sources.
Note: Not shown by this particular report, the Memory and Temp columns indicate the amount of
memory and temporary space consumed by corresponding operation of the execution plan.

Oracle Database 11g: SQL Tuning Workshop 5 - 39

Interpreting an Execution Plan


Explain plan output is a representation of a tree of row sources.
Each step (line in the execution plan or node in the tree) represents a row source.
The explain plan utility indents nodes to indicate that they are the children of the parent above it.
The order of the nodes under the parent indicates the order of execution of the nodes within that
level. If two steps are indented at the same level, the first one is executed first.
In the tree format, the leaf at the left on each level of the tree is where the execution starts.
The steps of the execution plan are not performed in the order in which they are numbered. there is a
parentchild relationship between steps.
In PLAN_TABLE and V$SQL_PLAN, the important elements to retrieve the tree structure are the
ID, PARENT_ID, and POSITION columns. In a trace file, these columns correspond to the id,
pid, and pos fields, respectively.
One way to read an execution plan is by converting it into a graph that has a tree structure. You can
start from the top, with id=1, which is the root node in the tree. Next, you must find the operations
that feed this root node. That is accomplished by operations, which have parent_id or pid with
value 1.
Note: The course focuses on serial plans and does not discusses parallel execution plans.

Oracle Database 11g: SQL Tuning Workshop 5 - 40

Interpreting an Execution Plan (continued)


To draw plan as a tree, do the following:
1. Take the ID with the lowest number and place it at the top.
2. Look for rows which have a PID (parent) equal to this value.
3. Place these in the tree below the Parent according to their POS values from the lowest to the
highest, ordered from left to right.
4. After all the IDs for a parent have been found, move down to the next ID and repeat the process,
finding new rows with the same PID.
The first thing to determine in an explain plan is which node is executed first. The method in the
slide explains this, but sometimes with complicated plans it is difficult to do this and also difficult to
follow the steps through to the end. Large plans are exactly the same as smaller ones, but with more
entries. The same basic rules apply. You can always collapse the plan to hide a branch of the tree
which does not consume much of the resources.
Standard explain plan interpretation:
1. Start at the top.
2. Move down the row sources until you get to one which produces data, but does not consume
any. This is the start row source.
3. Look at the siblings of this row source. These row sources are executed next.
4. After the children are executed, the parent is executed next.
5. Now that this parent and its children are completed, work back up the tree, and look at the
siblings of the parent row source and its parents. Execute as before.
6. Move back up the plan until all row sources are exhausted.
Standard tree interpretation:
1. Start at the top.
2. Move down the tree to the left until you reach the left node. This is executed first.
3. Look at the siblings of this row source. These row sources are executed next.
4. After the children are executed, the parent is executed next.
5. Now that this parent and its children are completed, work back up the tree, and look at the
siblings of the parent row source and its parents. Execute as before.
6. Move back up the tree until all row sources are exhausted.
If you remember the few basic rules of explain plans and with some experience, you can read most
plans easily.

Oracle Database 11g: SQL Tuning Workshop 5 - 41

Execution Plan Interpretation: Example 1


You start with an example query to illustrate how to interpret an execution plan. The slide shows a
query with its associated execution plan and the same plan in the tree format.
The query tries to find employees who have salaries outside the range of salaries in the salary grade
table. The query is a SELECT statement from two tables with a subquery based on another table to
check the salary grades.
See the execution order for this query. Based on the example in the slide, and from the previous slide,
the execution order is 3 5 4 2 6 1:
3: The plan starts with a full table scan of EMP (ID=3).
5: The rows are passed back to the controlling nested loops join step (ID=2), which uses them
to execute the lookup of rows in the PK_DEPT index in ID=5.
4: The ROWIDs from the index are used to lookup the other information from the DEPT table in
ID=4.
2: ID=2, the nested loops join step, is executed until completion.
6: After ID=2 has exhausted its row sources, a full table scan of SALGRADE in ID=6 (at the
same level in the tree as ID=2, therefore, its sibling) is executed.
1: This is used to filter the rows from ID2 and ID6.

Oracle Database 11g: SQL Tuning Workshop 5 - 42

Execution Plan Interpretation: Example 1 (continued)


Note that children are executed before parents, so although structures for joins must be set up before
the child execution, the children are notated as executed first. Probably, the easiest way is to consider
it as the order in which execution completes, so for the NESTED LOOPS join at ID=2, the two
children {ID=3 and ID=4 (together with its child)} must have completed their execution before
ID=2 can be completed.

Oracle Database 11g: SQL Tuning Workshop 5 - 43

Execution Plan Interpretation: Example 1 (continued)


The example in the slide is a plan dump from V$SQL_PLAN with STATISTICS_LEVEL set to
ALL. This report shows you some important additional information compared to the output of the
EXPLAIN PLAN command:
A-Rows corresponds to the number of rows produced by the corresponding row source.
Buffers corresponds to the number of consistent reads done by the row source.
Starts indicates how many times the corresponding operation was processed.
For each row from the EMP table, the system gets its ENAME, SAL, JOB, and DEPTNO.
Then the system accesses the DEPT table by its unique index (PK_DEPT) to get DNAME using
DEPTNO from the previous result set.
If you observe the statistics closely, the TABLE ACCESS FULL operation on the EMP table (ID=3)
is started once. However, operations from ID 5 and 4 are started 14 times; once for each EMP rows.
At this step (ID=2), the system gets all ENAME, SAL, JOB, and DNAME.
The system now must filter out employees who have salaries outside the range of salaries in the
salary grade table. To do that, for each row from ID=2, the system accesses the SALGRADE table
using a FULL TABLE SCAN operation to check if the employees salary is outside the salary range.
This operation only needs to be done 12 times in this case because at run time the system does the
check for each distinct salary, and there are 12 distinct salaries in the EMP table.

Oracle Database 11g: SQL Tuning Workshop 5 - 44

Execution Plan Interpretation: Example 2


This query retrieves names, department names, and addresses for employees whose departments are
located in Seattle and who have managers.
For formatting reasons, the explain plan has the ID in the first column, and PID in the second
column. The position is reflected by the indentation. The execution plan shows two nested loops join
operations.
You follow the steps from the previous example:
1. Start at the top. ID=0
2. Move down the row sources until you get to the one, which produces data, but does not consume
any. In this case, ID 0, 1, 2, and 3 consume data. ID=4 is the first row source that does not
consume any. This is the start row source. ID=4 is executed first. The index range scan
produces ROWIDs, which are used to lookup in the LOCATIONS table in ID=3.
3. Look at the siblings of this row source. These row sources are executed next. The sibling at the
same level as ID=3 is ID=5. Node ID=5 has a child ID=6, which is executed before it. This is
another index range scan producing ROWIDs, which are used to lookup in the DEPARTMENTS
table in ID=5.
4. After the children operation, the parent operation is next. The NESTED LOOPS join at ID=2 is
executed next bringing together the underlying data.

Oracle Database 11g: SQL Tuning Workshop 5 - 45

Execution Plan Interpretation: Example 2 (continued)


5. Now that this parent and its children are completed, walk back up the tree, and look at the
siblings of the parent row source and its parents. Execute as before. The sibling of ID=2 at the
same level in the plan is ID=7. This has a child ID=8, which is executed first. The index unique
scan produces ROWIDs, which are used to lookup in the EMPLOYEES table in ID=7.
6. Move back up the plan until all row sources are exhausted. Finally this is brought together with
the NESTED LOOPS at ID=1, which passes the results back to ID=0.
7. The execution order is: 4 3 6 5 2 8 7 1 0
Here is the complete description of this plan:
The inner nested loops is executed first using LOCATIONS as the driving table, using an index
access on the CITY column. This is because you search for departments in Seattle only.
The result is joined with the DEPARTMENTS table, using the index on the LOCATION_ID join
column; the result of this first join operation is the driving row source for the second nested loops
join.
The second join probes the index on the EMPLOYEE_ID column of the EMPLOYEES table. The
system can do that because it knows (from the first join) the employee ID of all managers of
departments in Seattle. Note that this is a unique scan because it is based on the primary key.
Finally, the EMPLOYEES table is accessed to retrieve the last name.

Oracle Database 11g: SQL Tuning Workshop 5 - 46

Execution Plan Interpretation: Example 3


See the execution plan in the slide. Try to find the order in which the plan is executed and deduce
what is the join order (order in which the system joins tables). Again, ID is in the first column and
PID in the second column. The position is reflected by the indentation. It is important to recognize
what the join order of an execution plan is, to be able to find your plan into a 10053 event trace file.
Here is the interpretation of this plan:
The system first hashes the T3 table (Operation ID=3) into memory.
Then it hashes the T1 table (Operation ID=5) into memory.
Then the scan of the T2 table begins (Operation ID=6).
The system picks a row from T2 and probes T1 (T1.i=T2.i).
If the row survives, the system probes T3 (T1.i=T3.i).
If the row survives, the system sends it to next operation.
The system outputs the maximum value from the previous result set.
In conclusion, the execution order is : 3 5 6 4 2 1
The join order is: T1 T2 T3
You can also use Enterprise Manager to understand execution plans, especially because it displays
the Order column.
Note: A special hint was used to make sure T3 would be first in the plan.

Oracle Database 11g: SQL Tuning Workshop 5 - 47

Reading More Complex Execution Plans


The plan at the left comes from the query (in the slide) on the data dictionary. It is so long that it is
very difficult to apply the previous method to interpret it and locate the first operation.
You can always collapse a plan to make it readable. This is illustrated at the right where you can see
the same plan collapsed. As shown, this is easy to do when using the Enterprise Manager graphical
interface. You can clearly see that this plan is a UNION ALL of two branches. Your knowledge about
the data dictionary enables you to understand that the two branches correspond to dictionarymanaged tablespaces and locally-managed ones. Your knowledge about your database enables you to
know that there are no dictionary-managed tablespaces. So, if there is a problem, it must be on the
second branch. To get confirmation, you must look at the plan information and execution statistics of
each row source to locate the part of the plan that consumes most resources. Then, you just need to
expand the branch you want to investigate (where time is being spent). To use this method, you must
look at the execution statistics that are generally found in V$SQL_PLAN_STATISTICS or in the
tkprof reports generated from trace files. For example, tkprof cumulates for each parent
operation the time it takes to execute itself plus the sum of all its child operation time.

Oracle Database 11g: SQL Tuning Workshop 5 - 48

Reviewing the Execution Plan


When you tune a SQL statement in an online transaction processing (OLTP) environment, the goal is
to drive from the table that has the most selective filter. This means that there are fewer rows passed
to the next step. If the next step is a join, this means fewer rows are joined. Check to see whether the
access paths are optimal. When you examine the optimizer execution plan, look for the following:
The plan is such that the driving table has the best filter.
The join order in each step means that the fewest number of rows are returned to the next step
(that is, the join order should reflect going to the best not-yet-used filters).
The join method is appropriate for the number of rows being returned. For example, nested loop
joins through indexes may not be optimal when many rows are returned.
Views are used efficiently. Look at the SELECT list to see whether access to the view is
necessary.
There are any unintentional Cartesian products (even with small tables).
Each table is being accessed efficiently: Consider the predicates in the SQL statement and the
number of rows in the table. Look for suspicious activity, such as a full table scans on tables
with large number of rows, which have predicates in the WHERE clause. Also, a full table scan
might be more efficient on a small table, or to leverage a better join method (for example,
hash_join) for the number of rows returned.
If any of these conditions are not optimal, consider restructuring the SQL statement or the indexes
available on the tables.
Oracle Database 11g: SQL Tuning Workshop 5 - 49

Looking Beyond Execution Plans


The execution plan alone cannot differentiate between well-tuned statements and those that perform
poorly. For example, an EXPLAIN PLAN output that shows that a statement uses an index does not
necessarily mean that the statement runs efficiently. Sometimes indexes can be extremely inefficient.
It is best to use EXPLAIN PLAN to determine an access plan, and then later prove that it is the
optimal plan through testing. When evaluating a plan, you should examine the statements actual
resource consumption.
The rest of this course is intended to show you various methods to achieve this.

Oracle Database 11g: SQL Tuning Workshop 5 - 50

Oracle Database 11g: SQL Tuning Workshop 5 - 51

Oracle Database 11g: SQL Tuning Workshop 5 - 52

Oracle Database 11g: SQL Tuning Workshop 6 - 2

The Star Schema Model


The star schema is the simplest data warehouse schema. It is called a star schema because the entityrelationship diagram of this schema resembles a star, with points radiating from a central table. The
center of the star consists of one or more fact tables and the points of the star are the dimension
tables. A star schema is characterized by one or more very large fact tables that contain the primary
information in the data warehouse and a number of much smaller dimension tables (or lookup
tables), each of which contains information about the entries for a particular attribute in the fact
table. A star query is a join between a fact table and a number of dimension tables. Each dimension
table is joined to the fact table using a primary key to foreign key join, but the dimension tables are
not joined to each other. The cost-based optimizer (CBO) recognizes star queries and generates
efficient execution plans for them. A typical fact table contains keys and measures. For example, in
the Sales History schema, the sales fact table contains the quantity_sold, amount, and
cost measures, and the cust_id, time_id, prod_id, channel_id, and promo_id keys.
The dimension tables are customers, times, products, channels, and promotions. The
products dimension table, for example, contains information about each product number that
appears in the fact table.
Note: It is easy to generalize this model to include more than one fact table.

Oracle Database 11g: SQL Tuning Workshop 6 - 3

The Snowflake Schema Model


The snowflake schema is a more complex data warehouse model than a star schema, and is a type of
star schema. It is called a snowflake schema because the diagram of the schema resembles a
snowflake. Snowflake schemas normalize dimensions to eliminate redundancy. That is, the
dimension data has been grouped into multiple tables instead of one large table. For example, a
product dimension table in a star schema might be normalized into a products table, a
product_category table, and a product_manufacturer table in a snowflake schema or,
as shown in the slide, you can normalize the customers table using the countries table. While
this saves space, it increases the number of dimension tables and requires more foreign key joins.
The result is more complex queries and reduced query performance.
Note: It is suggested that you select a star schema over a snowflake schema unless you have a clear
reason not to.

Oracle Database 11g: SQL Tuning Workshop 6 - 4

Star Query: Example


Consider the star query in the slide. For the star transformation to operate, it is supposed that the
sales table of the Sales History schema has bitmap indexes on the time_id, channel_id, and
cust_id columns.

Oracle Database 11g: SQL Tuning Workshop 6 - 5

Execution Plan Without Star Transformation


Before you see the benefits of a star transformation, you should review how a join on a star schema is
processed without their benefits.
The fundamental issue with the plan in the slide is that the query always starts joining the SALES
table to a dimension table. This results in a very large number of rows that can only be trimmed
down by the other parent joins in the execution plan.

Oracle Database 11g: SQL Tuning Workshop 6 - 6

Star Transformation
To get the best possible performance for star queries, it is important to follow some basic guidelines:
A bitmap index should be built on each of the foreign key columns of the fact table or tables.
The STAR_TRANSFORMATION_ENABLED initialization parameter should be set to TRUE.
This enables an important optimizer feature for star queries. It is set to FALSE by default for
backwards compatibility.
When a data warehouse satisfies these conditions, the majority of the star queries that run in the data
warehouse use a query execution strategy known as star transformation. Star transformation provides
very efficient query performance for star queries.
Star transformation is a powerful optimization technique that relies on implicitly rewriting (or
transforming) the SQL of the original star query. The end user never needs to know any of the details
about the star transformation. The systems CBO automatically selects star transformation where
appropriate. Oracle processes a star query using two basic phases:
The first phase retrieves exactly the necessary rows from the fact table (the result set). Because
this retrieval utilizes bitmap indexes, it is very efficient.
The second phase joins this result set to the dimension tables. This operation is called a
semijoin.
Note: At least three tables are used in the query (two dimensions and one fact).

Oracle Database 11g: SQL Tuning Workshop 6 - 7

Star Transformation: Considerations


Star transformation is not supported for tables with any of the following characteristics:
Queries with a table hint that is incompatible with a bitmap access path
Queries that contain bind variables
Tables with too few bitmap indexes. There must be a bitmap index on a fact table column for the
optimizer to generate a subquery for it.
Remote fact tables. However, remote dimension tables are allowed in the subqueries that are
generated.
Antijoined tables
Tables that are already used as a dimension table in a subquery
Tables that are really unmerged views, which are not view partitions

Oracle Database 11g: SQL Tuning Workshop 6 - 8

Star Transformation: Rewrite Example


The system processes the query seen earlier in two phases. In the first phase, the system uses the
filters against the dimensional tables to retrieve the dimensional primary keys, which match those
filters. Then the system uses those primary keys to probe the bitmap indexes on the foreign key
columns of the fact table to identify and retrieve only the necessary rows from the fact table. That is,
the system retrieves the result set from the sales table by using essentially the rewritten query in the
slide.
Note: The SQL in the slide is a theoretical SQL statement that represents what goes on in phase I.

Oracle Database 11g: SQL Tuning Workshop 6 - 9

Retrieving Fact Rows from One Dimension


The slide shows retrieval of fact table rows using only one dimension table. Based on the
corresponding dimension filter predicates, like in t.calendar_quarter_desc IN ('1999Q1','1999-Q2') from the example in the previous slide, the system scans the dimension table,
and for each corresponding row, it probes the corresponding fact bitmap index and fetches the
corresponding bitmap.
BITMAP KEY ITERATION makes each key coming from its left input a lookup key for the index
on its right input, and returns all bitmaps fetched by that index. Note that its left input supplies join
keys from the dimension table in this case.
The last step in this tree merges all fetched bitmaps from the previous steps. This merge operation
produces one bitmap that can be described as representing the rows of the fact table that join with the
rows of interest from the dimension table.
Note: BITMAP_MERGE_AREA_SIZE plays an important role in tuning the performance of this
operation when using the shared server mode. The system does not recommend using the
BITMAP_MERGE_AREA_SIZE parameter unless the instance is configured with the shared server
option. The system recommends that you enable automatic sizing of SQL working areas by setting
PGA_AGGREGATE_TARGET instead. BITMAP_MERGE_AREA_SIZE is retained for backward
compatibility.

Oracle Database 11g: SQL Tuning Workshop 6 - 10

Retrieving Fact Rows from All Dimensions


During the first phase, the steps mentioned in the previous slide are repeated for each dimension
table. So each BITMAP MERGE in the plan generates a bitmap for a single dimension table. To
identify all rows from the fact table that are of interest, the system must intersect all generated
bitmaps. This is to eliminate fact rows that join with one dimension, but not with all of them. This is
achieved by performing a very efficient BITMAP AND operation on all the bitmaps generated for
each dimension. The resulting bitmap can be described as representing the rows from the fact table
that are known to join with all the qualified dimension rows.
Note: Until now, only fact bitmap indexes and dimension tables were used. To further access the fact
table, the system must convert the generated bitmap to a rowids set.

Oracle Database 11g: SQL Tuning Workshop 6 - 11

Joining the Intermediate Result Set with Dimensions


After the result set is determined, the system enters phase 2 of the star transformation algorithm. In
this phase, it is needed to join the sales data, corresponding to the result set, with the dimension
tables data used to group the rows and pertaining to the querys select list.
Note that the graphic in the slide shows that a hash join is performed between the fact table and its
dimensions. Although a hash join is statistically the most-used technique to join rows in a star query,
this might not be always true, as this is evaluated by the CBO.

Oracle Database 11g: SQL Tuning Workshop 6 - 12

Star Transformation Plan: Example 1


This is a possible plan to answer the query shown in the Execution Plan Without Star
Transformation section. Note that for formatting purposes, only the channels and times dimensions
are shown. It is easy to generalize the case for n dimensions.
Note: It is supposed that sales is not partitioned.

Oracle Database 11g: SQL Tuning Workshop 6 - 13

Star Transformation: Further Optimization


When you look at the previous execution plan, you see that each dimension table is accessed
twiceonce during the first phase, where the system determines the necessary fact table rows, and
once when joining the fact rows to each dimension table during the second phase. This might be a
performance issue if the dimension tables are big, and there is no fast access path to them for solving
the problem. In such cases, the system might decide to create temporary tables containing
information needed for both phases. This decision is made if the costs for creating a temporary table,
consisting of the result set for both the predicate and the join columns on the dimension table, is
cheaper than accessing the dimension table twice. In the previous execution plan example, the
TIMES and CHANNELS tables are very small, and accessing them using a full table scan is not a big
deal.
The creation of these temporary tables and the data insertion are shown in the execution plan. The
name of those temporary tables is system-generated and varies. In the slide, you see an extract from
an execution plan using temporary tables for the CUSTOMERS table.
Note: In addition, temporary tables are not be used by star transformation under the following
conditions:
The database is in read-only mode.
The star query is part of a transaction that is in serializable mode.

Oracle Database 11g: SQL Tuning Workshop 6 - 14

Using Bitmap Join Indexes


The volume of data that must be joined can be reduced if the join indexes used as joins have already
been precalculated.
In addition, the join indexes, which contain multiple dimension tables can eliminate bitwise
operations, which are necessary in the star transformation with existing bitmap indexes.
Finally, bitmap join indexes are much more efficient in storage than materialized join views (MJVs),
which do not compress rowids of the fact tables.
Assume that you have created the additional index structure mentioned in the slide.

Oracle Database 11g: SQL Tuning Workshop 6 - 15

Star Transformation Plan: Example 2


The processing of the same star query using the bitmap join index is similar to the previous example.
The only difference is that the system uses the join index instead of a single-table bitmap index to
access the times data in the first phase of the star query.
The difference between this plan as compared to the previous one is that the inner part of the bitmap
index scan for the times dimension has no subselect in the rewritten query for phase 1. This is
because the join predicate information on times.calendar_quarter_desc can be satisfied
with the sales_q_bjx bitmap join index.
Note that access to the join index is done twice because the corresponding querys predicate is
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')

Oracle Database 11g: SQL Tuning Workshop 6 - 16

Star Transformation Hints


The STAR_TRANSFORMATION hint makes the optimizer use the best plan in which the
transformation has been used. Without the hint, the optimizer could make a cost-based decision
to use the best plan generated without the transformation, instead of the best plan for the
transformed query. Even if the hint is given, there is no guarantee that the transformation takes
place. The optimizer only generates the subqueries if it seems reasonable to do so. If no
subqueries are generated, there is no transformed query, and the best plan for the untransformed
query is used, regardless of the hint.
The FACT hint is used in the context of the star transformation to indicate to the transformation
that the hinted table should be considered as a fact table and all other tables regardless of their
size are considered as dimensions.
The NO_FACT hint is used in the context of the star transformation to indicate to the
transformation that the hinted table should not be considered as a fact table.
Note: The FACT and NO_FACT hints might be useful only in case there are more than one fact table
accessed in the star query.

Oracle Database 11g: SQL Tuning Workshop 6 - 17

Bitmap Join Indexes: Join Model 1


In the following three slides, F represents the fact table, D, the dimension table, PK a primary key,
and FK a foreign key.
A bitmap join index can be used in the SELECT statement in the slide to avoid the join operation.
Similar to the materialized join view, a bitmap join index precomputes the join and stores it as a
database object. The difference is that a materialized join view materializes the join into a table while
a bitmap join index materializes the join into a bitmap index.
Note: C1 is the indexed column in the dimension table.

Oracle Database 11g: SQL Tuning Workshop 6 - 18

Bitmap Join Indexes: Join Model 2


The model in the slide is an extension of model 1, requiring a concatenated bitmap join index to
represent it.
Note that BJX, in this case, can also be used to answer the following select statement:
select sum(f.facts) from d,f where d.pk=f.fk and d.c1=1
This is due to the fact that D.C1 is the leading part of the BJX.

Oracle Database 11g: SQL Tuning Workshop 6 - 19

Bitmap Join Indexes: Join Model 3


This model also requires the concatenated bitmap join index shown in the slide. In this case, two
dimension tables are used.

Oracle Database 11g: SQL Tuning Workshop 6 - 20

Bitmap Join Indexes: Join Model 4


The slide shows a snowflake model that involves joins between two or more dimension tables. It can
be expressed by a bitmap join index. The bitmap join index can be either single or concatenated
depending on the number of columns in the dimension tables to be indexed. A bitmap join index on
D1.C1 with a join between D1 and D2 and a join between D2 and F can be created as shown in the
slide with BJX.

Oracle Database 11g: SQL Tuning Workshop 6 - 21

Oracle Database 11g: SQL Tuning Workshop 6 - 22

Oracle Database 11g: SQL Tuning Workshop 6 - 23

Oracle Database 11g: SQL Tuning Workshop 7 - 2

Optimizer Statistics
Optimizer statistics describe details about the database and the objects in the database. These
statistics are used by the query optimizer to select the best execution plan for each SQL statement.
Because the objects in a database change constantly, statistics must be regularly updated so that they
accurately describe these database objects. Statistics are maintained automatically by Oracle
Database, or you can maintain the optimizer statistics manually using the DBMS_STATS package.

Oracle Database 11g: SQL Tuning Workshop 7 - 3

Types of Optimizer Statistics


Most of the optimizer statistics are listed in the slide.
Starting with Oracle Database 10g, index statistics are automatically gathered when the index is
created or rebuilt.
Note: The statistics mentioned in this slide are optimizer statistics, which are created for query
optimization and are stored in the data dictionary. These statistics should not be confused with
performance statistics visible through V$ views.

Oracle Database 11g: SQL Tuning Workshop 7 - 4

Table Statistics (DBA_TAB_STATISTICS)


NUM_ROWS
This is the basis for cardinality computations. Row count is especially important if the table is the
driving table of a nested loops join, as it defines how many times the inner table is probed.
BLOCKS
This is the number of used data blocks. Block count in combination with
DB_FILE_MULTIBLOCK_READ_COUNT gives the base table access cost.
EMPTY_BLOCKS
Number of empty (never used) data blocks in the table. This is the blocks between the used data
blocks and the high-water mark.
AVG_SPACE
This is the average amount of free space, in bytes, in a data block allocated to the table.
CHAIN_CNT
This is the number of rows in the table that are chained from one data block to another, or which
have migrated to a new block, requiring a link to preserve the old ROWID.
AVG_ROW_LEN
This is the average length of a row in the table in bytes.
STALE_STATS
Oracle Database 11g: SQL Tuning Workshop 7 - 5

This tells you if statistics are valid on the corresponding table.

Oracle Database 11g: SQL Tuning Workshop 7 - 5

Index Statistics (DBA_IND_STATISTICS)


In general, to select an index access, the optimizer requires a predicate on the prefix of the index
columns. However, in case there is no predicate and all the columns referenced in the query are
present in an index, the optimizer considers using a full index scan versus a full table scan.
BLEVEL
This is used to calculate the cost of leaf block lookups. It indicates the depth of the index from its
root block to its leaf blocks. A depth of 0 indicates that the root block and leaf block are the same.
LEAF_BLOCKS
This is used to calculate the cost of a full index scan.
CLUSTERING_FACTOR
This measures the order of the rows in the table based on the values of the index. If the value is near
the number of blocks, the table is very well ordered. In this case, the index entries in a single leaf
block tend to point to the rows in the same data blocks. If the value is near the number of rows, the
table is very randomly ordered. In this case, it is unlikely that the index entries in the same leaf block
point to rows in the same data blocks.
STALE_STATS
This tells you if the statistics are valid in the corresponding index.

Oracle Database 11g: SQL Tuning Workshop 7 - 6

Index Statistics (DBA_IND_STATISTICS) (continued)


DISTINCT_KEYS
This is the number of distinct indexed values. For indexes that enforce UNIQUE and PRIMARY KEY
constraints, this value is the same as the number of rows in the table.
AVG_LEAF_BLOCKS_PER_KEY
This is the average number of leaf blocks in which each distinct value in the index appears, rounded
to the nearest integer. For indexes that enforce UNIQUE and PRIMARY KEY constraints, this value
is always 1 (one).
AVG_DATA_BLOCKS_PER_KEY
This is the average number of data blocks in the table that are pointed to by a distinct value in the
index rounded to the nearest integer. This statistic is the average number of data blocks that contain
rows that contain a given value for the indexed columns.
NUM_ROWS
It is the number of rows in the index.

Oracle Database 11g: SQL Tuning Workshop 7 - 7

Index Clustering Factor


The system performs input/output (I/O) by blocks. Therefore, the optimizers decision to use full
table scans is influenced by the percentage of blocks accessed, not rows. This is called the index
clustering factor. If the blocks contain single rows, the rows accessed and blocks accessed are the
same.
However, most tables have multiple rows in each block. Consequently, the desired number of rows
could be clustered together in a few blocks, or they could be spread out over a larger number of
blocks. In addition to information, such as the level of the B*-tree, the number of leaf blocks, and the
index selectivity, the cost formula of an index range scan also includes the clustering factor. This is
because a lower clustering factor indicates that the individual rows are concentrated within fewer
blocks in the table. A high clustering factor indicates that the individual rows are scattered more
randomly across the blocks in the table. Therefore, a high clustering factor means that it costs more
to use an index range scan to fetch rows by ROWID because more blocks in the table need to be
visited to return the data. In real life scenarios, it appears that the clustering factor plays an important
role in determining the cost of an index range scan simply because the number of leaf blocks and the
height of the B*-tree are relatively small compared to the clustering factor and tables selectivity.
Note: If you have more than one index on a table, the clustering factor for one index might be small
while at the same time the clustering factor for another index might be large. An attempt to
reorganize the table to improve the clustering factor for one index can cause degradation of the
Oracle Database 11g: SQL Tuning Workshop 7 - 8

clustering factor of the other index.

Oracle Database 11g: SQL Tuning Workshop 7 - 8

Index Clustering Factor (continued)


The clustering factor is computed in the CLUSTERING_FACTOR column of the DBA_INDEXES
view when you gather statistics on the index. The way it is computed is relatively easy. You read the
index from left to right, and for each indexed entry, you add one to the clustering factor if the
corresponding row is located in a different block than the one from the previous row. Based on this
algorithm, the smallest possible value for the clustering factor is the number of blocks, and the
highest possible value is the number of rows.
The example in the slide shows how the clustering factor can affect cost. Assume the following
situation: There is a table with 9 rows, there is a nonunique index on col1 for table, the c1 column
currently stores the values A, B, and C, and the table only has three Oracle blocks.
Case 1: If the same rows in the table are arranged so that the index values are scattered across
the table blocks (rather than collocated), the index clustering factor is high.
Case 2: The index clustering factor is low for the rows as they are collocated in the same block
for the same value.
Note: For bitmap indexes, the clustering factor is not applicable and is not used.

Oracle Database 11g: SQL Tuning Workshop 7 - 9

Column Statistics (DBA_TAB_COL_STATISTICS)


NUM_DISTINCT is used in selectivity calculations, for example, 1/NDV
LOW_VALUE and HIGH_VALUE: The cost-based optimizer (CBO) assumes uniform distribution of
values between low and high values for all data types. These values are used to determine range
selectivity.
NUM_NULLS helps with selectivity of nullable columns and the IS NULL and IS NOT NULL
predicates.
DENSITY is only relevant for histograms. It is used as the selectivity estimate for nonpopular values.
Can be thought of as the probability of finding one particular value in this column. It is calculated
depending on whether there is a histogram on the column, and its type.
NUM_BUCKETS is the number of buckets in histogram for the column.
HISTOGRAM indicates the existence or type of the histogram: NONE, FREQUENCY, HEIGHT
BALANCED

Oracle Database 11g: SQL Tuning Workshop 7 - 10

Histograms
A histogram captures the distribution of different values in a column, so it yields better selectivity
estimates. Having histograms on columns that contain skewed data or values with large variations in
the number of duplicates help the query optimizer generate good selectivity estimates and make
better decisions regarding index usage, join orders, join methods, and so on.
Without histograms, an even distribution is assumed. If a histogram is available on a column, the
estimator uses it instead of the number of distinct values.
When creating histograms, Oracle Database uses two different types of histogram representations
depending on the number of distinct values found in the corresponding column. When you have a
data set with less than 254 distinct values, and the number of histogram buckets is not specified, the
system creates a frequency histogram. If the number of distinct values is greater than the required
number of histogram buckets, the system creates a height-balanced histogram.
You can find information about histograms in these dictionary views: DBA_TAB_HISTOGRAMS,
DBA_PART_HISTOGRAMS, and DBA_SUBPART_HISTOGRAMS
Note: Gathering histogram statistics is the most resource-consuming operation in gathering statistics.

Oracle Database 11g: SQL Tuning Workshop 7 - 11

Frequency Histograms
For the example in the slide, assume that you have a column that is populated with 40,001 numbers.
You only have ten distinct values: 1, 3, 5, 7, 10, 16, 27, 32, 39, and 49. Value 10 is the most popular
value with 16,293 occurrences.
When the requested number of buckets equals (or is greater than) the number of distinct values, you
can store each different value and record exact cardinality statistics. In this case, in
DBA_TAB_HISTOGRAMS, the ENDPOINT_VALUE column stores the column value and the
ENDPOINT_NUMBER column stores the row count for that column value.
The row counts are stored in cumulative form as this can avoid some calculation for range scans. The
actual number of row counts is shown by the curve in the slide for clarity; only the
ENDPOINT_VALUE and ENDPOINT_NUMBER columns are stored in the data dictionary.

Oracle Database 11g: SQL Tuning Workshop 7 - 12

Viewing Frequency Histograms


The example in the slide shows you how to view a frequency histogram. Because the number of
distinct values in the WAREHOUSE_ID column of the INVENTORIES table is 9, and the number of
requested buckets is 20, the system automatically creates a frequency histogram with 9 buckets. You
can view this information in the USER_TAB_COL_STATISTICS view.
To view the histogram itself, you can query the USER_HISTOGRAMS view. You can see both
ENDPOINT_NUMBER that corresponds to the cumulative frequency of the corresponding and
ENDPOINT_VALUE, which represents, in the case, the actual value of the column data.
Note: The DBMS_STATS package is dealt with later in the lesson.

Oracle Database 11g: SQL Tuning Workshop 7 - 13

Height-Balanced Histograms
In a height-balanced histogram, the column values are divided into bands so that each band contains
approximately the same number of rows. The histogram tells you where in the range of values the
endpoints end. In the slide example, assume that you have a column that is populated with 40,001
numbers. You only have ten distinct values: 1, 3, 5, 7, 10, 16, 27, 32, 39, and 49. Value 10 is the most
popular value with 16,293 occurrences. When the number of buckets is less than the number of
distinct values, ENDPOINT_NUMBER records the bucket number and ENDPOINT_VALUE records
the column value that corresponds to this endpoint. In the example, the number of rows per bucket is
1/5th of the total number of rows, that is 8000.
Based on this assumption, value 10 appears between 8000 and 24000 times. So you are sure that
value 10 is a popular value.
This type of histogram is good for equality predicate on popular value, and range predicates.
The number of rows per bucket is not recorded because this can be derived from the total number of
values and the fact that all the buckets contain an equal number of values. In this example, value 10
is a popular value because it spans multiple endpoint values. The histogram does not actually store
duplicated buckets to save space. In the example in the slide, bucket 2 (with endpoint value 10)
would not be recorded in DBA_TAB_HISTOGRAMS for that reason.
Note: The density statistic in this example would be 1/9 * 4/5 = 0.088 or 8.8% (9=#NPV and
4=#NPB). This value would be used for selectivity calculation for a nonpopular value.
Oracle Database 11g: SQL Tuning Workshop 7 - 14

Viewing Height-Balanced Histograms


The example in the slide shows you how to view a height-balanced histogram. Because the number
of distinct values in the QUANTITY_ON_HAND column of the INVENTORIES table is 237, and the
number of requested buckets is 10, the system automatically creates a height-balanced histogram
with 10 buckets. You can view this information in the USER_TAB_COL_STATISTICS view.
To view the histogram itself, you can query the USER_HISTOGRAMS view. You can see that the
ENDPOINT_NUMBER corresponds to the bucket number, and ENDPOINT_VALUE corresponds to
values of the endpoints end.
Note: The DBMS_STATS package is dealt with later in the lesson.

Oracle Database 11g: SQL Tuning Workshop 7 - 15

Histogram Considerations
Histograms are useful only when they reflect the current data distribution of a given column. The
data in the column can change as long as the distribution remains constant. If the data distribution of
a column changes frequently, you must recompute its histogram frequently.
Histograms are useful when you have a high degree of data skew in the columns for which you want
to create histograms.
However, there is no need to create histograms for columns which do not appear in a WHERE clause
of a SQL statement. Similarly, there is no need to create histograms for columns with uniform
distribution.
In addition, for columns declared as UNIQUE, histograms are useless because the selectivity is
obvious. Also, the maximum number of buckets is 254, which can be lower depending on the actual
number of distinct column values. Histograms can affect performance and should be used only when
they substantially improve query plans. For uniformly distributed data, the optimizer can make fairly
accurate guesses about the cost of executing a particular statement without the use of histograms.
Note: Character columns have some exceptional behavior as histogram data is stored only for the
first 32 bytes of any string.

Oracle Database 11g: SQL Tuning Workshop 7 - 16

Multicolumn Statistics: Overview


With Oracle Database 10g, the query optimizer takes into account the correlation between columns
when computing the selectivity of multiple predicates in the following limited cases:
If all the columns of a conjunctive predicate match all the columns of a concatenated index key,
and the predicates are equalities used in equijoins, then the optimizer uses the number of distinct
keys (NDK) in the index for estimating selectivity, as 1/NDK.
When DYNAMIC_SAMPLING is set to level 4, the query optimizer uses dynamic sampling to
estimate the selectivity of complex predicates involving several columns from the same table.
However, the sample size is very small and increases parsing time. As a result, the sample is
likely to be statistically inaccurate and may cause more harm than good.
In all other cases, the optimizer assumes that the values of columns used in a complex predicate are
independent of each other. It estimates the selectivity of a conjunctive predicate by multiplying the
selectivity of individual predicates. This approach results in underestimation of the selectivity if there
is a correlation between the columns. To circumvent this issue, Oracle Database 11g allows you to
collect, store, and use the following statistics to capture functional dependency between two or more
columns (also called groups of columns): number of distinct values, number of nulls, frequency
histograms, and density.

Oracle Database 11g: SQL Tuning Workshop 7 - 17

Multicolumn Statistics: Overview (continued)


For example, consider a VEHICLE table in which you store information about cars. The MAKE and
MODEL columns are highly correlated, in that MODEL determines MAKE. This is a strong dependency,
and both columns should be considered by the optimizer as highly correlated. You can signal that
correlation to the optimizer by using the CREATE_EXTENDED_STATS function as shown in the
example in the slide, and then compute the statistics for all columns (including the ones for the
correlated groups that you created).
The optimizer uses only multicolumn statistics with equality predicates.
Note
The CREATE_EXTENDED_STATS function returns a virtual hidden column name, such as
SYS_STUW_5RHLX443AN1ZCLPE_GLE4.
Based on the example in the slide, the name can be determined by using the following SQL:
select
dbms_stats.show_extended_stats_name('jfv','vehicle','(make,model
)') from dual
After you create the statistics extensions, you can retrieve them by using the
ALL|DBA|USER_STAT_EXTENSIONS views.

Oracle Database 11g: SQL Tuning Workshop 7 - 18

Expression Statistics: Overview


Predicates involving expressions on columns are a significant issue for the query optimizer. When
computing selectivity on predicates of the form function(Column) = constant, the optimizer
assumes a static selectivity value of 1 percent. This approach is wrong because it may cause the
optimizer to produce suboptimal plans.
The query optimizer has been extended to better handle such predicates in limited cases where
functions preserve the data distribution characteristics of the column and thus allow the optimizer to
use the columns statistics. An example of such a function is TO_NUMBER.
Further enhancements have been made to evaluate built-in functions during query optimization to
derive better selectivity using dynamic sampling. Finally, the optimizer collects statistics on virtual
columns created to support function-based indexes.
However, these solutions are either limited to a certain class of functions or work only for
expressions used to create function-based indexes. By using expression statistics in Oracle Database
11g, you can use a more general solution that includes arbitrary user-defined functions and does not
depend on the presence of function-based indexes. As shown in the example in the slide, this feature
relies on the virtual column infrastructure to create statistics on expressions of columns.

Oracle Database 11g: SQL Tuning Workshop 7 - 19

Gathering System Statistics


System statistics allow the optimizer to consider a systems I/O and CPU performance, and
utilization. For each candidate plan, the optimizer computes estimates for I/O and CPU costs. It is
important to know the system characteristics to select the most efficient plan with optimal proportion
between I/O and CPU cost. System CPU and I/O characteristics depend on many factors and do not
stay constant all the time. Using system statistics management routines, you can capture statistics in
the interval of time when the system has the most common workload. For example, database
applications can process online transaction processing (OLTP) transactions during the day and run
OLAP reports at night. You can gather statistics for both states and activate appropriate OLTP or
OLAP statistics when needed. This allows the optimizer to generate relevant costs with respect to the
available system resource plans. When the system generates system statistics, it analyzes system
activity in a specified period of time. Unlike the table, index, or column statistics, the system does
not invalidate already parsed SQL statements when system statistics get updated. All new SQL
statements are parsed using new statistics.
It is highly recommended that you gather system statistics. System statistics are gathered in a userdefined time frame with the DBMS_STATS.GATHER_SYSTEM_STATS routine. You can also set system
statistics values explicitly using DBMS_STATS.SET_SYSTEM_STATS. Use
DBMS_STATS.GET_SYSTEM_STATS to verify system statistics.

Oracle Database 11g: SQL Tuning Workshop 7 - 20

Gathering System Statistics (continued)


When you use the GATHER_SYSTEM_STATS procedure, you should specify the
GATHERING_MODE parameter:
NOWORKLOAD: This is the default. This mode captures characteristics of the I/O system.
Gathering may take a few minutes and depends on the size of the database. During this period
the system estimates the average read seek time and transfer speed for the I/O system. This
mode is suitable for all workloads. It is recommended that you run GATHER_SYSTEM_STATS
('noworkload') after you create the database and tablespaces.
INTERVAL: Captures system activity during a specified interval. This works in combination
with the interval parameter that specifies the amount of time for the capture. You should
provide an interval value in minutes, after which system statistics are created or updated in the
dictionary or a staging table. You can use GATHER_SYSTEM_STATS
(gathering_mode=>'STOP') to stop gathering earlier than scheduled.
START | STOP: Captures system activity during specified start and stop times, and refreshes the
dictionary or a staging table with statistics for the elapsed period.
Note: Since Oracle Database 10g, Release 2, the system automatically gathers essential parts of
system statistics at startup.

Oracle Database 11g: SQL Tuning Workshop 7 - 21

Gathering System Statistics: Example


The example in the slide shows database applications processing OLTP transactions during the day
and running reports at night.
First, system statistics must be collected during the day. In this example, gathering ends after 120
minutes and is stored in the mystats table.
Then, system statistics are collected during the night. Gathering ends after 120 minutes and is stored
in the mystats table.
Generally, the syntax in the slide is used to gather system statistics. Before invoking the
GATHER_SYSTEM_STATS procedure with the INTERVAL parameter specified, you must activate job
processes using a command, such as SQL> alter system set job_queue_processes = 1;. You
can also invoke the same procedure with different arguments to enable manual gathering instead of
using jobs.
If appropriate, you can switch between the statistics gathered. Note that it is possible to automate this
process by submitting a job to update the dictionary with appropriate statistics. During the day, a job
may import the OLTP statistics for the daytime run, and during the night, another job imports the
online analytical processing (OLAP) statistics for the nighttime run.

Oracle Database 11g: SQL Tuning Workshop 7 - 22

Gathering System Statistics: Example (continued)


The example in the previous slide shows how to collect system statistics with jobs by using the
internal parameter of the DBMS_STATS.GATHER_SYSTEM_STATS procedure. To collect system statistics
manually, another parameter of this procedure can be used as shown in the slide.
First, you must start the system statistics collection, and then you can can end the collection process
at any time after you are certain that a representative workload has been generated on the instance.
The example collects system statistics directly in the data dictionary.

Oracle Database 11g: SQL Tuning Workshop 7 - 23

Mechanisms for Gathering Statistics


Oracle Database provides several mechanisms to gather statistics. These are discussed in more detail
in the subsequent slides. It is recommended that you use automatic statistics gathering for objects.
Note: When the system encounters a table with missing statistics, it dynamically gathers the
necessary statistics needed by the optimizer. However, for certain types of tables, it does not perform
dynamic sampling. These include remote tables and external tables. In those cases and also when
dynamic sampling has been disabled, the optimizer uses default values for its statistics.

Oracle Database 11g: SQL Tuning Workshop 7 - 24

Statistic Preferences: Overview


The automated statistics-gathering feature was introduced in Oracle Database 10g, Release 1 to
reduce the burden of maintaining optimizer statistics. However, there were cases where you had to
disable it and run your own scripts instead. One reason was the lack of object-level control.
Whenever you found a small subset of objects for which the default gather statistics options did not
work well, you had to lock the statistics and analyze them separately by using your own options. For
example, the feature that automatically tries to determine adequate sample size
(ESTIMATE_PERCENT=AUTO_SAMPLE_SIZE) does not work well against columns that contain
data with very high frequency skews. The only way to get around this issue was to manually specify
the sample size in your own script.
The Statistic Preferences feature in Oracle Database 11g introduces flexibility so that you can rely
more on the automated statistics-gathering feature to maintain the optimizer statistics when some
objects require settings that are different from the database default.
This feature allows you to associate the statistics-gathering options that override the default behavior
of the GATHER_*_STATS procedures and the automated Optimizer Statistics Gathering task at the
object or schema level. You can use the DBMS_STATS package to manage the gathering statistics
options shown in the slide.

Oracle Database 11g: SQL Tuning Workshop 7 - 25

Statistic Preferences: Overview (continued)


You can set, get, delete, export, and import those preferences at the table, schema, database, and
global levels. Global preferences are used for tables that do not have preferences, whereas database
preferences are used to set preferences on all tables. The preference values that are specified in
various ways take precedence from the outer circles to the inner ones (as shown in the slide).
In the graphic in the slide, the last three highlighted options are new in Oracle Database 11g,
Release 1:
CASCADE gathers statistics on the indexes as well. Index statistics gathering is not parallelized.
ESTIMATE_PERCENT is the estimated percentage of rows used to compute statistics (Null
means all rows): The valid range is [0.000001,100]. Use the constant
DBMS_STATS.AUTO_SAMPLE_SIZE to have the system determine the appropriate sample
size for good statistics. This is the recommended default.
NO_INVALIDATE invalidates or not dependent cursors. It does not invalidate the dependent
cursors if set to TRUE. The procedure invalidates the dependent cursors immediately if set to
FALSE. Use DBMS_STATS.AUTO_INVALIDATE to have the system decide when to
invalidate dependent cursors. This is the default.
PUBLISH is used to decide whether to publish the statistics to the dictionary or to store them in
a pending area before.
STALE_PERCENT is used to determine the threshold level at which an object is considered to
have stale statistics. The value is a percentage of rows modified since the last statistics
gathering. The example changes the 10 percent default to 13 percent for SH.SALES only.
DEGREE determines the degree of parallelism used to compute statistics. The default for degree
is null, which means use the table default value specified by the DEGREE clause in the CREATE
TABLE or ALTER TABLE statement. Use the constant DBMS_STATS.DEFAULT_DEGREE to
specify the default value based on the initialization parameters. The AUTO_DEGREE value
determines the degree of parallelism automatically. This is either 1 (serial execution) or
DEFAULT_DEGREE (the system default value based on the number of CPUs and initialization
parameters), depending on the size of the object.
METHOD_OPT is a SQL string used to collect histogram statistics. The default value is FOR
ALL COLUMNS SIZE AUTO.
GRANULARITY is the granularity of statistics to collect for partitioned tables.
INCREMENTAL is used to gather global statistics on partitioned tables in an incremental way.
It is important to note that you can change default values for the above parameters using the
DBMS_STATS.SET_PARAM procedure.
Note: You can describe all the effective statistics preference settings for all relevant tables by using
the DBA_TAB_STAT_PREFS view.

Oracle Database 11g: SQL Tuning Workshop 7 - 26

When to Gather Statistics Manually


The automatic statistics gathering mechanism keeps all the statistics current. It is very important to
determine when and how often to gather new statistics. The default gathering interval is nightly, but
you can change this interval to suit your business needs. You can do so by changing the
characteristics of your maintenance windows. However, manual statistics gathering may be required
in certain cases. For example, the statistics on tables that are significantly modified during the day
may become stale. There are typically two types of such objects:
Volatile tables that are modified significantly during the course of the day
Objects that are the target of large bulk loads that add 10% or more to the objects total size
between statistics-gathering intervals
For external tables, statistics are not collected during GATHER_SCHEMA_STATS,
GATHER_DATABASE_STATS, and automatic optimizer statistics collection processing. However,
you can collect statistics on an individual external table using GATHER_TABLE_STATS. Sampling
on external tables is not supported so the ESTIMATE_PERCENT option should be explicitly set to
null. Because data manipulation is not allowed against external tables, it is sufficient to analyze
external tables when the corresponding file changes. Other areas in which statistics need to be
manually gathered are the system statistics and fixed objects, such as the dynamic performance
tables. These statistics are not automatically gathered.

Oracle Database 11g: SQL Tuning Workshop 7 - 27

Manual Statistics Gathering


Both Enterprise Manager and the DBMS_STATS package enable you to manually generate and
manage statistics for the optimizer. You can use the DBMS_STATS package to gather, modify, view,
export, import, lock, and delete statistics. You can also use this package to identify or name gathered
statistics. You can gather statistics on indexes, tables, columns, and partitions at various granularity:
object, schema, and database level.
DBMS_STATS gathers only statistics needed for optimization; it does not gather other statistics. For
example, the table statistics gathered by DBMS_STATS include the number of rows, number of
blocks currently containing data, and average row length, but not the number of chained rows,
average free space, or number of unused data blocks.
Note: Do not use the COMPUTE and ESTIMATE clauses of the ANALYZE statement to collect
optimizer statistics. These clauses are supported solely for backward compatibility and may be
removed in a future release. The DBMS_STATS package collects a broader, more accurate set of
statistics, and gathers statistics more efficiently. You may continue to use the ANALYZE statement for
other purposes not related to the optimizer statistics collection:
To use the VALIDATE or LIST CHAINED ROWS clauses
To collect information on free list blocks

Oracle Database 11g: SQL Tuning Workshop 7 - 28

Manual Statistics Collection: Factors


When you manually gather optimizer statistics, you must pay special attention to the following
factors:
Monitoring objects for mass data manipulation language (DML) operations and gathering
statistics if necessary
Determining the correct sample sizes
Determining the degree of parallelism to speed up queries on large objects
Determining if histograms should be created on columns with skewed data
Determining whether changes on objects cascade to any dependent indexes

Oracle Database 11g: SQL Tuning Workshop 7 - 29

Managing Statistics Collection: Example


The first example uses the DBMS_STATS package to gather statistics on the CUSTOMERS table of
the SH schema. It uses some of the options discussed in the previous slides.
Setting Parameter Defaults
You can use the SET_PARAM procedure in DBMS_STATS to set default values for parameters of all
DBMS_STATS procedures. The second example in the slide shows this usage. You can also use the
GET_PARAM function to get the current default value of a parameter.
Note: Granularity of statistics to collect is pertinent only if the table is partitioned. This parameter
determines at which level statistics should be gathered. This can be at the partition, subpartition, or
table level.

Oracle Database 11g: SQL Tuning Workshop 7 - 30

Optimizer Dynamic Sampling: Overview


Dynamic sampling improves server performance by determining more accurate selectivity and
cardinality estimates that allow the optimizer to produce better performing plans. For example,
although it is recommended that you collect statistics on all of your tables for use by the CBO, you
may not gather statistics for your temporary tables and working tables used for intermediate data
manipulation. In these cases, the CBO provides a value through a simple algorithm that can lead to a
suboptimal execution plan. You can use dynamic sampling to:
Estimate single-table predicate selectivities when collected statistics cannot be used or are likely
to lead to significant errors in estimation
Estimate table cardinality for tables and relevant indexes without statistics or for tables whose
statistics are too outdated to be reliable
You control dynamic sampling with the OPTIMIZER_DYNAMIC_SAMPLING initialization parameter. The
DYNAMIC_SAMPLING and DYNAMIC_SAMPLING_EST_CDN hints can be used to further control dynamic
sampling.
Note: The OPTIMIZER_FEATURES_ENABLE initialization parameter turns off dynamic sampling if set
to a version prior to 9.2.

Oracle Database 11g: SQL Tuning Workshop 7 - 31

Optimizer Dynamic Sampling at Work


The primary performance attribute is compile time. The system determines at compile time whether a
query would benefit from dynamic sampling. If so, a recursive SQL statement is issued to scan a
small random sample of the tables blocks, and to apply the relevant single table predicates to
estimate predicate selectivities.
Depending on the value of the OPTIMIZER_DYNAMIC_SAMPLING initialization parameter, a certain
number of blocks is read by the dynamic sampling query.
For a query that normally completes quickly (in less than a few seconds), you do not want to incur
the cost of dynamic sampling. However, dynamic sampling can be beneficial under any of the
following conditions:
A better plan can be found using dynamic sampling.
The sampling time is a small fraction of total execution time for the query.
The query is executed many times.
Note: Dynamic sampling can be applied to a subset of a single tables predicates and combined with
standard selectivity estimates of predicates for which dynamic sampling is not done.

Oracle Database 11g: SQL Tuning Workshop 7 - 32

OPTIMIZER_DYNAMIC_SAMPLING
You control dynamic sampling with the OPTIMIZER_DYNAMIC_SAMPLING parameter, which can be set
to a value from 0 to 10. A value of 0 means dynamic sampling is not done.
A value of 1 (the default) means dynamic sampling is performed on all unanalyzed tables if the
following criteria are met:
There is at least one unanalyzed table in the query.
This unanalyzed table is joined to another table or appears in a subquery or nonmergeable view.
This unanalyzed table has no indexes.
This unanalyzed table has more blocks than the default number of blocks that would be used for
dynamic sampling of this table. This default number is 32.
The default value is 2 if OPTIMIZER_FEATURES_ENABLE is set to 10.0.0 or higher. At this
level, the system applies dynamic sampling to all unanalyzed tables. The number of blocks sampled
is two times the default number of dynamic sampling blocks (32).
Increasing the value of the parameter results in more aggressive application of dynamic sampling, in
terms of both the type of tables sampled (analyzed or unanalyzed) and the amount of I/O spent on
sampling.
Note: Dynamic sampling is repeatable if no rows have been inserted, deleted, or updated in the table
being sampled since the previous sample operation.

Oracle Database 11g: SQL Tuning Workshop 7 - 33

Locking Statistics
Starting with Oracle Database 10g, you can lock statistics on a specified table with the
LOCK_TABLE_STATS procedure of the DBMS_STATS package. You can lock statistics on a table
without statistics or set them to NULL using the DELETE_*_STATS procedures to prevent
automatic statistics collection so that you can use dynamic sampling on a volatile table with no
statistic. You can also lock statistics on a volatile table at a point when it is fully populated so that the
table statistics are more representative of the table population.
You can also lock statistics at the schema level by using the LOCK_SCHEMA_STATS procedure. You
can query the STATTYPE_LOCKED column in the {USER | ALL | DBA}_TAB_STATISTICS view
to determine whether the statistics on the table are locked.
You can use the UNLOCK_TABLE_STATS procedure to unlock the statistics on a specified table.
You can set the value of the FORCE parameter to TRUE to overwrite the statistics even if they are
locked. The FORCE argument is found in the following DBMS_STATS procedures:
DELETE_*_STATS, IMPORT_*_STATS, RESTORE_*_STATS, and SET_*_STATS.
Note: When you lock the statistics on a table, all the dependent statistics are considered locked. This
includes table statistics, column statistics, histograms, and dependent index statistics.

Oracle Database 11g: SQL Tuning Workshop 7 - 34

Oracle Database 11g: SQL Tuning Workshop 7 - 35

Oracle Database 11g: SQL Tuning Workshop 7 - 36

Oracle Database 11g: SQL Tuning Workshop 8 - 2

Cursor Sharing and Different Literal Values


If you use SQL statements where literal values are provided for the WHERE clause conditions, you
get results in many versions of almost identical SQL to be stored in the library cache. As each SQL
statement is submitted with a different value, the statement is not found in the library cache, and so
you must perform all steps for processing a new SQL statement. Not only does this incur typically
unnecessary statement parsing, it may also cause the library cache to fill up quickly because of all the
different statements stored in it.
When coded this way, you are not taking advantage of cursor sharing.
However, depending on the literal value provided, different execution plans might be generated by
the optimizer. For example, there might be a lot of JOBS, where MIN_SALARY is greater than
12000. On the other hand, there might be very few JOBS that have a MIN_SALARY greater than
18000. This difference in data distribution could justify the addition of an index so that different
plans can be used depending on the value provided in the query. This is illustrated in the slide. As
you can see, the first and third queries use the same execution plan, but the second query uses a
different one.
From a performance perspective, it is good to have separate cursors. However, this is not very
economic because you could have shared cursors for the first and last queries in this example.
Note: In the case of the slide example, V$SQL.PLAN_HASH_VALUE is identical for the first and
third query.
Oracle Database 11g: SQL Tuning Workshop 8 - 3

Cursor Sharing and Bind Variables


If, instead of issuing different statements for each literal, you use a bind variable, then that extra
parse activity is eliminated (in theory). This is because the optimizer recognizes that the statement is
already parsed and decides to reuse the same execution plan even though you specified different bind
values the next time you reexecute the same statement.
In the example in the slide, the bind variable is called min_sal. It is to be compared with the
MIN_SALARY column of the JOBS table. Instead of issuing three different statements, issue a single
statement that uses a bind variable. At execution time, the same execution plan is used, the given
value is substituted for the variable.
However, from a performance perspective, this is not the best situation because you get best
performance two times out of three. On the other hand, this is very economic because you just need
one shared cursor in the library cache to execute all the three statements.

Oracle Database 11g: SQL Tuning Workshop 8 - 4

Bind Variables in SQL*Plus


Bind variables can be used in SQL*Plus sessions. In SQL*Plus, use the VARIABLE command to
define a bind variable. Then, you can assign values to the variable by executing an assignment
statement with the EXEC[UTE] command. Any references to that variable from then on use the value
you assigned.
In the example in the slide, the first count is selected while SA_REP is assigned to the variable. The
result is 30. Then, AD_VP is assigned to the variable, and the resulting count is 2.

Oracle Database 11g: SQL Tuning Workshop 8 - 5

Bind Variables in Enterprise Manager


On the SQL Worksheet page of Enterprise Manager (see the SQL Worksheet link in the Related
Links region of the Database Home page), you can specify that a SQL statement should use bind
variables. You can do this by selecting the Use bind variables for execution check box. When you
select that, several fields are generated, where you can enter bind variable values. Refer to these
values in the SQL statement using variable names that begin with a colon. The order in which
variables are referred to defines which variable gets which value. The first variable referred to gets
the first value, the second variable gets the second value, and so on. If you change the order in which
variables are referenced in the statement, you may need to change the value list to match that order.

Oracle Database 11g: SQL Tuning Workshop 8 - 6

Bind Variable Peeking


When literals are used in a query, those literal values can be used by the optimizer to decide on the
best plan. However, when bind variables are used, the optimizer still needs to select the best plan
based on the values of the conditions in the query, but cannot see those values readily in the SQL
text. That means, as a SQL statement is parsed, the system needs to be able to see the value of the
bind variables, to ensure that a good plan that would suit those values is selected. The optimizer does
this by peeking at the value in the bind variable. When the SQL statement is hard parsed, the
optimizer evaluates the value for each bind variable, and uses that as input in determining the best
plan. After the execution is determined the first time you parsed the query, it is reused when you
execute the same statement regardless of the bind values used.
This feature was introduced in Oracle9i Database, Release 2. Oracle Database 11g changes this
behavior.

Oracle Database 11g: SQL Tuning Workshop 8 - 7

Bind Variable Peeking (continued)


Under some conditions, bind variable peeking can cause the optimizer to select the suboptimal plan.
This occurs because the first value of the bind variable is what is used to determine the plan for all
subsequent executions of the query. Therefore, even though subsequent executions provide different
bind values, the same plan is used. It is possible that a different plan would be better for executions
that have different bind variable values. An example is where the selectivity of a particular index
varies extremely depending on the column value. For low selectivity, a full table scan may be faster.
For high selectivity, an index range scan may be more appropriate. As shown in the slide, plan A may
be good for the first and third values of min_sal, but it may not be the best for the second one.
Suppose there are very few MIN_SALARY values that are above 18000, and plan A is a full table
scan. It is probable that a full table scan is not a good plan for the second execution, in that case.
So bind variables are beneficial in that they cause more cursor sharing to happen, and thus reduce
parsing of SQL. But, as in this case, it is possible that they cause a suboptimal plan to be chosen for
some of the bind variable values. This is a good reason for not using bind variables for decision
support system (DSS) environments, where the parsing of the query is a very small percentage of the
work done when submitting a query. The parsing may take fractions of a second, but the execution
may take minutes or hours. To execute with a slower plan is not worth the savings gained in parse
time.

Oracle Database 11g: SQL Tuning Workshop 8 - 8

Cursor Sharing Enhancements


Oracle8i introduced the possibility of sharing SQL statements that differ only in literal values. Rather
than developing an execution plan each time the same statementwith a different literal valueis
executed, the optimizer generates a common execution plan used for all subsequent executions of the
statement.
Because only one execution plan is used instead of potential different ones, this feature should be
tested against your applications before you decide to enable it or not. That is why Oracle9i extends
this feature by sharing only statements considered as similar. That is, only when the optimizer has the
guarantee that the execution plan is independent of the literal value used. For example, consider a
query, where EMPLOYEE_ID is the primary key:
SQL> SELECT * FROM employees WHERE employee_id = 153;

The substitution of any value would produce the same execution plan. It would, therefore, be safe for
the optimizer to generate only one plan for different occurrences of the same statement executed with
different literal values.
On the other hand, assume that the same EMPLOYEES table has a wide range of values in its
DEPARTMENT_ID column. For example, department 50 could contain over one third of all employees
and department 70 could contain just one or two.

Oracle Database 11g: SQL Tuning Workshop 8 - 9

Cursor Sharing Enhancements (continued)


See the two queries:
SQL> SELECT * FROM employees WHERE department_id = 50;
SQL> SELECT * FROM employees WHERE department_id = 70;

Using only one execution plan for sharing the same cursor would not be safe if you have histogram
statistics (and there is skew in the data) on the DEPARTMENT_ID column. In this case, depending on
which statement was executed first, the execution plan could contain a full table (or fast full index)
scan, or it could use a simple index range scan.

Oracle Database 11g: SQL Tuning Workshop 8 - 10

The CURSOR_SHARING Parameter


The value of the CURSOR_SHARING initialization parameter determines how the optimizer processes
statements with bind variables:
EXACT: Literal replacement disabled completely
FORCE: Causes sharing for all literals
SIMILAR: Causes sharing for safe literals only
In earlier releases, you could select only the EXACT or the FORCE option. It causes the optimizer to
examine the statement to ensure that replacement occurs only for safe literals. In doing this, it can
use information about the nature of any available index (unique or nonunique) and statistics collected
on the index or underlying table, including histograms.
The value of CURSOR_SHARING in the initialization file can be overridden with an ALTER SYSTEM SET
CURSOR_SHARING or an ALTER SESSION SET CURSOR_SHARING command.
The CURSOR_SHARING_EXACT hint causes the system to execute the SQL statement without any
attempt to replace literals by bind variables.

Oracle Database 11g: SQL Tuning Workshop 8 - 11

Forcing Cursor Sharing: Example


Because you forced cursor sharing with the ALTER SESSION command, all your queries that differ
only with literal values are automatically rewritten to use the same system-generated bind variable
called SYS_B_0 in the example in the slide. As a result, you end up with only one child cursor
instead of three.
Note: Adaptive cursor sharing may also apply, and might generate a second child cursor in this case.

Oracle Database 11g: SQL Tuning Workshop 8 - 12

Adaptive Cursor Sharing: Overview


Bind variables were designed to allow the Oracle Database to share a single cursor for multiple SQL
statements to reduce the amount of shared memory used to parse SQL statements. However, cursor
sharing and SQL optimization are conflicting goals. Writing a SQL statement with literals provides
more information for the optimizer and naturally leads to better execution plans, while increasing
memory and CPU overhead caused by excessive hard parses. Oracle9i Database was the first attempt
to introduce a compromising solution by allowing similar SQL statements using different literal
values to be shared. For statements using bind variables, Oracle9i also introduced the concept of bind
peeking. To benefit from bind peeking, it is assumed that cursor sharing is intended and that different
invocations of the statement are supposed to use the same execution plan. If different invocations of
the statement would significantly benefit from different execution plans, bind peeking is of no use in
generating good execution plans.
To address this issue as much as possible, Oracle Database 11g introduces adaptive cursor sharing.
This feature is a more sophisticated strategy designed to not share the cursor blindly, but generate
multiple plans per SQL statement with bind variables if the benefit of using multiple execution plans
outweighs the parse time and memory usage overhead. However, because the purpose of using bind
variables is to share cursors in memory, a compromise must be found regarding the number of child
cursors that need to be generated.

Oracle Database 11g: SQL Tuning Workshop 8 - 13

Adaptive Cursor Sharing: Architecture


When you use adaptive cursor sharing, the following steps take place in the scenario illustrated in the
slide:
1. The cursor starts its life with a hard parse, as usual. If bind peeking takes place, and a histogram
is used to compute selectivity of the predicate containing the bind variable, then the cursor is
marked as a bind-sensitive cursor. In addition, some information is stored about the predicate
containing the bind variables, including the predicate selectivity. In the slide example, the
predicate selectivity that would be stored is a cube centered around (0.15,0.0025). Because of
the initial hard parse, an initial execution plan is determined using the peeked binds. After the
cursor is executed, the bind values and the execution statistics of the cursor are stored in that
cursor.
During the next execution of the statement when a new set of bind values is used, the system
performs a usual soft parse, and finds the matching cursor for execution. At the end of execution,
execution statistics are compared with the ones currently stored in the cursor. The system then
observes the pattern of the statistics over all the previous runs (see V$SQL_CS_ views in the slide
that follows) and decides whether or not to mark the cursor as bind aware.

Oracle Database 11g: SQL Tuning Workshop 8 - 14

Adaptive Cursor Sharing: Architecture (continued)


2. On the next soft parse of this query, if the cursor is now bind aware, bind-aware cursor matching
is used. Suppose the selectivity of the predicate with the new set of bind values is now
(0.18,0.003). Because selectivity is used as part of bind-aware cursor matching, and because the
selectivity is within an existing cube, the statement uses the existing child cursors execution
plan to run.
3. On the next soft parse of this query, suppose that the selectivity of the predicate with the new set
of bind values is now (0.3,0.009). Because that selectivity is not within an existing cube, no
child cursor match is found. So the system does a hard parse, which generates a new child cursor
with a second execution plan in that case. In addition, the new selectivity cube is stored as part
of the new child cursor. After the new child cursor executes, the system stores the bind values
and execution statistics in the cursor.
4. On the next soft parse of this query, suppose the selectivity of the predicate with the new set of
bind values is now (0.28,0.004). Because that selectivity is not within one of the existing cubes,
the system does a hard parse. Suppose that this time, the hard parse generates the same
execution plan as the first one. Because the plan is the same as the first child cursor, both child
cursors are merged. That is, both cubes are merged into a new bigger cube, and one of the child
cursors is deleted. The next time there is a soft parse, if the selectivity falls within the new cube,
the child cursor matches.

Oracle Database 11g: SQL Tuning Workshop 8 - 15

Adaptive Cursor Sharing: Views


These views determine whether a query is bind aware or not, and is handled automatically, without
any user input. However, information about what goes on is exposed through V$ views so that you
can diagnose problems, if any. New columns have been added to V$SQL:
IS_BIND_SENSITIVE: Indicates if a cursor is bind sensitive; value YES | NO. A query for
which the optimizer peeked at bind variable values when computing predicate selectivities and
where a change in a bind variable value may lead to a different plan is called bind sensitive.
IS_BIND_AWARE: Indicates if a cursor is bind aware; value YES | NO. A cursor in the cursor
cache that has been marked to use bind-aware cursor sharing is called bind aware.
V$SQL_CS_HISTOGRAM: Shows the distribution of the execution count across a three-bucket
execution history histogram.
V$SQL_CS_SELECTIVITY: Shows the selectivity cubes or ranges stored in a cursor for every
predicate containing a bind variable and whose selectivity is used in the cursor sharing checks. It
contains the text of the predicates and the selectivity range low and high values.

Oracle Database 11g: SQL Tuning Workshop 8 - 16

Adaptive Cursor Sharing: Views (continued)


V$SQL_CS_STATISTICS: Adaptive cursor sharing monitors execution of a query and collects
information about it for a while, and uses this information to decide whether to switch to using
bind-aware cursor sharing for the query. This view summarizes the information that it collects to
make this decision. For a sample of executions, it keeps track of the rows processed, buffer gets,
and CPU time. The PEEKED column has the value YES if the bind set was used to build the
cursor, and NO otherwise.

Oracle Database 11g: SQL Tuning Workshop 8 - 17

Adaptive Cursor Sharing: Example


Consider the data in the slide. There are histogram statistics on the JOB_ID column, showing that
there are many thousands times more occurrences of SA_REP than AD_ASST. In this case, if literals
were used instead of a bind variable, the query optimizer would see that the AD_ASST value occurs
in less than 1% of the rows, whereas the SA_REP value occurs in approximately a third of the rows.
If the table has over a million rows in it, the execution plans are different for each of these values
queries. The AD_ASST query results in an index range scan because there are so few rows with that
value. The SA_REP query results in a full table scan because so many of the rows have that value, it
is more efficient to read the entire table. But, as it is, using a bind variable causes the same execution
plan to be used for both of the values, at first. So, even though there exist different and better plans
for each of these values, they use the same plan.
After several executions of this query using a bind variable, the system considers the query bind
aware, at which point it changes the plan based on the bound value. This means the best plan is used
for the query, based on the bind variable value.

Oracle Database 11g: SQL Tuning Workshop 8 - 18

Interacting with Adaptive Cursor Sharing


Adaptive cursor sharing is independent of the CURSOR_SHARING parameter. The setting of
this parameter determines whether literals are replaced by the system-generated bind
variables. If they are, adaptive cursor sharing behaves just as it would if the user supplied binds
to begin with.
When using the SPM automatic plan capture, the first plan captured for a SQL statement with
bind variables is marked as the corresponding SQL plan baseline. If another plan is found for
that same SQL statement (which maybe the case with adaptive cursor sharing), it is added to the
SQL statements plan history and marked for verification: It will not be used immediately. So
even though adaptive cursor sharing has come up with a new plan based on a new set of bind
values, SPM does not let it be used until the plan has been verified. Thus reverting to10g
behavior, only the plan generated based on the first set of bind values is used by all subsequent
executions of the statement. One possible workaround is to run the system for some time with
automatic plan capture set to False, and after the cursor cache has been populated with all the
plans a SQL statement with bind has, load the entire plan directly from the cursor cache into the
corresponding SQL plan baseline. By doing this, all the plans for a single SQL statement are
marked as SQL baseline plans by default. This course does not tackle SQL Plan Management.
Refer to the SQL Performance Tuning course or the New Features for Administrators course.

Oracle Database 11g: SQL Tuning Workshop 8 - 19

Oracle Database 11g: SQL Tuning Workshop 8 - 20

Oracle Database 11g: SQL Tuning Workshop 8 - 21

Oracle Database 11g: SQL Tuning Workshop 9 - 2

Optimizer Hints: Overview


Hints enable you to influence decisions made by the optimizer. Hints provide a mechanism to direct
the optimizer to select a certain query execution plan based on the specific criteria.
For example, you might know that a certain index is more selective for certain queries. Based on this
information, you might be able to select a more efficient execution plan than the plan that the
optimizer recommends. In such a case, use hints to force the optimizer to use the optimal execution
plan. This is illustrated in the slide example where you force the optimizer to use the
EMPFIRSTNAME_IDX index to retrieve the data. As you can see, you can use comments in a SQL
statement to pass instructions to the optimizer.
The plus sign (+) causes the system to interpret the comment as a list of hints. The plus sign must
follow immediately after the comment delimiter. No space is permitted.
Hints should be used sparingly, and only after you have collected statistics on the relevant tables and
evaluated the optimizer plan without hints using the EXPLAIN PLAN statement. Changing database
conditions as well as query performance enhancements in subsequent releases can have a significant
impact on how hints in your code affect performance.
In addition, the use of hints involves extra code that must be managed, checked, and controlled.

Oracle Database 11g: SQL Tuning Workshop 9 - 3

Types of Hints
Single-table: Single-table hints are specified on one table or view. INDEX and USE_NL are
examples of single-table hints.
Multitable: Multitable hints are like single-table hints, except that the hint can specify one or more
tables or views. LEADING is an example of a multitable hint.
Query block: Query block hints operate on single query blocks. STAR_TRANSFORMATION and
UNNEST are examples of query block hints.
Statement: Statement hints apply to the entire SQL statement. ALL_ROWS is an example of a
statement hint.
Note: USE_NL(table1 table2) is not considered a multitable hint because it is actually a
shortcut for USE_NL(table1) and USE_NL(table2).

Oracle Database 11g: SQL Tuning Workshop 9 - 4

Specifying Hints
Hints apply to the optimization of only the block of the statement in which they appear. A statement
block is:
A simple MERGE, SELECT, INSERT, UPDATE, or DELETE statement
A parent statement or a subquery of a complex statement
A part of a compound query using set operators (UNION, MINUS, INTERSECT)
For example, a compound query consisting of two component queries combined by the UNION
operator has two blocks, one for each component query. For this reason, hints in the first component
query apply only to its optimization, not to the optimization of the second component query.
Optimizer Hint Syntax
Enclose hints within the comments of a SQL statement. You can use either style of comment. The
hint delimiter (+) must come immediately after the comment delimiter. If you separate them by a
space, the optimizer does not recognize that the comment contains hints.

Oracle Database 11g: SQL Tuning Workshop 9 - 5

Rules for Hints


You must place the hint comment immediately after the first keyword (MERGE, SELECT,
INSERT, DELETE, or UPDATE) of a SQL statement block.
A statement block can have only one comment containing hints, but it can contain many hints
inside that comment separated by spaces.
Hints apply to only the statement block in which they appear and override instance- or sessionlevel parameters.
If a SQL statement uses aliases, hints must reference the aliases rather than the table names.
The Oracle server ignores incorrectly specified hints. However, be aware of the following situations:
You never get an error message.
Other (correctly) specified hints in the same comment are considered.
The Oracle server also ignores combinations of conflicting hints.

Oracle Database 11g: SQL Tuning Workshop 9 - 6

Hint Recommendations
Use hints as a last remedy when tuning SQL statements.
Hints may prevent the optimizer from using better execution plans.
Hints may become less valid (or even invalid) when the database structure or contents change.

Oracle Database 11g: SQL Tuning Workshop 9 - 7

Optimizer Hint Syntax: Example


The slide shows an example with a hint that advises the cost-based optimizer (CBO) to use the
index. The execution plan is as follows:
Execution Plan
---------------------------------------------------------0
UPDATE STATEMENT Optimizer=ALL_ROWS (Cost=3 )
1
0
UPDATE OF 'PRODUCTS'
2
1
TABLE ACCESS (BY INDEX ROWID) OF 'PRODUCTS' (TABLE) (Cost)
3
2
INDEX (RANGE SCAN) OF 'PRODUCTS_PROD_CAT_IX' (INDEX)
(cost)
4
1
TABLE ACCESS (BY INDEX ROWID) OF 'PRODUCTS' (TABLE) (Cost)
5
4
INDEX (UNIQUE SCAN) OF 'PRODUCTS_PK' (INDEX (UNIQUE))
(Cost=0 )

The hint shown in the example works only if an index called PRODUCTS_PROD_CAT_IX
exists on the PRODUCTS table in the PROD_CATEGORY column.

Oracle Database 11g: SQL Tuning Workshop 9 - 8

Hint Categories
Most of these hints are discussed in the following slides. Many of these hints accept the table and
index names as arguments.
Note: Hints for parallel execution is not covered in this course.

Oracle Database 11g: SQL Tuning Workshop 9 - 9

Optimization Goals and Approaches


ALL_ROWS: The ALL_ROWS hint explicitly selects the cost-based approach to optimize a statement
block with a goal of best throughput. That is, minimum total resource consumption.
FIRST_ROWS(n): The FIRST_ROWS(n) hint (where n is any positive integer) instructs the
Oracle server to optimize an individual SQL statement for fast response. It instructs the server to
select the plan that returns the first n rows most efficiently. The FIRST_ROWS hint, which optimizes
for the best plan to return the first single row, is retained for backward compatibility and plan
stability. The optimizer ignores this hint SELECT statement blocks that include any blocking
operations, such as sorts or groupings. Such statements cannot be optimized for best response time
because Oracle Database must retrieve all rows accessed by the statement before returning the first
row. If you specify this hint in any such statement, the database optimizes for best throughput.
If you specify either the ALL_ROWS or the FIRST_ROWS(n) hint in a SQL statement, and if the
data dictionary does not have statistics about tables accessed by the statement, then the optimizer
uses default statistical values to estimate the missing statistics and to subsequently select an
execution plan.
If you specify hints for access paths or join operations along with either the ALL_ROWS or
FIRST_ROWS(n) hint, the optimizer gives precedence to the access paths and join operations
specified by the hints.
Note: The FIRST_ROWS hints are probably the most useful hints.

Oracle Database 11g: SQL Tuning Workshop 9 - 10

Hints for Access Paths


Specifying one of these hints causes the optimizer to choose the specified access path only if the
access path is available based on the existence of an index and on the syntactic constructs of the SQL
statement. If a hint specifies an unavailable access path, the optimizer ignores it. You must specify
the table to be accessed exactly as it appears in the statement. If the statement uses an alias for the
table, use the alias rather than the table name in the hint. The table name in the hint should not
include the schema name if the schema name is present in the statement.
FULL: The FULL hint explicitly selects a full table scan for the specified table. For example:
SELECT /*+ FULL(e) */ employee_id, last_name
FROM hr.employees e WHERE last_name LIKE 'K%';

The Oracle server performs a full table scan on the employees table to execute this statement, even if
there is an index on the last_name column that is made available by the condition in the WHERE
clause.
CLUSTER: The CLUSTER hint instructs the optimizer to use a cluster scan to access the specified
table. This hint applies only to clustered tables.
HASH: The HASH hint instructs the optimizer to use a hash scan to access the specified table. This
hint applies only to tables stored in a table cluster.
ROWID: The ROWID hint explicitly chooses a table scan by ROWID for the specified table.

Oracle Database 11g: SQL Tuning Workshop 9 - 11

Hints for Access Paths (continued)


INDEX: The INDEX hint explicitly chooses an index scan for the specified table. You can use the
INDEX hint for domain, B*-tree, bitmap, and bitmap join indexes. However, it is better if you use
INDEX_COMBINE rather than INDEX for bitmap indexes because it is a more versatile hint. This
hint can optionally specify one or more indexes.
If this hint specifies a single available index, the optimizer performs a scan on this index. The
optimizer does not consider a full table scan or a scan on another index on the table.
If this hint specifies a list of available indexes, the optimizer considers the cost of a scan on each
index in the list and then performs the index scan with the lowest cost. The optimizer can also choose
to scan multiple indexes from this list and merge the results, if such an access path has the lowest
cost. The optimizer does not consider a full table scan or a scan on an index not listed in the hint.
If this hint specifies no indexes, the optimizer considers the cost of a scan on each available index on
the table and then performs the index scan with the lowest cost. The optimizer can also choose to
scan multiple indexes and merge the results, if such an access path has the lowest cost. The optimizer
does not consider a full table scan.
INDEX_ASC: The INDEX_ASC hint explicitly chooses an index scan for the specified table. If the
statement uses an index range scan, the Oracle server scans the index entries in ascending order of
their indexed values. Because the servers default behavior for a range scan is to scan index entries in
the ascending order of their indexed values, this hint does not specify anything more than the INDEX
hint. However, you might want to use the INDEX_ASC hint to specify ascending range scans
explicitly, should the default behavior change.
INDEX_DESC: The INDEX_DESC hint instructs the optimizer to use a descending index scan for
the specified table. If the statement uses an index range scan and the index is ascending, the system
scans the index entries in the descending order of their indexed values. In a partitioned index, the
results are in the descending order within each partition. For a descending index, this hint effectively
cancels out the descending order, resulting in a scan of the index entries in the ascending order.
INDEX_COMBINE: The INDEX_COMBINE hint explicitly chooses a bitmap access path for the
table. If no indexes are given as arguments for the INDEX_COMBINE hint, the optimizer uses a
Boolean combination of bitmap indexes that has the best cost estimate for the table. If certain indexes
are given as arguments, the optimizer tries to use some Boolean combination of those particular
bitmap indexes.
For example:
SELECT /*+INDEX_COMBINE(customers cust_gender_bix cust_yob_bix)*/ *
FROM customers WHERE cust_year_of_birth < 70 AND cust_gender = 'M';

Note: For INDEX, INDEX_FFS, and INDEX_SS, there are counter hints, NO_INDEX,
NO_INDEX_FFS, and NO_INDEX_SS, respectively to avoid using those paths.

Oracle Database 11g: SQL Tuning Workshop 9 - 12

Hints for Access Paths (continued)


INDEX_JOIN: The INDEX_JOIN hint explicitly instructs the optimizer to use an index join as an
access path. For the hint to have a positive effect, a sufficiently small number of indexes must exist
that contain all the columns required to resolve the query.
For example, the following query uses an index join to access the employee_id and
department_id columns, both of which are indexed in the employees table:
SELECT /*+index_join(employees emp_emp_id_pk emp_department_ix)*/
employee_id, department_id
FROM hr.employees WHERE department_id > 50;

INDEX_DESC: The INDEX_DESC hint explicitly chooses an index scan for the specified table. If
the statement uses an index range scan, the Oracle server scans the index entries in the descending
order of their indexed values. In a partitioned index, the results are in the descending order within
each partition.
For example:
SELECT /*+ INDEX_DESC(a ord_order_date_ix) */ a.order_date,
a.promotion_id, a.order_id
FROM oe.orders a WHERE a.order_date < '01-jan-1985';

Oracle Database 11g: SQL Tuning Workshop 9 - 13

Hints for Access Paths (continued)


INDEX_FFS: The INDEX_FFS hint causes a fast-full index scan to be performed rather than a full
table scan.
For example:
SELECT /*+ INDEX_FFS ( o order_pk ) */ COUNT(*)
FROM order_items l, orders o
WHERE l.order_id > 50 AND l.order_id = o.order_id;

INDEX_SS: The INDEX_SS hint instructs the optimizer to perform an index skip scan for the
specified indexes of the specified table. If the statement uses an index range scan, the system scans
the index entries in the ascending order of their indexed values. In a partitioned index, the results are
in the ascending order within each partition. There are also INDEX_SS_ASC and
INDEX_SS_DESC hints.
NO_INDEX: The NO_INDEX hint explicitly disallows a set of indexes for the specified table.
If this hint specifies a single available index, the optimizer does not consider a scan on this
index. Other indexes that are not specified are still considered.
If this hint specifies a list of available indexes, the optimizer does not consider a scan on any of
the specified indexes. Other indexes that are not specified in the list are still considered.
If this hint specifies no indexes, the optimizer does not consider a scan on any index on the
table. This behavior is the same as a NO_INDEX hint that specifies a list of all available indexes
for the table.
The NO_INDEX hint applies to function-based, B*-tree, bitmap, or domain indexes. If a NO_INDEX
hint and an index hint (INDEX, INDEX_ASC, INDEX_DESC, INDEX_COMBINE, or
INDEX_FFS) both specify the same indexes, then both the NO_INDEX hint and the index hint are
ignored for the specified indexes and the optimizer considers the specified indexes.
For example:
SELECT /*+NO_INDEX(employees emp_empid)*/ employee_id
FROM employees WHERE employee_id > 200;

AND_EQUAL: The AND_EQUAL hint explicitly chooses an execution plan that uses an access path
that merges the scans on several single-column indexes, where you can specify:
The name or alias of the table that is associated with the indexes to be merged
The indexes on which an index scan is to be performed. You must specify at least two indexes,
but no more than five.

Oracle Database 11g: SQL Tuning Workshop 9 - 14

The INDEX_COMBINE Hint: Example


The INDEX_COMBINE hint is designed for bitmap index operations. Remember the following:
If certain indexes are given as arguments for the hint, the optimizer tries to use some
combination of those particular bitmap indexes.
If no indexes are named in the hint, all indexes are considered to be hinted.
The optimizer always tries to use hinted indexes, whether or not it considers them to be cost
effective.
In the example in the slide, suppose that all the three columns that are referenced in the WHERE
predicate of the statement in the slide (CUST_MARITAL_STATUS, CUST_GENDER, and
CUST_YEAR_OF_BIRTH) have a bitmap index. When you enable AUTOTRACE, the execution plan
of the statement might appear as shown in the next slide.

Oracle Database 11g: SQL Tuning Workshop 9 - 15

The INDEX_COMBINE Hint: Example (continued)


In the example in the slide, the following bitmap row sources are used:

Bitmap Row Source


BITMAP CONVERSION
TO ROWIDS:
BITMAP OR

Description
Converts bitmaps into ROWIDs to access a table
COUNT: Returns the number of entries if the actual values
are not needed
Computes the bitwise OR of two bitmaps

BITMAP AND

Computes the bitwise AND of two bitmaps

BITMAP INDEX

SINGLE VALUE: Looks up the bitmap for a single key


RANGE SCAN: Retrieves bitmaps for a value range

BITMAP MERGE

Merges several bitmaps resulting from a range scan into


one (using a bitwise AND operator)

Oracle Database 11g: SQL Tuning Workshop 9 - 16

Hints for Query Transformation


NO_QUERY_TRANSFORMATION: The NO_QUERY_TRANSFORMATION hint instructs the
optimizer to skip all query transformations, including but not limited to OR-expansion, view
merging, subquery unnesting, star transformation, and materialized view rewrite.
USE_CONCAT: The USE_CONCAT hint forces combined OR conditions in the WHERE clause of a
query to be transformed into a compound query using the UNION ALL set operator. Generally, this
transformation occurs only if the cost of the query using the concatenations is cheaper than the cost
without them. The USE_CONCAT hint disables IN-list processing.
NO_EXPAND: The NO_EXPAND hint prevents the cost-based optimizer from considering ORexpansion for queries having OR conditions or IN-lists in the WHERE clause. Usually, the optimizer
considers using OR expansion and uses this method if it decides that the cost is lower than not
using it.
REWRITE: The REWRITE hint instructs the optimizer to rewrite a query in terms of materialized
views, when possible, without cost consideration. Use the REWRITE hint with or without a view list.
This course does not deal with Materialized Views.
UNNEST: The UNNEST hint instructs the optimizer to unnest and merge the body of the subquery
into the body of the query block that contains it, allowing the optimizer to consider them together
when evaluating access paths and joins.

Oracle Database 11g: SQL Tuning Workshop 9 - 17

Hints for Query Transformation (continued)


MERGE: The MERGE hint lets you merge a view for each query. If a views query contains a GROUP
BY clause or a DISTINCT operator in the SELECT list, then the optimizer can merge the views
query into the accessing statement only if complex view merging is enabled. This is the case by
default, but you can disable this mechanism using the NO_MERGE hint for example. Complex
merging can also be used to merge an IN subquery into the accessing statement if the subquery is not
correlated.
When the MERGE hint is used without an argument, it should be placed in the view query block.
When MERGE is used with the view name as an argument, it should be placed in the surrounding
query.
NO_MERGE: The NO_MERGE hint causes the Oracle server not to merge views that can be merged.
This hint gives the user more influence over the way in which the view is accessed. When the
NO_MERGE hint is used without an argument, it should be placed in the view query block. When
NO_MERGE is used with the view name as an argument, it should be placed in the surrounding query.

Oracle Database 11g: SQL Tuning Workshop 9 - 18

Hints for Query Transformation (continued)


STAR_TRANSFORMATION: The STAR_TRANSFORMATION hint causes the optimizer to use the
best plan in which the transformation has been used. Without the hint, the optimizer could make a
cost-based decision to use the best plan that is generated without the transformation, instead of the
best plan for the transformed query.
Even if the hint is given, there is no guarantee that the transformation will take place. The optimizer
generates the subqueries only if it seems reasonable to do so. If no subqueries are generated, there is
no transformed query, and the best plan for the untransformed query is used regardless of the hint.
FACT: The FACT hint is used in the context of the star transformation to indicate to the
transformation that the hinted table should be considered as a fact table.
NO_FACT: The NO_FACT hint is used in the context of the star transformation to indicate to the
transformation that the hinted table should not be considered as a fact table.

Oracle Database 11g: SQL Tuning Workshop 9 - 19

Hints for Join Orders


The following hints are used to suggest join orders:
ORDERED: The ORDERED hint causes the Oracle server to join tables in the order in which they
appear in the FROM clause. If you omit the ORDERED hint from a SQL statement performing a join,
the optimizer selects the order in which to join the tables. You might want to use the ORDERED hint
to specify a join order if you know something that the optimizer does not know about the number of
rows that are selected from each table. With a nested loops example, the most precise method is to
order the tables in the FROM clause in the order of the keys in the index, with the large table at the
end. Then use the following hints:
/*+ ORDERED USE_NL(FACTS) INDEX(facts fact_concat) */

Here, facts is the table and fact_concat is the index. A more general method is to use the
STAR hint.
LEADING: The LEADING hint instructs the optimizer to use the specified set of tables as the prefix
in the execution plan. The LEADING hint is ignored if the tables specified cannot be joined first in
the order specified because of dependencies in the join graph. If you specify two or more LEADING
hints on different tables, all the hints are ignored. If you specify the ORDERED hint, it overrides all
LEADING hints.

Oracle Database 11g: SQL Tuning Workshop 9 - 20

Hints for Join Operations


Each hint described here suggests a join operation for a table. In the hint, you must specify a table
exactly the same way as it appears in the statement. If the statement uses an alias for the table, you
must use the alias rather than the table name in the hint. However, the table name in the hint should
not include the schema name if the schema name is present in the statement. Use of the USE_NL and
USE_MERGE hints is recommended with the ORDERED hint. The Oracle server uses these hints
when the referenced table is forced to be the inner table of a join; the hints are ignored if the
referenced table is the outer table.
USE_NL: The USE_NL hint causes the Oracle server to join each specified table to another row
source with a nested loops join, using the specified table as the inner table. If you want to optimize
the statement for best response time or for the minimal elapsed time that is necessary to return the
first row selected by the query, rather than for best throughput, then you can force the optimizer to
select a nested loop join by using the USE_NL hint.
USE_NL_WITH_INDEX: The USE_NL_WITH_INDEX hint is similar to the USE_NL hint.
However, if no index is specified, the optimizer must be able to use some index with at least one join
predicate as the index key. If an index is specified, the optimizer must be able to use that index with
at least one join predicate as the index key.
NO_USE_NL: The NO_USE_NL hint causes the optimizer to exclude the nested loops join.
However, in some cases tables can only be joined using nested loops. In such cases, the optimizer
Oracle Database 11g: SQL Tuning Workshop 9 - 21

ignores the hint for those tables.

Oracle Database 11g: SQL Tuning Workshop 9 - 21

Hints for Join Operations (continued)


In many cases, a nested loop join returns the first row faster than a sort-merge join does. A nested
loop join can return the first row after reading the first selected row from one table and the first
matching row from the other and combining them. But a sort-merge join cannot return the first row
until after reading and sorting all selected rows of both tables and then combining the first rows of
each sorted row source.
In the following statement in which a nested loop is forced through a hint, orders is accessed
through a full table scan and the l.order_id = h.order_id filter condition is applied to
every row. For every row that meets the filter condition, order_items is accessed through the
index order_id.
SELECT /*+ USE_NL(l h) */ h.customer_id, l.unit_price * l.quantity
FROM oe.orders h ,oe.order_items l
WHERE l.order_id = h.order_id;

Adding an INDEX hint to the query could avoid the full table scan on orders, resulting in an
execution plan similar to one that is used on larger systems, even though it might not be particularly
efficient here.
USE_MERGE: The USE_MERGE hint causes the Oracle server to join each specified table with
another row source by using a sort-merge join, as in the following example:
SELECT /*+USE_MERGE(employees departments)*/ * FROM employees,
departments WHERE employees.department_id = departments.department_id;

NO_USE_MERGE: The NO_USE_MERGE hint causes the optimizer to exclude the sort-merge join to
join each specified table to another row source using the specified table as the inner table.
USE_HASH: The USE_HASH hint causes the Oracle server to join each specified table with another
row source using a hash join, as in the following example:
SELECT /*+USE_HASH(l l2) */ l.order_date, l.order_id,
l2.product_id, SUM(l2.unit_price*quantity)
FROM oe.orders l, oe.order_items l2
WHERE l.order_id = l2.order_id
GROUP BY l2.product_id, l.order_date, l.order_id;
Here is another example:
SELECT /*+use_hash(employees departments)*/ *
FROM hr.employees, hr.departments
WHERE employees.department_id = departments.department_id;
NO_USE_HASH: The NO_USE_HASH hint causes the optimizer to exclude the hash join to join each
specified table to another row source using the specified table as the inner table.
DRIVING_SITE: This hint instructs the optimizer to execute the query at a different site than that
selected by the database. This hint is useful if you are using distributed query optimization to decide
on which site a join should be executed.

Oracle Database 11g: SQL Tuning Workshop 9 - 22

Additional Hints
APPEND: The APPEND hint lets you enable direct-path INSERT if your database runs in serial
mode. Your database is in serial mode if you are not using Enterprise Edition. Conventional INSERT
is the default in serial mode, and direct-path INSERT is the default in parallel mode. In direct-path
INSERT, data is appended to the end of the table rather than using existing space currently allocated
to the table. As a result, direct-path INSERT can be considerably faster than the conventional
INSERT.
NOAPPEND: The NOAPPEND hint enables direct-path INSERT by disabling parallel mode for the
duration of the INSERT statement. (Conventional INSERT is the default in serial mode, and directpath INSERT is the default in parallel mode.)
ORDERED_PREDICATES: The ORDERED_PREDICATES hint forces the optimizer to preserve the
order of predicate evaluation, except for predicates that are used as index keys. Use this hint in the
WHERE clause of SELECT statements.

Oracle Database 11g: SQL Tuning Workshop 9 - 23

Additional Hints (continued)


If you do not use the ORDERED_PREDICATES hint, the Oracle server evaluates all predicates in the
following order:
1. Predicates without user-defined functions, type methods, or subqueries are evaluated first, in the
order specified in the WHERE clause.
2. Predicates with user-defined functions and type methods that have user-computed costs are
evaluated next, in the order of increasing cost.
3. Predicates with user-defined functions and type methods without user-computed costs are
evaluated next, in the order specified in the WHERE clause.
4. Predicates that are not specified in the WHERE clause (for example, predicates that are
transitively generated by the optimizer) are evaluated next.
5. Predicates with subqueries are evaluated last, in the order specified in the WHERE clause.
CURSOR_SHARING_EXACT: The Oracle server can replace literals in SQL statements with bind
variables if it is safe to do so. This is controlled with the CURSOR_SHARING startup parameter. The
CURSOR_SHARING_EXACT hint causes this behavior to be disabled. In other words, the Oracle
server executes the SQL statement without any attempt to replace literals with bind variables.
CACHE: The CACHE hint instructs the optimizer to place the blocks retrieved for the table in the
corresponding hot part of the buffer cache when a full table scan is performed. This hint is useful for
small lookup tables.
The CACHE and NOCACHE hints affect system statistics table scans (long tables) and table scans
(short tables), as shown in the V$SYSSTAT data dictionary view.
PUSH_PRED: The PUSH_PRED hint instructs the optimizer to push a join predicate into the view.
PUSH_SUBQ: The PUSH_SUBQ hint instructs the optimizer to evaluate nonmerged subqueries at the
earliest possible step in the execution plan. Generally, subqueries that are not merged are executed as
the last step in the execution plan. If the subquery is relatively inexpensive and reduces the number
of rows significantly, evaluating the subquery earlier can improve performance. This hint has no
effect if the subquery is applied to a remote table or one that is joined using a merge join.
DYNAMIC_SAMPLING: The DYNAMIC_SAMPLING hint lets you control dynamic sampling to
improve server performance by determining more accurate selectivity and cardinality estimates. You
can set the value of DYNAMIC_SAMPLING to a value from 0 to 10. The higher the level, the more
effort the compiler puts into dynamic sampling and the more broadly it is applied. Sampling defaults
to the cursor level unless you specify a table.
Consider the following example:
SELECT /*+ dynamic_sampling(1) */ * FROM ...

This example enables dynamic sampling if all the following conditions are true:
There is more than one table in the query.
At least one table has not been analyzed and has no indexes.
The optimizer determines that a relatively expensive table scan is required for the table that has
not been analyzed.

Oracle Database 11g: SQL Tuning Workshop 9 - 24

Additional Hints (continued)


MONITOR: The MONITOR hint forces real-time SQL monitoring for the query, even if the statement
is not long running. This hint is valid only when the CONTROL_MANAGEMENT_PACK_ACCESS
parameter is set to DIAGNOSTIC+TUNING.
NO_MONITOR: The NO_MONITOR hint disables real-time SQL monitoring for the query.
RESULT_CACHE: The RESULT_CACHE hint instructs the database to cache the results of the
current query or query fragment in memory and then to use the cached results in future executions of
the query or query fragment.
NO_RESULT_CACHE: The optimizer caches query results in the result cache if the
RESULT_CACHE_MODE initialization parameter is set to FORCE. In this case, the
NO_RESULT_CACHE hint disables such caching for the current query.
OPT_PARAM: The OPT_PARAM hint lets you set an initialization parameter for the duration of the
current query only. This hint is valid only for the following parameters:
OPTIMIZER_DYNAMIC_SAMPLING, OPTIMIZER_INDEX_CACHING,
OPTIMIZER_INDEX_COST_ADJ, OPTIMIZER_SECURE_VIEW_MERGING, and
STAR_TRANSFORMATION_ENABLED

Oracle Database 11g: SQL Tuning Workshop 9 - 25

Hints and Views


You should not use hints in or on views because views can be defined in one context and used in
another; such hints can result in unexpected plans. In particular, hints in views are handled differently
from hints on views depending on whether or not the view is mergeable into the top-level query.
View Optimization
The statement is normally transformed into an equivalent statement that accesses the views base
tables. The optimizer can use one of the following techniques to transform the statement:
Merge the views query into the referencing query block in the accessing statement.
Push the predicate of the referencing query block inside the view.
When these transformations are impossible, the views query is executed and the result is accessed as
if it were a table. This appears as a VIEW step in execution plans.

Oracle Database 11g: SQL Tuning Workshop 9 - 26

Hints and Views (continued)


Mergeable Views
The optimizer can merge a view into a referencing query block if the view definition does not
contain the following:
Set operators (UNION, UNION ALL, INTERSECT, MINUS)
The CONNECT BY clause
The ROWNUM pseudocolumn
Group functions (AVG, COUNT, MAX, MIN, SUM) in the select list
Hints and Mergeable Views
Optimization-approach and goal hints can occur in a top-level query or in views:
If there is such a hint in the top-level query, that hint is used regardless of any such hints in the
views.
If there is no top-level optimizer-mode hint, mode hints in referenced views are used as long as
all mode hints in the views are consistent.
If two or more mode hints in the referenced views conflict, all mode hints in the views are
discarded and the session mode is used, whether default or user specified.
Access-method and join hints on referenced views are ignored unless the view contains a single table
(or references another view with a single table). For such single-table views, an access-method hint
or a join hint on the view applies to the table in the view.
Access-method and join hints can also appear in a view definition:
If the view is a subquery (that is, if it appears in the FROM clause of a SELECT statement), all
access-method and join hints in the view are preserved when the view is merged with the toplevel query.
For views that are not subqueries, access-method and join hints in the view are preserved only if
the top-level query references no other tables or views (that is, if the FROM clause of the
SELECT statement contains only the view).
Hints and Nonmergeable Views
With nonmergeable views, optimizer-mode hints in the view are ignored. The top-level query decides
the optimization mode.
Because nonmergeable views are optimized separately from the top-level query, access-method and
join hints in the view are always preserved. For the same reason, access-method hints on the view in
the top-level query are ignored.
However, join hints on the view in the top-level query are preserved because (in this case) a
nonmergeable view is similar to a table.

Oracle Database 11g: SQL Tuning Workshop 9 - 27

Global Table Hints


Hints that specify a table generally refer to tables in the DELETE, SELECT, or UPDATE query block
in which the hint occurs, rather than to tables inside any views that are referenced by the statement.
When you want to specify hints for tables that appear inside views, it is recommended that you use
global hints instead of embedding the hint in the view.
The table hints can be transformed into global hints by using an extended table specification syntax
that includes view names with the table name as shown in the slide. In addition, an optional query
block name can precede the table specification.
For example, by using the global hint structure, you can avoid the modification of a view with the
specification of an index hint in the body of view.
Note: If a global hint references a table name or alias that is used twice in the same query (for
example, in a UNION statement), the hint applies to only the first instance of the table (or alias).

Oracle Database 11g: SQL Tuning Workshop 9 - 28

Specifying a Query Block in a Hint


You can specify an optional query block name in many hints to specify the query block to which the
hint applies. This syntax lets you specify in the outer query a hint that applies to an inline view.
The syntax of the query block argument is of the @queryblock form, where queryblock is an
identifier that specifies a query block in the query. The queryblock identifier can either be
system-generated or user-specified. When you specify a hint in the query block itself to which the
hint applies, you do not have to specify the @queryblock syntax.
The slide gives you an example. You can see that the SELECT statement uses an inline view. The
corresponding query block is given the name strange through the use of the QB_NAME hint.
The example assumes that there is an index on the DEPTNO column of the DEPT table so that the
optimizer would normally choose that index to access the DEPT table. However, because you specify
the FULL hint to apply to the strange query block in the main query block, the optimizer does not
use the index in question. You can see that the execution plan exhibits a full table scan on the DEPT
table. In addition, the output of the plan clearly shows the system-generated names for each query
block in the original query.

Oracle Database 11g: SQL Tuning Workshop 9 - 29

Specifying a Full Set of Hints


When using hints, you might sometimes need to specify a full set of hints to ensure the optimal
execution plan. For example, if you have a very complex query consisting of many table joins, and if
you specify only the INDEX hint for a given table, then the optimizer needs to determine the
remaining access paths to be used as well as the corresponding join methods. Therefore, even though
you gave the INDEX hint, the optimizer might not necessarily use that hint because the optimizer
might have determined that the requested index cannot be used due to the join methods and access
paths that were selected by the optimizer.
In the example, the LEADING hint specifies the exact join order to be used. The join methods to be
used on the different tables are also specified.

Oracle Database 11g: SQL Tuning Workshop 9 - 30

Summary
In this lesson, you should have learned about additional optimizer settings and hints.
By using hints, you can influence the optimizer at the statement level. Use hints as a last remedy
when tuning SQL statements. There are several hint categories, one of which includes hints for
access-path methods.
To specify a hint, use the hint syntax in the SQL statement.

Oracle Database 11g: SQL Tuning Workshop 9 - 31

Oracle Database 11g: SQL Tuning Workshop 9 - 32

Oracle Database 11g: SQL Tuning Workshop 10 - 2

End-to-End Application Tracing Challenge


Oracle Database implements tracing by generating a trace file for each server process when you
enable the tracing mechanism.
Tracing a specific client is usually not a problem in the dedicated server model as a single dedicated
process serves a session during its lifetime. All the trace information for the session can be seen from
the trace file belonging to the dedicated server serving it. However, in a shared server configuration,
a client is serviced by different processes from time-to-time. The trace pertaining to the user session
is scattered across different trace files belonging to different processes. This makes it difficult for you
to get a complete picture of the life cycle of a session.
Moreover, what if you want to consolidate trace information for a particular service for performance
or debugging purposes? This is also difficult because you have multiple clients using the same
service and each generating trace files belonging to the server process serving it.

Oracle Database 11g: SQL Tuning Workshop 10 - 3

End-to-End Application Tracing


End-to-End Application Tracing simplifies the process of diagnosing performance problems in
multitier environments. In multitier environments, a request from an end client is routed to different
database sessions by the middle tier, making it difficult to track a client across different database
sessions. End-to-End Application Tracing uses a client identifier to uniquely trace a specific end
client through all tiers to the database server.
You can used End-to-End Application Tracing to identify the source of an excessive workload, such
as a high-load SQL statement. Also, you can identify what a users session does at the database level
to resolve user performance problems.
End-to-End Application Tracing also simplifies management of application workloads by tracking
specific modules and actions in a service. Workload problems can be identified by End-to-End
Application Tracing for:
Client identifier: Specifies an end user based on the logon Id, such as HR.
Service: Specifies a group of applications with common attributes, service level thresholds, and
priorities; or a single application
Module: Specifies a functional block within an application
Action: Specifies an action, such as an INSERT or an UPDATE operation, in a module
Session: Specifies a session based on a given database session identifier (SID).
The primary interface for End-to-End Application Tracing is Enterprise Manager. Other tools listed
Oracle Database 11g: SQL Tuning Workshop 10 - 4

in the slide are discussed later in this lesson.

Oracle Database 11g: SQL Tuning Workshop 10 - 4

Location for Diagnostic Traces


Automatic Diagnostic Repository (ADR) is a file-based repository for database diagnostic data such
as traces, incident dumps, packages, the alert log, Health Monitor reports, core dumps, and so on.
Starting with Oracle Database 11g, Release 1, the traditional _DUMP_DEST initialization
parameters are ignored. The ADR root directory is known as the ADR base. Its location is set by the
DIAGNOSTIC_DEST initialization parameter. The table shown in the slide describes the different
classes of trace data and dumps that reside both in Oracle Database 10g (and earlier releases) and in
Oracle Database 11g. With Oracle Database 11g, there is no distinction between foreground and
background trace files. Both types of files go into the $ADR_HOME/trace directory. You can use
V$DIAG_INFO to list some important ADR locations.
All nonincident traces are stored inside the TRACE subdirectory. This is the main difference from
previous releases, where critical error information is dumped into the corresponding process trace
files instead of incident dumps. Incident dumps are placed in files separated from the normal process
trace files starting with Oracle Database 11g.
Note: The main difference between a trace and a dump is that a trace is more of a continuous output,
such as when SQL tracing is turned on, and a dump is a one-time output in response to an event, such
as an incident. Also, a core is a binary memory dump that is port specific.
In the slide, $ADR_HOME is used to denote the ADR Home directory defined by the
DIAGNOSTIC_DEST initialization parameter. However, there is no official environment variable
Oracle Database 11g: SQL Tuning Workshop 10 - 5

called ADR_HOME.

Oracle Database 11g: SQL Tuning Workshop 10 - 5

What Is a Service?
The concept of a service was first introduced in Oracle8i as a means for the listener to perform
connection load balancing between nodes and instances of a cluster. However, the concept,
definition, and implementation of services have been dramatically expanded. A service organizes
work execution within the database to make it more manageable, measurable, tunable, and
recoverable. A service is a grouping of related tasks within the database with common functionality,
quality expectations, and priority relative to other services. A service provides a single-system image
for managing competing applications that run within a single instance and across multiple instances
and databases.
Using standard interfaces, Enterprise Manager, and SRVCTL, services can be configured,
administered, enabled, disabled, and measured as a single entity.
Services provide availability. Following outages, a service is recovered quickly and automatically at
surviving instances.
Services provide a dimension to performance tuning. With services, workloads are visible and
measurable. Tuning by service and SQL replaces tuning by session and SQL in the majority of
systems where sessions are anonymous and shared.
From a tracing point of view, a service provides a handle that permits capturing trace information by
service name regardless of the session.

Oracle Database 11g: SQL Tuning Workshop 10 - 6

Use Services with Client Applications


Applications and middle-tier connection pools select a service by using the Transparent Network
Substrate (TNS) connection descriptor.
The selected service must match the service that has been created.
The first example in the slide shows the TNS connect descriptor that can be used to access the ERP
service.
The second example shows the thick Java Database Connectivity (JDBC) connection description
using the previously defined TNS connect descriptor.
The third example shows the thin JDBC connection description using the same TNS connect
descriptor.

Oracle Database 11g: SQL Tuning Workshop 10 - 7

Tracing Services
An application can qualify a service by MODULE and ACTION names to identify the important
transactions within the service. This enables you to locate the poorly performing transactions for
categorized workloads. This is important when you monitor performance in systems using
connection pools or transaction processing monitors. For these systems, the sessions are shared,
which makes accountability difficult. SERVICE_NAME, MODULE, ACTION,
CLIENT_IDENTIFIER, and SESSION_ID are actual columns in V$SESSION. SERVICE_NAME
is set automatically at login time for the user. MODULE and ACTION names are set by the application
by using the DBMS_APPLICATION_INFO PL/SQL package or special Oracle Call Interface (OCI)
calls. MODULE should be set to a user-recognizable name for the program that currently executes.
Likewise, ACTION should be set to a specific action or task that a user performs within a module
(for example, entering a new customer).
SESSION_ID is automatically set by the database when a session is created, and
CLIENT_IDENTIFIER can be set using the DBMS_SESSION.SET_IDENTIFIER procedure.

Oracle Database 11g: SQL Tuning Workshop 10 - 8

Tracing Services (continued)


The traditional method of tracing each session produces trace files with SQL commands that can
span workloads. This results in a hit-or-miss approach to diagnose problematic SQL. With the criteria
that you provide (SERVICE_NAME, MODULE, or ACTION), specific trace information is captured in
a set of trace files and combined into a single output trace file. This enables you to produce trace files
that contain SQL that is relevant to a specific workload. It is also possible to do the same for
CLIENT_IDs and SESSION_IDs.
Note: DBA_ENABLED_TRACES displays information about enabled traces.

Oracle Database 11g: SQL Tuning Workshop 10 - 9

Use Enterprise Manager to Trace Services


On the Performance page, you can click the Top Consumers link. The Top Consumers page is
displayed.
The Top Consumers page has several tabs for displaying your database as a single-system image. The
Overview tabbed page contains four pie charts: Top Clients, Top Services, Top Modules, and Top
Actions. Each chart provides a different perspective about the top resource consumers in your
database.
The Top Services tabbed page displays performance-related information for the services that are
defined in your database. On this page, you can enable or disable tracing at the service level.

Oracle Database 11g: SQL Tuning Workshop 10 - 10

Service Tracing: Example


In the first code box, all sessions that log in under the AP service are traced. A trace file is created for
each session that uses the service, regardless of the module and action. To be precise, you can trace
only specific tasks within a service. This is illustrated in the second example, where all sessions of
the AP service that execute the QUERY_DELINQUENT action within the PAYMENTS module are
traced.
Tracing by service, module, and action enable you to focus your tuning efforts on specific SQL,
rather than sifting through trace files with SQL from different programs. Only the SQL statements
that define this task are recorded in the trace file. This complements collecting statistics by service,
module, and action because relevant wait events for an action can be identified.
You can also start tracing for a particular client identifier as shown by the third example. In this
example, C4 is the client identifier for which SQL tracing is to be enabled. The TRUE argument
specifies that wait information is present in the trace file. The FALSE argument specifies that bind
information is not present in the trace file.
Although not shown in the slide, you can use the CLIENT_ID_TRACE_DISABLE procedure to
disable tracing globally for the database for a given client identifier. To disable tracing, for the
previous example, execute the following command:
EXECUTE DBMS_MONITOR.CLIENT_ID_TRACE_DISABLE(client_id => 'C4');
Note: CLIENT_IDENTIFIER can be set using the DBMS_SESSION.SET_IDENTIFIER
Oracle Database 11g: SQL Tuning Workshop 10 - 11

procedure.

Oracle Database 11g: SQL Tuning Workshop 10 - 11

Session Level Tracing: Example


You can use tracing to debug performance problems. Trace-enabling procedures have been
implemented as part of the DBMS_MONITOR package. These procedures enable tracing globally for
a database.
You can use the DATABASE_TRACE_ENABLE procedure to enable instance-wide session-level SQL
tracing. The procedure has the following parameters:
WAITS: Specifies whether wait information is to be traced
BINDS: Specifies whether bind information is to be traced
INSTANCE_NAME: Specifies the instance for which tracing is to be enabled. Omitting
INSTANCE_NAME means that the session-level tracing is enabled for the whole database.
Use the DATABASE_TRACE_DISABLE procedure to disable SQL tracing for the whole database or
a specific instance.
Similarly, you can use the SESSION_TRACE_ENABLE procedure to enable tracing for a given
database session identifier on the local instance. The SID and SERIAL# information can be found
from V$SESSION.
Use the SESSION_TRACE_DISABLE procedure to disable the trace for a given database session
identifier and serial number.
Note: SQL Trace involves some overhead, so you usually do not want to enable SQL Trace at the
instance level.
Oracle Database 11g: SQL Tuning Workshop 10 - 12

Trace Your Own Session


While the DBMS_MONITOR package can be invoked only by a user with the DBA role, any user can
enable SQL tracing for his or her own session by using the DBMS_SESSION package. The
SESSION_TRACE_ENABLE procedure can be invoked by any user to enable session-level SQL
tracing for his or her own session. An example is shown in the slide.
You can then use the DBMS_SESSION.SESSION_TRACE_DISABLE procedure to stop dumping
to your trace file.
TRACEFILE_IDENTIFIER is an initialization parameter that specifies a custom identifier that
becomes part of the Oracle trace file name. You can use such a custom identifier to identify a trace
file simply from its name and without opening it or view its contents. Each time this parameter is
dynamically modified at the session level, the next trace dump is written to a trace file, which has the
new parameter value embedded in its name. This parameter can only be used to change the name of
the foreground process trace file; the background processes continue to have their trace files named
in the regular format. For foreground processes, the TRACEID column of the V$PROCESS view
contains the current value of this parameter. When this parameter value is set, the trace file name has
the following format: sid_ora_pid_traceid.trc
Note: The SQL_TRACE initialization parameter is deprecated as of Oracle Database 10g. You can
obtain the complete list of deprecated parameters by using the following statement:
SELECT name FROM v$parameter WHERE isdeprecated = 'TRUE'
Oracle Database 11g: SQL Tuning Workshop 10 - 13

The trcsess Utility


The trcsess utility consolidates trace output from selected trace files on the basis of several
criteria: session ID, client identifier, service name, action name, and module name. After trcsess
merges the trace information into a single output file, the output file can be processed by tkprof.
When using the DBMS_MONITOR.SERV_MOD_ACT_TRACE_ENABLE procedure, tracing
information is present in multiple trace files and you must use the trcsess tool to collect it into a
single file.
The trcsess utility is useful for consolidating the tracing of a particular session or service for
performance or debugging purposes.
Tracing a specific session is usually not a problem in the dedicated server model because a single
dedicated process serves a session during its lifetime. All the trace information for the session can be
seen from the trace file belonging to the dedicated server that serves it. However, tracing a service
might become a complex task even in the dedicated server model.
Moreover, in a shared-server configuration, a user session is serviced by different processes from
time-to-time. The trace pertaining to the user session is scattered across different trace files
belonging to different processes. This makes it difficult to get a complete picture of the life cycle of a
session.

Oracle Database 11g: SQL Tuning Workshop 10 - 14

Invoking the trcsess Utility


The syntax for the trcsess utility is shown in the slide, where:
output specifies the file where the output is generated. If this option is not specified, standard
output is used for the output.
session consolidates the trace information for the session specified. The session identifier is a
combination of session index and session serial number, such as 21.2371. You can locate these
values in the V$SESSION view.
clientid consolidates the trace information for the given client identifier.
service consolidates the trace information for the given service name.
action consolidates the trace information for the given action name.
module consolidates the trace information for the given module name.
<trace file names> is a list of all the trace file names, separated by spaces, in which
trcsess should look for trace information. The wildcard character * can be used to specify
the trace file names. If trace files are not specified, all the files in the current directory are taken
as input to trcsess. You can find trace files in ADR.
Note: One of the session, clientid, service, action, or module options must be
specified. If there are more than one options specified, the trace files, which satisfy all the criteria
specified are consolidated into the output file.

Oracle Database 11g: SQL Tuning Workshop 10 - 15

The trcsess Utility: Example


The example in the slide illustrates a possible use of the trcsess utility. The example assumes that
you have three different sessions: Two sessions that are traced (left and right), and one session
(center) that enables or disables tracing and concatenates trace information from the previous two
sessions.
The first and second session set their client identifier to the HR session value. This is done
using the DBMS_SESSION package. Then, the third session enables tracing for these two sessions
using the DBMS_MONITOR package.
At that point, two new trace files are generated in ADR; one for each session that is identified with
the HR session client identifier.
Each traced session now executes its SQL statements. Every statement generates trace information in
its own trace file in ADR.
Then, the third session stops trace generation using the DBMS_MONITOR package, and consolidates
trace information for the HR session client identifier in the mytrace.trc file. The example
assumes that all trace files are generated in the
$ORACLE_BASE/diag/rdbms/orcl/orcl/trace directory, which is the default in most
cases.

Oracle Database 11g: SQL Tuning Workshop 10 - 16

SQL Trace File Contents


As seen already, a SQL trace file provides performance information on individual SQL statements. It
generates the following statistics for each statement:
Parse, execute, and fetch counts
CPU and elapsed times
Physical reads and logical reads
Number of rows processed
Misses on the library cache
Username under which each parse occurred
Each commit and rollback
Wait event data for each SQL statement, and a summary for each trace file
If the cursor for the SQL statement is closed, SQL Trace also provides row source information that
includes:
Row operations showing the actual execution plan of each SQL statement
Number of rows, number of consistent reads, number of physical reads, number of physical
writes, and time elapsed for each operation. This is possible only when the
STATISTICS_LEVEL initialization parameter is set to ALL.
Note: Using the SQL Trace facility can have a severe performance impact and may result in
increased system overhead, excessive CPU usage, and inadequate disk space.
Oracle Database 11g: SQL Tuning Workshop 10 - 17

SQL Trace File Contents: Example


There are multiple types of trace files that can be generated by the Oracle Database. The one that is
referred to in this lesson is generally called a SQL trace file.
The slide shows you a sample output from the mytrace.trc SQL trace file generated by the
previous example.
In this type of trace files, you can find (for each statement that were traced) the statement itself, with
some corresponding cursor details. You can see statistic details for each phase of the statements
execution: PARSE, EXEC, and FETCH. As you can see, you can have multiple FETCH for one EXEC
depending on the number of rows returned by your query.
Last part of the trace is the execution plan with some cumulated statistics for each row source.
Depending on the way you enabled tracing, you can also obtain information about wait events and
bind variables in the generated trace files.
Generally, you do not try to interpret the trace file itself. This is because you do not get an overall
idea of what your sessions did. For example, one session could have executed the same statement
multiple times at different moments. The corresponding traces are then scattered across the entire
trace file, which makes them hard to find.
Instead, you use another tool, such as tkprof to interpret the contents of the raw trace information.

Oracle Database 11g: SQL Tuning Workshop 10 - 18

Formatting SQL Trace Files: Overview


tkprof is an executable that parses SQL trace files to produce more readable output. Remember
that all the information in tkprof is available from the raw trace file. You can invoke a number of
sort functions using tkprof. There is a huge number of sort options that can be accessed by
entering tkprof at the command prompt. A useful starting point is the fchela sort option, which
orders the output by elapsed time fetching. The resultant .prf file contains the most timeconsuming SQL statement at the start of the file. Another useful parameter is sys. This can be used
to prevent SQL statements run as the SYS user from being displayed. This can make the output file
much shorter and easier to manage.
After a number of SQL trace files have been generated, you can perform the following:
Run tkprof on each individual trace file, producing a number of formatted output files, one
for each session.
Concatenate the trace files, and then run tkprof on the result to produce a formatted output
file for the entire instance.
Run the trcsess command-line utility to consolidate tracing information from several trace
files, then run tkprof on the result.
tkprof does not report COMMITs and ROLLBACKs that are recorded in the trace file.
Note: Set the TIMED_STATISTICS parameter to TRUE when tracing sessions because no
time-based comparisons can be made without this. TRUE is the default value with Oracle Database
Oracle Database 11g: SQL Tuning Workshop 10 - 19

11g.

Oracle Database 11g: SQL Tuning Workshop 10 - 19

Invoking the tkprof Utility


When you enter the tkprof command without any arguments, it generates a usage message
together with a description of all tkprof options. The various arguments are shown in the slide:
inputfile: Specifies the SQL trace input file
outputfile: Specifies the file to which tkprof writes its formatted output
waits: Specifies whether to record the summary for any wait events found in the trace file.
Values are YES or NO. The default is YES.
sorts: Sorts traced SQL statements in the descending order of specified sort option before
listing them into the output file. If more than one option is specified, the output is sorted in the
descending order by the sum of the values specified in the sort options. If you omit this
parameter, tkprof lists statements into the output file in the order of first use.
print: Lists only the first integer sorted SQL statements from the output file. If you omit this
parameter, tkprof lists all traced SQL statements. This parameter does not affect the optional
SQL script. The SQL script always generates insert data for all traced SQL statements.
aggregate: If set to NO, tkprof does not aggregate multiple users of the same SQL text.

Oracle Database 11g: SQL Tuning Workshop 10 - 20

Invoking the tkprof Utility (continued)


insert: Creates a SQL script to store the trace file statistics in the database. tkprof creates
this script with the name you specify for sqlscritfile. This script creates a table and
inserts a row of statistics for each traced SQL statement into the table.
sys: Enables and disables the listing of SQL statements issued by the SYS user, or recursive
SQL statements, into the output file. The default value of YES causes tkprof to list these
statements. The value of NO causes tkprof to omit them. This parameter does not affect the
optional SQL script. The SQL script always inserts statistics for all traced SQL statements,
including recursive SQL statements.
table: Specifies the schema and name of the table into which tkprof temporarily places
execution plans before writing them to the output file. If the specified table already exists,
tkprof deletes all rows in the table, uses it for the EXPLAIN PLAN statement (which writes
more rows into the table), and then deletes those rows. If this table does not exist, tkprof
creates it, uses it, and then drops it. The specified user must be able to issue INSERT, SELECT,
and DELETE statements against the table. If the table does not already exist, the user must also
be able to issue the CREATE TABLE and DROP TABLE statements. This option allows
multiple individuals to run tkprof concurrently with the same user in the EXPLAIN value.
These individuals can specify different TABLE values and avoid destructively interfering with
each others processing on the temporary plan table. If you use the EXPLAIN parameter without
the TABLE parameter, tkprof uses the PROF$PLAN_TABLE table in the schema of the user
specified by the EXPLAIN parameter. If you use the TABLE parameter without the EXPLAIN
parameter, tkprof ignores the TABLE parameter. If no plan table exists, tkprof creates the
PROF$PLAN_TABLE table and then drops it at the end.
explain: Determines the execution plan for each SQL statement in the trace file and writes
these execution plans to the output file. tkprof determines execution plans by issuing the
EXPLAIN PLAN statement after connecting to the system with the user and password specified
in this parameter. The specified user must have CREATE SESSION system privileges.
tkprof takes longer to process a large trace file if the EXPLAIN option is used.
record: Creates a SQL script with the specified file name statementfile with all the
nonrecursive SQL statements in the trace file. This can be used to replay the user events from
the trace file.
width: An integer that controls the output line width of some tkprof output, such as the
explain plan. This parameter is useful for post-processing of tkprof output.
The input and output files are the only required arguments.

Oracle Database 11g: SQL Tuning Workshop 10 - 21

tkprof Sorting Options


The table lists all the sort options you can use with the sort argument of tkprof.

Oracle Database 11g: SQL Tuning Workshop 10 - 22

Oracle Database 11g: SQL Tuning Workshop 10 - 23

Output of the tkprof Command


The tkprof command output lists the statistics for a SQL statement by the SQL processing step.
The step for each row that contains statistics is identified by the value of the call column.

PARSE

EXECUTE

FETCH

This step translates the SQL statement into an execution plan and includes
checks for proper security authorization and checks for the existence of
tables, columns, and other referenced objects.
This step is the actual execution of the statement by the Oracle server.
For the INSERT, UPDATE, and DELETE statements, this step modifies the
data (including sorts when needed). For the SELECT statements, this step
identifies the selected rows.
This step retrieves rows returned by a query and sorts them when needed.
Fetches are performed only for the SELECT statements.

Note: The PARSE value includes both hard and soft parses. A hard parse refers to the development of
the execution plan (including optimization); it is subsequently stored in the library cache. A soft
parse means that a SQL statement is sent for parsing to the database, but the database finds it in the
library cache and only needs to verify things, such as access rights. Hard parses can be expensive,
particularly due to the optimization. A soft parse is mostly expensive in terms of library cache
activity.
Oracle Database 11g: SQL Tuning Workshop 10 - 24

Output of the tkprof Command (continued)


The output is explained on the following page.
Sample output is as follows:
call
count cpu
elapsed
disk
query
current
rows
------- ------ -------- ---------- ---------- ---------- ---------- ---Parse
1
0.03
0.06
0
0
0
0
Execute
1
0.06
0.30
1
3
0
0
Fetch
2
0.00
0.46
0
0
0
1
------- ------ -------- ---------- ---------- ---------- ---------- ---total
4
0.09
0.83
1
3
0
1

Oracle Database 11g: SQL Tuning Workshop 10 - 25

Output of the tkprof Command (continued)


Next to the CALL column, tkprof displays the following statistics for each statement:
Count: Number of times a statement was parsed, executed, or fetched (Check this column for
values greater than 1 before interpreting the statistics in the other columns. Unless the
AGGREGATE = NO option is used, tkprof aggregates identical statement executions into one
summary table.)
CPU: Total CPU time in seconds for all parse, execute, or fetch calls
Elapsed: Total elapsed time in seconds for all parse, execute, or fetch calls
Disk: Total number of data blocks physically read from the data files on disk for all parse,
execute, or fetch calls
Query: Total number of buffers retrieved in consistent mode for all parse, execute, or fetch
calls (Buffers are usually retrieved in consistent mode for queries.)
Current: Total number of buffers retrieved in current mode (Buffers typically are retrieved in
current mode for data manipulation language statements. However, segment header blocks are
always retrieved in current mode.)
Rows: Total number of rows processed by the SQL statement (This total does not include rows
processed by subqueries of the SQL statement. For SELECT statements, the number of rows
returned appears for the fetch step. For the UPDATE, DELETE, and INSERT statements, the
number of rows processed appears for the execute step.)
Note
DISK is equivalent to physical reads from v$sysstat or AUTOTRACE.
QUERY is equivalent to consistent gets from v$sysstat or AUTOTRACE.
CURRENT is equivalent to db block gets from v$sysstat or AUTOTRACE.

Oracle Database 11g: SQL Tuning Workshop 10 - 26

Output of the tkprof Command (continued)


Recursive Calls
To execute a SQL statement issued by a user, the Oracle server must occasionally issue additional
statements. Such statements are called recursive SQL statements. For example, if you insert a row in
a table that does not have enough space to hold that row, the Oracle server makes recursive calls to
allocate the space dynamically. Recursive calls are also generated when data dictionary information
is not available in the data dictionary cache and must be retrieved from disk.
If recursive calls occur while the SQL Trace facility is enabled, tkprof marks them clearly as
recursive SQL statements in the output file. You can suppress the listing of recursive calls in the
output file by setting the SYS=NO command-line parameter. Note that the statistics for recursive
SQL statements are always included in the listing for the SQL statement that caused the recursive
call.
Library Cache Misses
tkprof also lists the number of library cache misses resulting from parse and execute steps for each
SQL statement. These statistics appear on separate lines following the tabular statistics.

Oracle Database 11g: SQL Tuning Workshop 10 - 27

Output of the tkprof Command (continued)


Row Source Operations
These provide the number of rows processed for each operation executed on the rows and additional
row source information, such as physical reads and writes; cr = consistent reads, w = physical writes,
r = physical reads, time = time (in microseconds).
Parsing User ID
This is the ID of the last user to parse the statement.
Row Source Operation
The row source operation shows the data sources for execution of the SQL statement. This is
included only if the cursor has been closed during tracing. If the row source operation does not
appear in the trace file, you may then want to see EXPLAIN PLAN.
Execution Plan
If you specify the EXPLAIN parameter on the tkprof command line, tkprof uses the EXPLAIN
PLAN command to generate the execution plan of each SQL statement traced. tkprof also displays
the number of rows processed by each step of the execution plan.
Note: Be aware that the execution plan is generated at the time that the tkprof command is run
and not at the time the trace file was produced. This could make a difference if, for example, an
index has been created or dropped since tracing the statements.
Optimizer Mode or Hint
This indicates the optimizer hint that is used during the execution of the statement. If there is no hint,
it shows the optimizer mode that is used.

Oracle Database 11g: SQL Tuning Workshop 10 - 28

tkprof Output with No Index: Example


The example in the slide shows that the aggregation of results across several executions (rows) is
being fetched from the CUSTOMERS table. It requires .12 second of CPU fetch time. The statement is
executed through a full table scan of the CUSTOMERS table, as you can see in the row source
operation of the output.
The statement must be optimized.
Note: If CPU or elapsed values are 0, timed_statistics is not set.

Oracle Database 11g: SQL Tuning Workshop 10 - 29

tkprof Output with Index: Example


The results shown in the slide indicate that CPU time was reduced to .01 second when an index was
created on the CUST_CITY column. These results may have been achieved because the statement
uses the index to retrieve the data. Additionally, because this example reexecutes the same statement,
most of the data blocks are already in memory. You can achieve significant improvements in
performance by indexing sensibly. Identify areas for potential improvement using the SQL Trace
facility.
Note: Indexes should not be built unless required. Indexes do slow down the processing of the
INSERT, UPDATE, and DELETE commands because references to rows must be added, changed, or
removed. Unused indexes should be removed. However, instead of processing all the application
SQL through EXPLAIN PLAN, you can use index monitoring to identify and remove any indexes
that are not used.

Oracle Database 11g: SQL Tuning Workshop 10 - 30

Summary
This lesson introduced you to the SQL Trace facility. By using SQL Trace, you can evaluate the
performance of SQL statements. After SQL Trace is enabled, you view execution plans and statistics
on SQL statements that are generated in a trace file.
To use SQL Trace, follow these steps:
Identify the appropriate initialization parameters that you want to use.
Enable SQL Trace.
Use tkprof to format the trace file that is generated.
Interpret the output of tkprof.

Oracle Database 11g: SQL Tuning Workshop 10 - 31

Oracle Database 11g: SQL Tuning Workshop 10 - 32

Oracle Database 11g: SQL Tuning Workshop 11 - 2

Tuning SQL Statements Automatically


Tuning SQL statements automatically is the capability of the query optimizer to automate the entire
SQL tuning process. This automatic process replaces manual SQL tuning, which is a complex,
repetitive, and time-consuming function. The SQL Tuning Advisor exposes the features of SQL
tuning to the user. The enhanced query optimizer has two modes:
In the normal mode, the optimizer compiles SQL and generates an execution plan. The normal
mode of the optimizer generates a reasonable execution plan for the vast majority of SQL
statements. In the normal mode, the optimizer operates with very strict time constraints, usually
a fraction of a second, during which it must find a good execution plan.
In the tuning mode, the optimizer performs additional analysis to check whether the execution
plan produced under the normal mode can be further improved. The output of the query
optimizer in the tuning mode is not an execution plan, but a series of actions, along with their
rationale and expected benefit (for producing a significantly superior plan). When called under
tuning mode, the optimizer is referred to as Automatic Tuning Optimizer (ATO). The tuning
performed by ATO is called system SQL tuning.
Under the tuning mode, the optimizer can take several minutes to tune a single statement. ATO is
meant to be used for complex and high-load SQL statements that have a nontrivial impact on the
entire system.

Oracle Database 11g: SQL Tuning Workshop 11 - 3

Application Tuning Challenges


The process of identifying high-load SQL statements and tuning them is very challenging even for an
expert. SQL tuning is not only one of the most critical aspects of managing the performance of a
database server, but also one of the most difficult tasks to accomplish. Starting with Oracle Database
10g, the task of identifying high-load SQL statements has been automated by Automatic Database
Diagnostic Monitor (ADDM). Even though the number of high-load SQL statements that are
identified by ADDM may represent a very small percentage of the total SQL workload, the task of
tuning them is still highly complex and requires a high level of expertise.
Also, the SQL tuning activity is a continuous task because the SQL workload can change relatively,
often when new application modules are deployed.
SQL Tuning Advisor, introduced with Oracle Database 10g, is designed to replace the manual tuning
of SQL statements. SQL statements that consume high resources (such as CPU, I/O, and temporary
space) are good candidates for SQL Tuning Advisor. The advisor receives one or more SQL
statements as input and then provides advice on how to optimize the execution plan, a rationale for
the advice, estimated performance benefits, and the actual command to implement the advice. You
accept the advice, thereby tuning the SQL statements. With the introduction of SQL Tuning Advisor,
you can now let the Oracle optimizer tune the SQL code for you.

Oracle Database 11g: SQL Tuning Workshop 11 - 4

SQL Tuning Advisor: Overview


SQL Tuning Advisor is primarily the driver of the tuning process. It calls Automatic Tuning
Optimizer (ATO) to perform the following four specific types of analysis:
Statistics Analysis: ATO checks each query object for missing or stale statistics and makes a
recommendation to gather relevant statistics. It also collects auxiliary information to supply
missing statistics or correct stale statistics in case recommendations are not implemented.
SQL Profiling: ATO verifies its own estimates and collects auxiliary information to remove
estimation errors. It also collects auxiliary information in the form of customized optimizer
settings, such as first rows and all rows, based on the past execution history of the SQL
statement. It builds a SQL Profile using the auxiliary information and makes a recommendation
to create it. When a SQL Profile is created, the profile enables the query optimizer, under normal
mode, to generate a well-tuned plan.
Access Path Analysis: ATO explores whether a new index can be used to significantly improve
access to each table in the query, and when appropriate makes recommendations to create such
indexes.
SQL Structure Analysis: Here, ATO tries to identify SQL statements that lend themselves to
bad plans, and makes relevant suggestions to restructure them. The suggested restructuring can
be syntactic as well as semantic changes to the SQL code.

Oracle Database 11g: SQL Tuning Workshop 11 - 5

Stale or Missing Object Statistics


The query optimizer relies on object statistics to generate execution plans. If these statistics are stale
or missing, the optimizer does not have the necessary information it needs and can generate
suboptimal execution plans.
ATO checks each query object for missing or stale statistics and produces two types of outputs:
Auxiliary information in the form of statistics for objects with no statistics, and statistic
adjustment factor for objects with stale statistics
Recommendations to gather relevant statistics for objects with stale or no statistics
For optimal results, you gather statistics when recommended and then rerun Automatic Tuning
Optimizer. However, you may be hesitant to accept this recommendation immediately because of the
impact it could have on other queries in the system.

Oracle Database 11g: SQL Tuning Workshop 11 - 6

SQL Statement Profiling


The main verification step during SQL Profiling is the verification of the query optimizers own
estimates of cost, selectivity, and cardinality for the statement that is tuned.
During SQL Profiling, ATO performs verification steps to validate its own estimates. The validation
consists of taking a sample of data and applying appropriate predicates to the sample. The new
estimate is compared to the regular estimate, and if the difference is large enough, a correction factor
is applied. Another method of estimate validation involves the execution of a fragment of the SQL
statement. The partial execution method is more efficient than the sampling method when the
respective predicates provide efficient access paths. ATO picks the appropriate estimate validation
method.
ATO also uses the past execution history of the SQL statement to determine correct settings. For
example, if the execution history indicates that a SQL statement is only partially executed the
majority of times, ATO uses the FIRST_ROWS optimization as opposed to ALL_ROWS.
ATO builds a SQL Profile if it has generated auxiliary information either during Statistics Analysis or
during SQL Profiling. When a SQL Profile is built, it generates a user recommendation to create a
SQL Profile.
In this mode, ATO can recommend the acceptance of the generated SQL Profile to activate it.

Oracle Database 11g: SQL Tuning Workshop 11 - 7

Plan Tuning Flow and SQL Profile Creation


A SQL Profile is a collection of auxiliary information that is built during automatic tuning of a SQL
statement. Thus, a SQL Profile is to a SQL statement what statistics are to a table or index. After it is
created, a SQL Profile is used in conjunction with the existing statistics by the query optimizer, in
normal mode, to produce a well-tuned plan for the corresponding SQL statement. A SQL Profile is
stored persistently in the data dictionary. However, the SQL profile information is not exposed
through regular dictionary views. After creation of a SQL Profile, every time the corresponding SQL
statement is compiled in normal mode, the query optimizer uses the SQL Profile to produce a welltuned plan.
The slide shows the process flow of the creation and use of a SQL Profile. The process consists of
two separate phases. They are system SQL tuning phase and regular optimization phase. During the
system SQL tuning phase, you select a SQL statement for system tuning and run SQL Tuning
Advisor by using either Database Control or the command-line interface. SQL Tuning Advisor
invokes ATO to generate tuning recommendations, possibly with a SQL Profile. If a SQL Profile is
built, you can accept it. When it is accepted, the SQL Profile is stored in the data dictionary. In the
next phase, when an end user issues the same SQL statement, the query optimizer (under normal
mode) uses the SQL Profile to build a well-tuned plan. The use of the SQL Profile remains
completely transparent to the end user and does not require changes to the application source code.

Oracle Database 11g: SQL Tuning Workshop 11 - 8

SQL Tuning Loop


The auxiliary information contained in a SQL Profile is stored in such a way that it stays relevant
after database changes, such as addition or removal of indexes, growth in the size of tables, and
periodic collection of database statistics. Therefore, when a profile is created, the corresponding plan
is not frozen (as when outlines are used).
However, a SQL Profile may not adapt to massive changes in the database or changes that have
accumulated over a long period of time. In such cases, a new SQL Profile needs to be built to replace
the old one.
For example, when a SQL Profile becomes outdated, the performance of the corresponding SQL
statement may become noticeably worse. In such a case, the corresponding SQL statement may start
showing up as high-load or top SQL, thus becoming again a target for system SQL Tuning. In such a
situation, ADDM again captures the statement as high-load SQL. If that happens, you can decide to
re-create a new profile for that statement.

Oracle Database 11g: SQL Tuning Workshop 11 - 9

Access Path Analysis


ATO also provides advice on indexes. Effective indexing is a well-known tuning technique that can
significantly improve the performance of SQL statements by reducing the need for full table scans.
Any index recommendations generated by ATO are specific to the SQL statement being tuned.
Therefore, it provides a quick solution to the performance problem associated with a single SQL
statement.
Because ATO does not perform an analysis of how its index recommendations are going to affect the
entire SQL workload, it recommends running the Access Advisor on the SQL statement along with a
representative SQL workload. The Access Advisor collects advice given on each statement of a SQL
workload and consolidates it into global advice for the entire SQL workload.
The Access Path Analysis can make the following recommendations:
Create new indexes if they provide significantly superior performance.
Run SQL Access Advisor to perform a comprehensive index analysis based on application
workload.

Oracle Database 11g: SQL Tuning Workshop 11 - 10

SQL Structure Analysis


The goal of the SQL Structure Analysis is to help you identify poorly written SQL statements as well
as to advise you on how to restructure them.
There are certain syntax variations that are known to have a negative impact on performance. In this
mode, ATO evaluates statements against a set of rules, identifying less efficient coding techniques,
and providing recommendations for an alternative statement where possible. The recommendation
may be very similar, but not precisely equivalent to the original query. For example, the NOT
EXISTS and NOT IN constructors are similar, but not exactly the same. Therefore, you have to
decide whether the recommendation is valid. For this reason, ATO does not automatically rewrite the
query, but gives advice instead.
The following categories of problems are detected by the SQL Structure Analysis:
Use of SQL constructors such as NOT IN instead of NOT EXISTS, or UNION instead of UNION
ALL
Use of predicates involving indexed columns with data-type mismatch that prevents the use of
the index
Design mistakes (such as Cartesian products)

Oracle Database 11g: SQL Tuning Workshop 11 - 11

SQL Tuning Advisor: Usage Model


SQL Tuning Advisor takes one or more SQL statements as input. The input can come from different
sources:
High-load SQL statements identified by ADDM
SQL statements that are currently in cursor cache
SQL statements from Automatic Workload Repository (AWR): A user can select any set of SQL
statements captured by AWR. This can be done using snapshots or baselines.
Custom workload: A user can create a custom workload consisting of statements of interest to
the user. These may be statements that are not in cursor cache and are not high-load to be
captured by ADDM or AWR. For such statements, a user can create a custom workload and tune
it using the advisor.
SQL statements from cursor cache, AWR, and custom workload can be filtered and ranked before
they are input to SQL Tuning Advisor.
For a multistatement input, a new object called SQL Tuning Set (STS) is provided. An STS stores
multiple SQL statements along with their execution information:
Execution context: Parsing schema name and bind values
Execution statistics: Average elapsed time and execution count
Note: Another STS can be a possible source for STS creation.

Oracle Database 11g: SQL Tuning Workshop 11 - 12

Database Control and SQL Tuning Advisor


The easiest way to access the SQL Tuning Advisor from Enterprise Manager is on the Advisor
Central page. On the Home page, click the Advisor Central link located in the Related Links section
to open the Advisor Central page.
On the Advisor Central page, click the SQL Advisors link. On the SQL Advisors page, click the SQL
Tuning Advisor link. This takes you to the Schedule SQL Tuning Advisor page. On this page, you
find links to various other pages. You click the Top Activity link to open the Top Activity page.

Oracle Database 11g: SQL Tuning Workshop 11 - 13

Running SQL Tuning Advisor: Example


You can use Database Control to identify the high-load or top SQL statements. There are several
locations in Database Control from where SQL Tuning Advisor can be launched with the identified
SQL statement or statements, or an STS:
Tuning ADDM-identified SQL statements: The ADDM Finding Details page shows high-load
SQL statements identified by ADDM. Each of these high-load SQL statements is known to
consume a significant proportion of one or more system resources such as CPU time, buffer gets,
disk reads, and so on. You can use this page to launch SQL Tuning Advisor on a selected highload SQL statement.
Tuning top SQL statements: Another SQL source is the list of top SQL statements. This is
shown in the slide. You can identify the list of top SQL statements by looking at their cumulative
execution statistics based on a selected time window. The user can select one or more top SQL
statements identified by their SQL IDs and launch SQL Tuning Advisor on them.
Tuning a SQL Tuning Set: It is also possible to look at various STSs created by different users.
An STS could have been created from a list of top SQL statements, by selecting SQL statements
from a range of snapshots created by AWR, or by selecting customized SQL statements.

Oracle Database 11g: SQL Tuning Workshop 11 - 14

Implementing Recommendations
After SQL Tuning Advisor is launched, Enterprise Manager automatically creates a tuning task,
provided the user has the appropriate ADVISOR privilege to do so. Enterprise Manager shows the
tuning task with automatic defaults on the Schedule SQL Tuning Advisor page shown in the previous
slide. On this page, the user can change the automatic defaults pertaining to a tuning task.
One of the important options is to select the scope of the tuning task. If you select the Limited option,
SQL Tuning Advisor produces recommendations based on statistics check, access path analysis, and
SQL structure analysis. No SQL Profile recommendation is generated with Limited scope. If you
select the Comprehensive option, SQL Tuning Advisor performs all recommendations under Limited
scope, and invokes the optimizer under SQL Profiling mode to build a SQL Profile if applicable.
With the Comprehensive option, you can also specify a time limit for the tuning task, which by
default is 30 minutes. Another useful option is to run the tuning task immediately or schedule it to be
run at a later time.
You can configure your tuning task on the Schedule Advisor page. To do this, select the Schedule
SQL Tuning Advisor action and click Go. Navigating back to the Top Activity page, you can click
the tuned statement to open the SQL Details page, where you can view the Tuning Information.
This shows you the completed tuning task. By clicking the task, you can see its general SQL Tuning
Results. By clicking the View button, you can see its details. As shown, a SQL Profile has been
created; you can implement it if you want, after you view the new plan.
Oracle Database 11g: SQL Tuning Workshop 11 - 15

SQL Access Advisor: Overview


Defining appropriate access structures to optimize SQL queries has always been a concern for the
developer. As a result, there have been many papers and scripts written as well as high-end tools
developed to address the matter. In addition, with the development of partitioning and materialized
view technology, deciding on access structures has become even more complex.
As part of the manageability improvements in Oracle Database 10g and 11g, SQL Access Advisor
has been introduced to address this very critical need.
SQL Access Advisor identifies and helps resolve performance problems relating to the execution of
SQL statements by recommending which indexes, materialized views, materialized view logs, or
partitions to create, drop, or retain. It can be run from Database Control or from the command line by
using PL/SQL procedures.
SQL Access Advisor takes an actual workload as input, or the Advisor can derive a hypothetical
workload from the schema. It then recommends the access structures for faster execution path. It
provides the following advantages:
Does not require you to have expert knowledge
Bases decision making on rules that actually reside in the cost-based optimizer (CBO)
Is synchronized with the optimizer and Oracle database enhancements
Is a single advisor covering all aspects of SQL access methods
Provides simple, user-friendly GUI wizards

Oracle Database 11g: SQL Tuning Workshop 11 - 16

Generates scripts for implementation of recommendations

Oracle Database 11g: SQL Tuning Workshop 11 - 16

SQL Access Advisor: Usage Model


SQL Access Advisor takes as input a workload that can be derived from multiple sources:
SQL cache, to take the current content of V$SQL
Hypothetical, to generate a likely workload from your dimensional model. This option is
interesting when your system is being initially designed.
SQL Tuning Sets, from the workload repository
SQL Access Advisor also provides powerful workload filters that you can use to target the tuning.
For example, a user can specify that the advisor should look at only the 30 most resource-intensive
statements in the workload, based on optimizer cost. For the given workload, the advisor then does
the following:
Simultaneously considers index solutions, materialized view solutions, partition solutions, or
combinations of all three
Considers storage for creation and maintenance costs
Does not generate drop recommendations for partial workloads
Optimizes materialized views for maximum query rewrite usage and fast refresh
Recommends materialized view logs for fast refresh
Recommends partitioning for tables, indexes, and materialized views
Combines similar indexes into a single index
Generates recommendations that support multiple workload queries

Oracle Database 11g: SQL Tuning Workshop 11 - 17

Possible Recommendations
SQL Access Advisor carefully considers the overall impact of recommendations and makes
recommendations by using only the known workload and supplied information. Two workload
analysis methods are available:
Comprehensive: With this approach, SQL Access Advisor addresses all aspects of tuning
partitions, materialized views, indexes, and materialized view logs. It assumes that the workload
contains a complete and representative set of application SQL statements.
Limited: Unlike the comprehensive workload approach, a limited workload approach assumes
that the workload contains only problematic SQL statements. Thus, advice is sought for
improving the performance of a portion of an application environment.
When comprehensive workload analysis is chosen, SQL Access Advisor forms a better set of global
tuning adjustments, but the effect may be a longer analysis time. As shown in the table, the chosen
workload approach determines the type of recommendations made by the advisor.
Note: Partition recommendations can work on only those tables that have at least 10,000 rows, and
workloads that have some predicates and joins on columns of the NUMBER or DATE type.
Partitioning advices can be generated only on these types of columns. In addition, partitioning
advices can be generated only for single-column interval and hash partitions. Interval partitioning
recommendations can be output as range syntax, but interval is the default. Hash partitioning is done
to leverage only partitionwise joins.
Oracle Database 11g: SQL Tuning Workshop 11 - 18

SQL Access Advisor Session: Initial Options


The next few slides describe a typical SQL Access Advisor session. You can access the SQL Access
Advisor by clicking the Advisor Central link on the Database Home page or through individual alerts
or performance pages that may include a link to facilitate solving a performance problem. The SQL
Access Advisor consists of several steps during which you supply the SQL statements to tune and the
types of access methods you want to use.
On the SQL Access Advisor: Initial Options page, you can select a template or task from which to
populate default options before starting the wizard. You can click Continue to start the wizard or
Cancel to go back to the Advisor Central page.
Note: The SQL Access Advisor may be interrupted while generating recommendations, thereby
allowing the results to be reviewed.
For general information about using SQL Access Advisor, see the Overview of the SQL Access
Advisor section in the lesson titled SQL Access Advisor of the Oracle Data Warehousing Guide.

Oracle Database 11g: SQL Tuning Workshop 11 - 19

SQL Access Advisor Session: Initial Options (continued)


If you select the Inherit Options from a Task or Template option on the Initial Options page, you
can select an existing task, or an existing template to inherit SQL Access Advisors options. By
default, the SQLACCESS_EMTASK template is used.
You can view the various options defined by a task or a template by selecting the corresponding
object and clicking View Options.

Oracle Database 11g: SQL Tuning Workshop 11 - 20

SQL Access Advisor: Workload Source


You can select your workload source from three different sources:
Current and Recent SQL Activity: This source corresponds to SQL statements that are still
cached in your System Global Area (SGA).
Use an existing SQL Tuning Set: You also have the possibility of creating and using a SQL
Tuning Set that holds your statements.
Hypothetical Workload: This option provides a schema that allows the advisor to search for
dimension tables and produce a workload. This is very useful to initially design your schema.
Using the Filter Options section, you can further filter your workload source. Filter options are:
Resource Consumption: Number of statements ordered by Optimizer Cost, Buffer Gets, CPU
Time, Disk Reads, Elapsed Time, Executions
Users
Tables
SQL Text
Module IDs
Actions

Oracle Database 11g: SQL Tuning Workshop 11 - 21

SQL Access Advisor: Recommendation Options


On the Recommendations Options page, you can select whether to limit the SQL Access Advisor to
recommendations based on a single access method. You can select the type of structures to be
recommended by the advisor. If none of the three possible ones are chosen, the advisor evaluates
existing structures instead of trying to recommend new ones.
You can use the Advisor Mode section to run the advisor in one of the two modes. These modes
affect the quality of recommendations as well as the length of time required for processing. In
Comprehensive Mode, the Advisor searches a large pool of candidates resulting in recommendations
of the highest quality. In Limited Mode, the advisor performs quickly, limiting the candidate
recommendations by working on the highest-cost statements only.
Note: You can click Advanced Options to show or hide options that allow you to set space
restrictions, tuning options, and default storage locations.

Oracle Database 11g: SQL Tuning Workshop 11 - 22

SQL Access Advisor: Schedule and Review


You can then schedule and submit your new analysis by specifying various parameters to the
scheduler. The possible options are shown in the screenshots in the slide.

Oracle Database 11g: SQL Tuning Workshop 11 - 23

SQL Access Advisor: Results


From the Advisor Central page, you can retrieve the task details for your analysis. By selecting the
task name in the Results section of the Advisor Central page, you can access the Results for Task
Summary page, on which you can see an overview of the Access Advisor findings. The page shows
you charts and statistics that provide overall workload performance and potential for improving
query execution time for the recommendations. You can use the page to show statement counts and
recommendation action counts.

Oracle Database 11g: SQL Tuning Workshop 11 - 24

SQL Access Advisor: Results and Implementation


To see other aspects of the results for the Access Advisor task, click one of the three other tabs on the
page, Recommendations, SQL Statements, or Details.
On the Recommendations page, you can drill down to each of the recommendations. For each of
them, you see important information in the Select Recommendations for Implementation table. You
can then select one or more recommendations and schedule their implementation.
If you click the ID for a particular recommendation, you are taken to the Recommendations page that
displays all actions for the specified recommendation and, optionally, to modify the tablespace name
of the statement. When you complete any changes, click OK to apply the changes. On the
Recommendations page, you can view the full text of an action by clicking the link in the Action
field for the specified action. You can view the SQL for all actions in the recommendation by
clicking Show SQL.
The SQL Statements page (not shown here) gives you a chart and a corresponding table that lists
SQL statements initially ordered by the largest cost improvement. The top SQL statement is
improved the most by implementing its associated recommendation.
The Details page shows you the workload and task options that were used when the task was created.
This page also gives you all journal entries that were logged during the task execution.
You can also schedule the implementation of the recommendations by clicking the Schedule

Oracle Database 11g: SQL Tuning Workshop 11 - 25

Implementation button.

Oracle Database 11g: SQL Tuning Workshop 11 - 25

SQL Tuning Loop


Oracle Database 10g introduced SQL Tuning Advisor to help application developers improve the
performance of SQL statements. The advisor targets the problem of poorly written SQL, in which
SQL statements have not been designed in the most efficient fashion. It also targets the (more
common) problem in which a SQL statement performs poorly because the optimizer generated a poor
execution plan due to lack of accurate and relevant data statistics. In all cases, the advisor makes
specific suggestions for speeding up SQL performance, but it leaves the responsibility of
implementing the recommendations to the user.
In addition to SQL Tuning Advisor, Oracle Database 10g has an automated process to identify highload SQL statements in your system. This is done by ADDM, which automatically identifies highload SQL statements that are good candidates for tuning.
However, major issues still remain: Although it is true that ADDM identifies some SQL that should
be tuned, users must manually look at ADDM reports and run SQL Tuning Advisor on the reports for
tuning.

Oracle Database 11g: SQL Tuning Workshop 11 - 26

Automatic SQL Tuning


Oracle Database 11g further automates the SQL Tuning process by identifying problematic SQL
statements, running SQL Tuning Advisor on them, and implementing the resulting SQL profile
recommendations to tune the statement without requiring user intervention. Automatic SQL Tuning
uses the AUTOTASK framework through a new task called Automatic SQL Tuning that runs every
night by default. A brief description of the automated SQL tuning process in Oracle Database 11g is
as follows:
Step 1: Based on the AWR Top SQL identification (SQLs that were top in four different time
periods: the past week, any day in the past week, any hour in the past week, or single response
time), Automatic SQL Tuning targets for automatic tuning.
Steps 2 and 3: While the Automatic SQL Tuning task executes during the maintenance window,
the previously identified SQL statements are automatically tuned by invoking SQL Tuning
Advisor. As a result, SQL profiles are created for them if needed. However, before making any
decision, the new profile is carefully tested.
Step 4: At any point in time, you can request a report about these automatic tuning activities.
You then have the option of checking the tuned SQL statements to validate or remove the
automatic SQL profiles that were generated.

Oracle Database 11g: SQL Tuning Workshop 11 - 27

Automatic Tuning Process


During the tuning process, all the recommendation types are considered and reported, but only SQL
profiles can be implemented automatically (when the ACCEPT_SQL_PROFILES task parameter is
set to TRUE). Otherwise, only the recommendation to create a SQL profile is reported in the
automatic SQL tuning reports.
In Oracle Database 11g, the performance improvement factor has to be at least three before a SQL
profile is implemented. As we have already mentioned, the Automatic SQL Tuning process
implements only SQL profile recommendations automatically. Other recommendations (to create
new indexes, refresh stale statistics, or restructure SQL statements) are generated as part of the SQL
tuning process, but are not implemented. These are left for you to review and implement manually, as
appropriate.
Here is a short description of the automatic tuning process in general:
Tuning is performed on a per-statement basis. Because only SQL profiles can be implemented, there
is no need to consider the effect of such recommendations on the workload as a whole. For each
statement (in the order of importance), the tuning process carries out each of the following steps:
1. Tune the statement by using SQL Tuning Advisor. Look for a SQL profile and, if it is found,
verify that the base optimizer statistics are current for it.

Oracle Database 11g: SQL Tuning Workshop 11 - 28

Automatic Tuning Process (continued)


2. If a SQL profile is recommended, perform the following:
1. Test the new SQL profile by executing the statement with and without it.
2. When a SQL profile is generated and it causes the optimizer to pick a different execution
plan for the statement, the advisor must decide whether to implement the SQL profile. It
makes its decision according to the flowchart in the slide. Although the benefit thresholds
here apply to the sum of CPU and input/output (I/O) time, SQL profiles are not accepted
when there is degradation in either statistic. So the requirement is that there is a threetimes improvement in the sum of CPU and I/O time, with neither statistic becoming worse.
In this way, the statement runs faster than it would without the profile, even with
contention in CPU or I/O.
3. If stale or missing statistics are found, make this information available to
GATHER_STATS_JOB.
Automatic implementation of tuning recommendations is limited to SQL profiles because they have
fewer risks. It is easy for you to revert the implementation.
Note: All SQL profiles are created in the standard EXACT mode. They are matched and tracked
according to the current value of the CURSOR_SHARING parameter. You are responsible for setting
CURSOR_SHARING appropriately for your workload.

Oracle Database 11g: SQL Tuning Workshop 11 - 29

Automatic SQL Tuning Controls


Here is a PL/SQL control example for the Automatic SQL Tuning task:
BEGIN
dbms_sqltune.set_tuning_task_parameter('SYS_AUTO_SQL_TUNING_TASK',
'LOCAL_TIME_LIMIT', 1400);
dbms_sqltune.set_tuning_task_parameter('SYS_AUTO_SQL_TUNING_TASK',
'ACCEPT_SQL_PROFILES', 'TRUE');
dbms_sqltune.set_tuning_task_parameter('SYS_AUTO_SQL_TUNING_TASK',
'MAX_SQL_PROFILES_PER_EXEC', 50);
dbms_sqltune.set_tuning_task_parameter('SYS_AUTO_SQL_TUNING_TASK',
'MAX_AUTO_SQL_PROFILES', 10002);
END;

The last three parameters in this example are supported only for the Automatic SQL Tuning task. You
can also use parameters such as LOCAL_TIME_LIMIT, or TIME_LIMIT, which are valid
parameters for the traditional SQL tuning tasks. One important example is to disable test-execute
mode (to save time) and to use only execution plan costs to decide about the performance by using
the TEST_EXECUTE parameter.
In addition, you can control when the Automatic SQL Tuning task runs and the CPU resources that it
is allowed to use.

Oracle Database 11g: SQL Tuning Workshop 11 - 30

Automatic SQL Tuning Task


As already stated, Automatic SQL Tuning is implemented as an automated maintenance task that is
itself called Automatic SQL Tuning. You can see some high-level information about the last runs of
the Automatic SQL Tuning task on the Automated Maintenance Tasks page. To open this page, on
your Database Control Home page, click the Server tab. On the Server tabbed page that opens, click
the Automated Maintenance Tasks link in the Tasks section.
On the Automated Maintenance Tasks page, you see the predefined tasks. You then access each task
by clicking the corresponding link to get more information about the task itself (illustrated in the
slide). When you click either the Automatic SQL Tuning link or the latest execution icon (the green
area on the timeline), the Automatic SQL Tuning Result Summary page opens.

Oracle Database 11g: SQL Tuning Workshop 11 - 31

Configuring Automatic SQL Tuning


You can configure various Automatic SQL Tuning parameters by using the Automatic SQL Tuning
Settings page.
To navigate to that page, click the Configure button on the Automated Maintenance Tasks page. You
see the Automated Maintenance Tasks Configuration page, on which you see the various
maintenance windows that are delivered with Oracle Database 11g.
By default, Automatic SQL Tuning executes on all predefined maintenance windows in
MAINTENANCE_WINDOW_GROUP. You can disable it for specific days in the week. On this page,
you can also edit each Window to change its characteristics. You can do so by clicking Edit Window
Group.
To navigate to the Automatic SQL Tuning Settings page, click the Configure button on the line
corresponding to Automatic SQL Tuning in the Task Settings section.
On the Automatic SQL Tuning Settings page, you can specify the parameters shown in the slide. By
default, Automatic Implementation of SQL Profiles is not selected.
Note: If you set STATISTICS_LEVEL to BASIC, turn off the AWR snapshots by using
DBMS_WORKLOAD_REPOSITORY, or if AWR retention is less than seven days, you also stop
Automatic SQL Tuning.

Oracle Database 11g: SQL Tuning Workshop 11 - 32

Automatic SQL Tuning: Result Summary


In addition, the Automatic SQL Tuning Result Summary page contains various summary graphs so
that you can control the Automatic SQL Tuning task. An example is given in the slide. The first chart
in the Overall Task Statistics section shows you the breakdown by finding types for the designated
period of time. You can control the period of time for which you want the report to be generated by
selecting a value from the Time Period list. In the example, Customized is used; it shows you the
latest run. You can select All to cover all executions of the task so far. Users can request it for any
time period over the past month, since that is the amount of time for which the advisor persists its
tuning history. You then generate the report by clicking View Report.
On the Breakdown by Finding Type chart, you can clearly see that only SQL profiles can be
implemented. Although many more profiles were recommended, not all of them were automatically
implemented for the reasons that have already been explained. Similarly, recommendations for index
creation and other types are not implemented. However, the advisor keeps historical information
about all the recommendations if you want to implement them later.
In the Profile Effect Statistics section, you can see the Tuned SQL DB Time Benefit chart, which
shows you the before-and-after DB Time for implemented profiles and other recommendations.

Oracle Database 11g: SQL Tuning Workshop 11 - 33

Automatic SQL Tuning: Result Details


On the Automatic SQL Tuning Result Details page, you can also see important information for each
automatically tuned SQL statement, including its SQL text and SQL ID, the type of recommendation
that was done by SQL Tuning Advisor, the verified benefit percentage, whether a particular
recommendation was automatically implemented, and the date of the recommendation.
From this page, you can either drill down to the SQL statement itself by clicking its corresponding
SQL ID link, or you can select one of the SQL statements and click the View Recommendations
button to have more details about the recommendation for that statement.
Note: The benefit percentage shown for each recommendation is calculated using the formula
bnf% = (time_old - time_new)/(time_old). With this formula, you can see that a three-time benefit
(for example, time_old = 100, time_new = 33) corresponds to 66%. So the system implements any
profiles with benefits over 66%. According to this formula, 98% is a 50-times benefit.

Oracle Database 11g: SQL Tuning Workshop 11 - 34

Automatic SQL Tuning Result Details: Drilldown


On the Recommendations for SQL ID page, you can see the corresponding recommendations and
implement them manually.
By clicking the SQL Test link, you access the SQL Details page, where you see the tuning history as
well as the plan control associated with your SQL statement.
In the slide, you see that the statement was tuned by Automatic SQL Tuning and that the associated
profile was automatically implemented.

Oracle Database 11g: SQL Tuning Workshop 11 - 35

Automatic SQL Tuning Considerations


Automatic SQL Tuning does not seek to solve every SQL performance issue occurring on a system.
It does not consider the following types of SQL.
Ad hoc or rarely repeated SQL: If a SQL is not executed multiple times in the same form, the
advisor ignores it. SQL that do not repeat within a week are also not considered.
Parallel queries.
Long-running queries (postprofile): If a query takes too long to run after being SQL profiled, it
is not practical to test-execute and, therefore, it is ignored by the advisor. Note that this does not
mean that the advisor ignores all long-running queries. If the advisor can find a SQL profile that
causes a query that once took hours to run in minutes, it could be accepted because testexecution is still possible. The advisor would execute the old plan just long enough to determine
that it is worse than the new one, and then would terminate test-execution without waiting for
the old plan to finish, thereby switching the order of execution.
Recursive SQL statements
DMLs, such as INSERT SELECT or CREATE TABLE AS SELECT
With the exception of truly ad hoc SQL, these limitations apply to Automatic SQL Tuning only. Such
statements can still be tuned by manually running SQL Tuning Advisor.

Oracle Database 11g: SQL Tuning Workshop 11 - 36

Oracle Database 11g: SQL Tuning Workshop 11 - 37

Oracle Database 11g: SQL Tuning Workshop 11 - 38

Objectives

After completing this lesson, you should be able to:


Implement the new partitioning methods
Employ data compression

Oracle Database 11g: SQL Tuning Workshop 2

Oracle Partitioning
Core functionality

Performance

Manageability

Range partitioning
Global range indexes

Static partition
pruning

Basic maintenance
operations: add,
drop, exchange

Oracle8i

Hash and composite


range-hash partitioning

Partitionwise joins
Dynamic pruning

Merge operation

Oracle9i

List partitioning

Oracle9i R2

Composite range-list
partitioning

Oracle10g

Global hash indexes

Oracle10g R2

1M partitions per table

Oracle
Database 11g

More composite choices


REF Partitioning
Virtual Column Partitioning

Oracle8

Global index
maintenance
Fast partition split
Local Index
maintenance
Multidimensional
pruning

Fast drop table


Interval Partitioning
Partition Advisor

The table above summarizes the 10 years of partitioning development at Oracle.


Note: REF partitioning enables pruning and partitionwise joins against child tables. Though
performance seems to be the most visible improvement, do not forget about the rest.
Partitioning must address all business-relevant areas of performance, manageability, and
availability.

Oracle Database 11g: SQL Tuning Workshop 2

11g Partitioning Enhancements

Interval partitioning
System partitioning
Composite partitioning enhancement s
Virtual column-based partitioning
Reference partitioning

Data Pump Enhancement:


Single Partition Transportable Tablespace

Partitioning allows the DBA to employ a divide and conquer methodology for managing
database tables, especially as those tables grow. Partitioned tables allow a database to scale for
very large datasets while maintaining consistent performance, without unduly impacting
administrative or hardware resources.
Partitioning enables faster data access within an Oracle database. Whether a database has 10 GB
or 10 TB of data, partitioning can speed up data access by orders of magnitude.
With the introduction of Oracle Database 11g, the DBA will find a useful assortment of
partitioning enhancements. These enhancements include:
Addition of interval partitioning
Addition of system partitioning
Composite partitioning enhancements
Addition of virtual column-based partitioning
Addition of reference partitioning
Data Pump Enhancement: You can now export one or more partitions of a table without having to
move the entire table. On import, you can choose to load partitions as is, merge them into a single
table, or promote each into a separate table. Please refer to the Oracle Database Utilities 11g
Release 1 (11.1) guide for more information on Data Pump.

Oracle Database 11g: SQL Tuning Workshop 2

Interval Partitioning

Interval partitioning is an extension of range


partitioning.
Partitions of a specified interval are created when
inserted data exceeds all of the range partitions.
At least one range partition must be created.
Interval partitioning automates the creation of range
partitions.

Before the introduction of interval partitioning, the DBA was required to explicitly define the
range of values for each partition. The problem is explicitly defining the bounds for each partition
does not scale as the number of partitions grow.
Interval partitioning is an extension of range partitioning, which instructs the database to
automatically create partitions of a specified interval when data inserted into the table exceeds all
of the range partitions. You must specify at least one range partition. The range partitioning key
value determines the high value of the range partitions, which is called the transition point, and the
database creates interval partitions for data beyond that transition point.
Interval partitioning fully automates the creation of range partitions. Managing the creation of
new partitions can be a cumbersome and highly repetitive task. This is especially true for
predictable additions of partitions covering small ranges, such as adding new daily partitions.
Interval partitioning automates this operation by creating partitions on demand.
When using interval partitioning, consider the following restrictions:
You can specify only one partitioning key column, and it must be of NUMBER or DATE type.
Interval partitioning is not supported for index-organized tables.
You cannot create a domain index on an interval-partitioned table.

Oracle Database 11g: SQL Tuning Workshop 2

Interval Partitioning: Example


CREATE TABLE SH.SALES_INTERVAL
PARTITION BY RANGE (time_id)
INTERVAL (NUMTOYMINTERVAL(1,'month')) STORE IN (tbs1,tbs2,tbs3,tbs4)
(
PARTITION P1 values less than (TO_DATE('1-1-2005','dd-mm-yyyy')),
PARTITION P2 values less than (TO_DATE('1-1-2006','dd-mm-yyyy')),
PARTITION P3 values less than (TO_DATE('1-1-2007','dd-mm-yyyy')))
AS
SELECT *
FROM SH.SALES
WHERE TIME_ID < TO_DATE('1-1-2007','dd-mm-yyyy');
Automatically created
when you insert data

P1

P2

P3

Pi1

Range section

Pin

In terval section
Transition point

Consider the example above, which illustrates the creation of an interval-partitioned table. The
original CREATE TABLE statement specifies four partitions with varying widths.
This portion of the table is range-partitioned. It also specifies that above the transition point of 11-2007, partitions be created with a width of one month. These partitions are intervalpartitioned. Partition Pi1 is automatically created using this information when a row with a value
corresponding to January 2007 is inserted into the table. The high bound of partition P3 represents
a transition point. P3 and all partitions below it (P1 and P2 in this example) are in the range
section, whereas all partitions above it fall into the interval section. The only argument to the
INTERVAL clause is a constant of the interval type. Currently, you can specify only one
partitioning key column, and it must be of DATE or NUMBER type.
You can use the optional STORE IN clause of the INTERVAL clause to specify one or more
tablespaces into which the database will store interval partition data in a round-robin fashion.

Oracle Database 11g: SQL Tuning Workshop 2

Moving the Transition Point: Example


PREVIOUS
< 01/01/07

INSERT INTO orders_interval ();


Transition point

PREVIOUS

Not yet
materialized

< 01/01/07

SYS_Px

SYS_Py

SYS_Pz

SYS_Pt

< 01/08/07

< 01/11/07

< 01/12/07

< 01/03/08

Transition point

alter table orders_interval merge partitions


for(TO_DATE('15-10-2007','dd-mm-yyyy')),for(TO_DATE('15-11-2007','dd-mm-yyyy'))
into partition sys_pz;

PREVIOUS

SYS_Px

SYS_Pz

SYS_Pt

< 01/01/07

< 01/08/07

< 01/12/07

< 01/03/08

Transition point

The graphic in the slide shows you a typical Information Lifecycle Management (ILM) scenario
where after one year of automated partition creation, you merge the created partitions (SYS_Py
and SYS_Pz in the example) to move the transition point. You can then move the resulting
partitions to a different storage for ILM purposes.
The example assumes that you created a table called ORDERS_INTERVAL that has initially one
range partition called PREVIOUS, which holds orders from before 2007. The interval is defined
to be one month. Then during the year 2007 and 2008, some orders are inserted, and it is assumed
that four partitions are created. They are shown in the graphic. They are automatically named
according to certain system rules.
You then decide to merge the last two partitions of the year 2007 using the ALTER TABLE
statement shown in the slide. You must merge two adjacent partitions. The new extended
partition syntax is used to designate a partition without needing to know the partition name.
The syntax uses an expression that must represent a possible value for the partition in question.
This syntax works for all cases when you have to reference a partition, whether it is range, list,
interval, or hash. It supports all operations such as drop, merge, split, and so on.
As a result of your MERGE operation, you can see that the transition point moved. The bottom
part of the graphic shows you the new range section that now contains three partitions.
Note: You can change the interval of an interval-partitioned table; the existing intervals remain
unaffected.

Oracle Database 11g: SQL Tuning Workshop 2

System Partitioning

System partitioning:
Enables application-controlled partitioning for selected
tables
Provides the benefits of partitioning but the partitioning
and data placement are controlled by the application
Does not employ partitioning keys like other
partitioning methods
Does not support partition pruning in the traditional
sense

System partitioning enables application-controlled partitioning for arbitrary tables. This is mainly
useful when you develop your own partitioned domain indexes. The database simply provides the
ability to break down a table into meaningless partitions. All other aspects of partitioning are
controlled by the application. System partitioning provides the well-known benefits of
partitioning (scalability, availability, and manageability), but the partitioning and actual data
placement are controlled by the application.
The most fundamental difference between system partitioning and other methods is that system
partitioning does not have any partitioning keys. Consequently, the distribution or mapping of the
rows to a particular partition is not implicit. Instead, the user specifies the partition to which a
row maps by using partition-extended syntax when inserting a row.
Because system-partitioned tables do not have a partitioning key, the usual performance benefits
of partitioned tables are not available for system-partitioned tables. Specifically, there is no
support for traditional partition pruning, partitionwise joins, and so on. Partition pruning is
achieved by accessing the same partitions in the system-partitioned tables as those that were
accessed in the base table.
System-partitioned tables provide the manageability advantages of equipartitioning. For example,
a nested table can be created as a system-partitioned table that has the same number of partitions
as the base table. A domain index can be backed up by a system-partitioned table that has the same
number of partitions as the base table.

Oracle Database 11g: SQL Tuning Workshop 2

System Partitioning: Example

CREATE TABLE
PARTITION BY
(
PARTITION
PARTITION
PARTITION
PARTITION
);

systab (c1 integer, c2 integer)


SYSTEM
p1
p2
p3
p4

TABLESPACE
TABLESPACE
TABLESPACE
TABLESPACE

tbs_1,
tbs_2,
tbs_3,
tbs_4

INSERT INTO systab PARTITION (p1) VALUES (4,5);


INSERT INTO systab PARTITION (p2) VALUES (150,2);

alter table systab merge partitions p1,p2 into partition p1;

The syntax in the slide example creates a table with four partitions. Each partition can have
different physical attributes. INSERT and MERGE statements must use partition-extended syntax
to identify a particular partition that a row should go into. For example, the value (4,5) can be
inserted into any one of the four partitions given in the example.
Deletes and updates do not require the partition-extended syntax. However, because there is no
partition pruning, if the partition-extended syntax is omitted, the entire table is scanned to execute
the operation. Again, this example highlights the fact that there is no implicit mapping from rows
to any partition.

Oracle Database 11g: SQL Tuning Workshop 2

System Partitioning: Guidelines

The following operations are supported for systempartitioned tables:


Partition maintenance operations and other data
definition language (DDL) operations
Creation of local indexes
Creation of local bitmapped indexes
Creation of global indexes
All DML operations
INSERT. . . SELECT with partition-extended syntax:
INSERT INTO <table_name> PARTITION(<partition-name>) <subquery>

The following operations are supported for system-partitioned tables:


Partition maintenance operations and other DDLs (see exceptions below)
Creation of local indexes
Creation of local bitmapped indexes
Creation of global indexes
All data manipulation language (DML) operations
INSERT. . .SELECT with partition-extended syntax.
Because of the peculiar requirements of system partitioning, the following operations are not
supported for system partitioning:
Unique local indexes are not supported because they require a partitioning key.
CREATE TABLE AS SELECT is not supported because there is no partitioning method. It
is not possible to distribute rows to partitions. Instead, you should first create the table and
then insert rows into each partition.
SPLIT PARTITION operations

Oracle Database 11g: SQL Tuning Workshop 2

Oracle Database 11g: SQL Tuning Workshop 2

Virtual ColumnBased Partitioning

Virtual column values are derived by the evaluation of a


function or expression.
Virtual columns can be defined within a CREATE or
ALTER table operation.
CREATE TABLE employees
(employee_id number(6) not null,

total_compensation as (salary *( 1+commission_pct))

Virtual column values are not physically stored in the


table row on disk, but are evaluated on demand.
Virtual columns can be indexed, and used in queries,
DML, and DDL statements like other table column types.
Tables and indexes can be partitioned on a virtual
column and statistics can be gathered upon them.
Columns of a table whose values are derived by computation of a function or an expression are
known as virtual columns. These columns can be specified during a CREATE or ALTER table
operation. Virtual columns share the same SQL namespace as other real table columns and
conform to the data type of the underlying expression that describes it. These columns can be
used in queries like any other table columns, thereby providing a simple, elegant, and consistent
mechanism of accessing expressions in a SQL statement.
The values for virtual columns are not physically stored in the table row on disk, rather they are
evaluated on demand. The functions or expressions describing the virtual columns should be
deterministic and pure, meaning the same set of input values should return the same output
values.
Virtual columns can be used like any other table columns. They can be indexed, and used in
queries, DML, and DDL statements. Tables and indexes can be partitioned on a virtual column
and statistics can be gathered upon them.
You can use virtual column partitioning to partition key columns defined on virtual columns of a
table. Frequently, business requirements to logically partition objects do not match existing
columns in a one-to-one manner. With the introduction of Oracle Database 11g, partitioning has
been enhanced to allow a partitioning strategy defined on virtual columns, thus enabling a more
comprehensive match of the business requirements.

Oracle Database 11g: SQL Tuning Workshop 2

Virtual ColumnBased Partitioning: Example

CREATE TABLE employees


(employee_id
number(6) not null, first_name varchar2(30),
last_name varchar2(40) not null, emailvarchar2(25),
phone_number
varchar2(20), hire_date date not null,
job_id varchar2(10) not null, salary number(8,2),
commission_pct number(2,2), manager_id number(6),
department_id number(4),
total_compensation as (salary *( 1+commission_pct))
)
PARTITION BY RANGE (total_compensation)
(
PARTITION p1 VALUES LESS THAN (50000),
PARTITION p2 VALUES LESS THAN (100000),
PARTITION p3 VALUES LESS THAN (150000),
PARTITION p4 VALUES LESS THAN (MAXVALUE)
);

Consider the example in the slide. The EMPLOYEES table is created using the standard CREATE
TABLE syntax. The total_compensation column is a virtual column calculated by
multiplying the value of salary by the commission_pct plus one. The next statement
declares total_compensation (a virtual column) to be the partitioning key of the
EMPLOYEES table.
Partition pruning takes place for virtual column partition keys when the predicates on the
partitioning key are of the following types:
Equality or Like
List
Range
Partition-extended names
Given a join operation between two tables, the optimizer recognizes when a partitionwise join
(full or partial) is applicable, decides whether to use it or not, and annotates the join properly
when it decides to use it. This applies to both serial and parallel cases.
To recognize full partitionwise joins, the optimizer relies on the definition of equipartitioning of
two objects; this definition includes the equivalence of the virtual expression on which the tables
were partitioned.

Oracle Database 11g: SQL Tuning Workshop 2

Reference Partitioning

A table can now be partitioned based on the partitioning


method of a table referenced in its referential constraint.
The partitioning key is resolved through an existing
parent/child relationship.
The partitioning key is enforced by active primary key or
foreign key constraints.
Tables with a parent/child relationship can be
equipartitioned by inheriting the partitioning key from
the parent table without duplicating the key columns.
Partitions are automatically maintained.

Reference partitioning provides the ability to partition a table based on the partitioning scheme of
the table referenced in its referential constraint. The partitioning key is resolved through an
existing parent/child relationship, which is enforced by active primary key or foreign key
constraints. The benefit of this is that tables with a parent/child relationship can be logically
equipartitioned by inheriting the partitioning key from the parent table without duplicating the
key columns. The logical dependency also automatically cascades partition maintenance
operations, making application development easier and less error prone.

Oracle Database 11g: SQL Tuning Workshop 2

Reference Partitioning: Benefit


Reference partitioning

Without using reference partitioning

Range (ORDER_DATE)
Primary key (ORDER_ID)

Range (ORDER_DATE)
Foreign key (ORDER_ID)

Table ORDERS

Table
ORDER_ITEMS

Redundant storage/maintenance of ORDER_DATE

Partition key inherited through PK/FK relationship

As illustrated in the slide, you can see the benefit of using reference partitioning. The left part of
the graphic shows you the situation where you have two tables, ORDERS and ORDER_ITEMS,
that are equipartitioned on the ORDER_DATE column. In that case, both tables need to define the
ORDER_DATE column. However, defining ORDER_DATE in the ORDER_ITEMS table is
redundant because there is a primary key/foreign key relationship between the two tables.
The right part of the graphic shows you the situation where you use reference partitioning. This
time, you no longer need to define the ORDER_DATE column in the ORDER_ITEMS table. The
partition key of the ORDER_ITEMS table is automatically inherited from the primary key/foreign
key relationship that exists.
When used for pruning and partitionwise joins, reference partitioning has the benefit that query
predicates can be different and partitionwise joins still workfor example, partitioning on
ORDER_DATE and search on ORDER_ID. With previous releases, both partitioning and
predicates had to be identical for a partitionwise join to work.
Note: This partitioning method can be useful for nested table partitioning.

Oracle Database 11g: SQL Tuning Workshop 2

Reference Partitioning: Example


CREATE TABLE orders
( order_id
NUMBER(12) , order_date
DATE,
order_mode
VARCHAR2(8), customer_id NUMBER(6),
order_status NUMBER(2) , order_total NUMBER(8,2),
sales_rep_id NUMBER(6) , promotion_id NUMBER(6),
CONSTRAINT
orders_pk PRIMARY KEY(order_id)
)
PARTITION BY RANGE(order_date)
(PARTITION Q105 VALUES LESS THAN (TO_DATE('1-4-2005','DD-MM-YYYY')),
PARTITION Q205 VALUES LESS THAN (TO_DATE('1-7-2005','DD-MM-YYYY')),
PARTITION Q305 VALUES LESS THAN (TO_DATE('1-10-2005','DD-MM-YYYY')),
PARTITION Q405 VALUES LESS THAN (TO_DATE('1-1-2006','DD-MM-YYYY')));
CREATE TABLE order_items
( order_id
NUMBER(12) NOT NULL, line_item_id NUMBER(3) NOT NULL,
product_id
NUMBER(6) NOT NULL, unit_price
NUMBER(8,2),
quantity
NUMBER(8),
CONSTRAINT
order_items_fk
FOREIGN KEY(order_id) REFERENCES orders(order_id)
) PARTITION BY REFERENCE(order_items_fk);

The example above creates the following two tables:


ORDERS: Range-partitioned table partitioned on order_date. It is created with four partitions,
Q105, Q205, Q305, and Q405.
ORDER_ITEMS: Reference-partitioned child table:
This table is created with four partitionsQ105, Q205, Q305, and Q405with each
containing rows corresponding to ORDERS in the respective parent partition.
If partition descriptors are provided, the number of partitions described must be exactly equal
to the number of partitions or subpartitions in the referenced table.
If the parent table is a composite-partitioned table, then the table will have one partition for
each subpartition of its parent.
Partition bounds cannot be specified for the partitions of a reference-partitioned table. The
partitions of a reference-partitioned table can be named unless there is a conflict with
inherited names. In this case, the partition will have a system-generated name.
Partitions of a reference-partitioned table will collocate with the corresponding partition of
the parent table, if no explicit tablespace is specified. As with other partitioned tables, you
can specify object-level default attributes, and partition descriptors that override object-level
defaults.
It is not possible to disable the foreign key constraint of a reference-partitioned table.
Oracle Database 11g: SQL Tuning Workshop 2

It is not permitted to add or drop partitions of a reference-partitioned table. However,

performing partition maintenance operations on the parent table is automatically cascaded to


the child table.

Oracle Database 11g: SQL Tuning Workshop 2

Composite Partitioning Enhancements

Range top level


Range-Range

RANGE, LIST, INTERVAL

List top level


List-List
List-Hash
List-Range

SP1

SP1

SP1

SP1

SP1

SP2

SP2

SP2

SP2

SP2

SP3

SP3

SP3

SP3

SP3

SP4

SP4

SP4

SP4

SP4

Interval top level


Interval-Range
Interval-List
Interval-Hash

LIST, RANGE, HASH

Before the release of Oracle Database 11g, the only composite partitioning methods supported
were range-list and range-hash. With this new release, list partitioning can be a top-level
partitioning method for composite partitioned tables giving us list-list, list-hash, list-range, and
range-range composite methods. With the introduction of interval partitioning, interval-range,
interval-list, and interval-hash are now supported composite partitioning methods.
Range-Range Partitioning
Composite range-range partitioning enables logical range partitioning along two dimensions; for
example, range partition by order_date and range subpartition by shipping_date.
List-Range Partitioning
Composite list-range partitioning enables logical range subpartitioning within a given list
partitioning strategy; for example, list partition by country_id and range subpartition by
order_date.
List-Hash Partitioning
Composite list-hash partitioning enables hash subpartitioning of a list-partitioned object; for
example, to enable partitionwise joins.
List-List Partitioning
Composite list-list partitioning enables logical list partitioning along two dimensions; for
example, list partition by country_id and list subpartition by sales_channel.
Oracle Database 11g: SQL Tuning Workshop 2

Range-Range Partitioning: Example


CREATE TABLE sales (
prod_id NUMBER(6) NOT NULL, cust_id NUMBER NOT NULL,
time_id DATE NOT NULL, channel_id char(1) NOT NULL,
promo_id NUMBER (6) NOT NULL,
quantity_sold NUMBER(3) NOT NULL,
amount_sold NUMBER(10,2) NOT NULL )
PARTITION BY RANGE (time_id)
SUBPARTITION BY RANGE (cust_id)
SUBPARTITION TEMPLATE
( SUBPARTITION sp1 VALUES LESS THAN (50000),
SUBPARTITION sp2 VALUES LESS THAN (100000),
SUBPARTITION sp3 VALUES LESS THAN (150000),
SUBPARTITION sp4 VALUES LESS THAN (MAXVALUE) )
(
PARTITION VALUES LESS THAN (TO_DATE('1-4-2007','DD-MM-YYYY')),
PARTITION VALUES LESS THAN (TO_DATE('1-7-2007','DD-MM-YYYY')),
PARTITION VALUES LESS THAN (TO_DATE('1-8-2007','DD-MM-YYYY')),
PARTITION VALUES LESS THAN (TO_DATE('1-1-2008','DD-MM-YYYY'))
);

Composite range-range partitioning enables logical range partitioning along two dimensions. In
the example in the slide, the SALES table is created and range-partitioned on time_id. Using a
subpartition template, the SALES table is subpartitioned by range using cust_id as the
subpartition key.
Because of the template, all partitions have the same number of subpartitions with the same
bounds as defined by the template. If no template is specified, a single default partition bound by
MAXVALUE (range) or DEFAULT value (list) is created.
Although the example illustrates the range-range methodology, the other new composite
partitioning methods use similar syntax and statement structure. All of the composite partitioning
methods fully support the existing partition pruning methods for queries involving predicates on
the subpartitioning key.

Oracle Database 11g: SQL Tuning Workshop 2

Table Compression: Overview

Oracle Database 11g extends compression for OLTP


data.
Support for conventional DML operations
(INSERT, UPDATE, DELETE)

New algorithm significantly reduces write overhead.


Batched compression ensures no impact for most OLTP
transactions.

There is no impact on reads.


Reads may actually see improved performance due to
fewer I/Os and enhanced memory efficiency.

The Oracle database was the pioneer in terms of compression technology for databases with the
introduction of table compression for bulk load operations in Oracle9i. Using this feature, you
could compress data at the time of performing bulk load using operations such as direct loads, or
Create Table As Select (CTAS). However, until now, compression was not available for regular
data manipulation operations such as INSERT, UPDATE, and DELETE. Oracle Database 11g
extends the compression technology to support these operations as well. Consequently,
compression in Oracle Database 11g can be used for all kinds of workload, be it online
transaction processing (OLTP) or data warehousing.
It is important to mention that table compression enhancements introduced in Oracle Database
11g are not just incremental changes. An enormous amount of work has gone into making sure
that the new compression technology has negligible impact on updates because any noticeable
write-time penalty due to compression will not be acceptable in an OLTP environment. As a
result, compression technology in Oracle Database 11g is very efficient and could reduce the space
consumption by 5075%. And while you do that, not only your write performance does not
degrade, but also your read performance or queries improve. This is because unlike desktop- based
compression techniques where you have to wait for data to be uncompressed, Oracle technology
reads the compressed data (less fetches needed) directly and does not require any uncompress
operation.
Note: Compression technology is completely application transparent. This means that you can use
this technology with any homegrown or packaged application such as SAP, Siebel, EBS, and so
on.
Oracle Database 11g: SQL Tuning Workshop 2

Table Compression Concepts

Inserts are again


uncompressed.

Uncompressed
data

Compressed
data

Data block

PCTFREE reached,
compression triggered

PCTFREE reached,
compression triggered

Header
PCTFREE
limit

Free
space

Inserts are
uncompressed.

The slide shows you a data block evolution when that block is part of a compressed table. You
should read it from left to right. At the start, the block is empty and available for inserts.
When you start inserting into this block, data is stored in an uncompressed format (like for
uncompressed tables). However, as soon as you reach the PCTFREE of that block, the data is
automatically compressed, potentially reducing the space it originally occupied. This allows for
new uncompressed inserts to take place in the same block, until PCTFREE is reached again. At
that point compression is triggered again to reduce space occupation in the block.
Note: Compression eliminates holes created due to deletions, and maximizes contiguous free
space in blocks.

Oracle Database 11g: SQL Tuning Workshop 2

Using Table Compression

Requires database compatibility level at 11.1 or greater


New syntax extends the COMPRESS keyword:
COMPRESS [FOR {ALL | DIRECT_LOAD} OPERATIONS]
FOR DIRECT_LOAD is the default: Refers to bulk load
operations from prior releases
FOR ALL OPERATIONS: OLTP + direct loads

Enable compression for new tables:


CREATE TABLE t1 COMPRESS FOR ALL OPERATIONS;

Enable compression on existing table:


ALTER TABLE t2 COMPRESS FOR ALL OPERATIONS;

Does not trigger compression on existing rows

To use the new compression algorithm, you must flag your table with the COMPRESS FOR ALL
OPERATIONS clause. You can do so at table creation, or after creation. This is illustrated in the
examples given above.
If you use the COMPRESS clause without specifying any FOR option, or if you use the
COMPRESS FOR DIRECT_LOAD OPERATIONS clause, you fall back to the old compression
mechanism that was available in earlier releases.
You can also enable compression at the partition or tablespace level. For example, you can use
the DEFAULT storage clause of the CREATE TABLESPACE command to optionally specify a
COMPRESS FOR clause.
Note: You can view compression flags for your tables using the COMPRESS and
COMPRESS_FOR columns in views such as DBA_TABLES and DBA_TAB_PARTITIONS.

Oracle Database 11g: SQL Tuning Workshop 2

Summary

In this lesson, you should have learned how to:


Implement the new partitioning methods
Employ data compression

Oracle Database 11g: SQL Tuning Workshop 2

Appendix A
Practices and Solutions

Table of Contents
Practices for Lesson 1 ......................................................................................................... 3
Practice 1-1: Exploring the Database Architecture......................................................... 4
Practices for Lesson 2 ....................................................................................................... 12
Practice 2-1: Avoiding Common Mistakes................................................................... 13
Practices for Lesson 3 ....................................................................................................... 32
Practice 3-1: Understanding Optimizer Decisions........................................................ 33
Practices for Lesson 4 ....................................................................................................... 57
Practice 4-1: Using Different Access Paths .................................................................. 58
Practice 4-2: Using the Result Cache.......................................................................... 121
Practices for Lesson 5 ..................................................................................................... 144
Practice 5-1: Extracting Execution Plans.................................................................... 145
Practices for Lesson 6 ..................................................................................................... 179
Practice 6-1: Star Schema Tuning............................................................................... 180
Practices for Lesson 7 ..................................................................................................... 196
Practice 7-1: Using System Statistics ......................................................................... 197
Practice 7-2: Automatic Statistics Gathering.............................................................. 212
Practices for Lesson 8 ..................................................................................................... 240
Practice 8-1: Understanding Adaptive Cusrsor Sharing ............................................. 241
Practice 8-2: Understanding CURSOR_SHARING................................................... 260
Practices for Lesson 9 ..................................................................................................... 273
Practice 9-1: Using Hints ............................................................................................ 274
Practices for Lesson 10 ................................................................................................... 318
Practice 10-1: Tracing Applications ........................................................................... 319
Practices for Lesson 11 ................................................................................................... 363
Practice 11-1: Proactively Tuning High-Load SQL Statements................................. 364
Practice 11-2: Using SQL Access Advisor................................................................. 385
Practice 11-3: Using Automatic SQL Tuning............................................................. 415

Oracle Database 11g: SQL Tuning Workshop A - 2

Practices for Lesson 1

Oracle Database 11g: SQL Tuning Workshop A - 3

Practice 1-1: Exploring the Database Architecture


Fill in the blanks with the correct answers:
1) The two main components of Oracle RDBMS are
_________________________ and _______________________.
2) An instance consists of _____________________and _____________________
processes.
3) A session is a connection between the _______________ process and either the
______________ process or the ________________ process.
4) Name some components of the System Global Area (SGA).

___________________________

___________________________

___________________________

___________________________

___________________________

___________________________

____________________________

5) List some background processes:

___________________________

___________________________

___________________________

___________________________

___________________________

___________________________

6) The _______________________ process writes the dirty buffers to the data files.
7) The _______________________ process writes the redo logs to the log files.
8) Name some files associated with an Oracle database.

Oracle Database 11g: SQL Tuning Workshop A - 4

Practice 1-1: Exploring the Database Architecture (continued)

___________________________

___________________________

___________________________

___________________________

___________________________

___________________________

____________________________

9) Some of the logical storage structures of an Oracle database are:

___________________________

___________________________

___________________________

___________________________

___________________________

___________________________

____________________________

10) The ___________________ process copies the redo log files to an archive
destination.
11) The _____________________ contains data and control information for a server or a
background process.
12) The logical tablespace structure is associated with the physical
____________________ files on disk.
13) State whether the following statements are true or false.
a) The SGA includes the Database buffer cache and the Redo log buffer. ____
b) Each server process and background process has its own Program Global Area
(PGA). ____
c) User processes run the application or Oracle tool code. ____
d) Oracle Database processes include server processes and background processes.
____.

Oracle Database 11g: SQL Tuning Workshop A - 5

Practice 1-1: Exploring the Database Architecture (continued)


14) From a terminal session connected as the oracle user, execute the
processes.sh script located in your
$HOME/solutions/Database_Architecture directory. What does this
script show you?
a) It shows you all the database instance processes currently running on your
machine. This includes both background processes and foreground processes.
[oracle@edrsr33p1-orcl Database_Architecture]$ ./processes.sh
oracle
9537
1 0 01:00 ?
00:00:00 ora_w000_orcl
oracle
12132 22002 0 Mar26 pts/4
00:01:08
/u01/app/oracle/product/11.1.0/db_1/jdk/bin/java -server -Xmx256M XX:MaxPermSize=200m -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40
-DORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1 Doracle.home=/u01/app/oracle/product/11.1.0/db_1/oc4j Doracle.oc4j.localhome=/u01/app/oracle/product/11.1.0/db_1/edrsr33p1
.us.oracle.com_orcl/sysman DEMSTATE=/u01/app/oracle/product/11.1.0/db_1/edrsr33p1.us.oracle.com
_orcl -Doracle.j2ee.dont.use.memory.archive=true Djava.protocol.handler.pkgs=HTTPClient Doracle.security.jazn.config=/u01/app/oracle/product/11.1.0/db_1/oc4
j/j2ee/OC4J_DBConsole_edrsr33p1.us.oracle.com_orcl/config/jazn.xml Djava.security.policy=/u01/app/oracle/product/11.1.0/db_1/oc4j/j2ee/
OC4J_DBConsole_edrsr33p1.us.oracle.com_orcl/config/java2.policy Djavax.net.ssl.KeyStore=/u01/app/oracle/product/11.1.0/db_1/sysman/c
onfig/OCMTrustedCerts.txtDjava.security.properties=/u01/app/oracle/product/11.1.0/db_1/oc4j/j
2ee/home/config/jazn.security.props DEMDROOT=/u01/app/oracle/product/11.1.0/db_1/edrsr33p1.us.oracle.com
_orcl -Dsysman.md5password=true Drepapi.oracle.home=/u01/app/oracle/product/11.1.0/db_1 Ddisable.checkForUpdate=true Doracle.sysman.ccr.ocmSDK.websvc.keystore=/u01/app/oracle/product/11
.1.0/db_1/jlib/emocmclnt.ks Dice.pilots.html4.ignoreNonGenericFonts=true Djava.awt.headless=true -jar
/u01/app/oracle/product/11.1.0/db_1/oc4j/j2ee/home/oc4j.jar -config
/u01/app/oracle/product/11.1.0/db_1/oc4j/j2ee/OC4J_DBConsole_edrsr33
p1.us.oracle.com_orcl/config/server.xml
oracle
12225
1 0 Mar26 ?
00:00:00 ora_pmon_orcl
oracle
12227
1 0 Mar26 ?
00:00:00 ora_vktm_orcl
oracle
12231
1 0 Mar26 ?
00:00:00 ora_diag_orcl
oracle
12233
1 0 Mar26 ?
00:00:00 ora_dbrm_orcl
oracle
12235
1 0 Mar26 ?
00:00:00 ora_psp0_orcl
oracle
12239
1 0 Mar26 ?
00:00:00 ora_dia0_orcl
oracle
12241
1 0 Mar26 ?
00:00:00 ora_mman_orcl
oracle
12243
1 0 Mar26 ?
00:00:21 ora_dbw0_orcl
oracle
12245
1 0 Mar26 ?
00:00:48 ora_lgwr_orcl
oracle
12247
1 0 Mar26 ?
00:00:01 ora_ckpt_orcl
oracle
12249
1 0 Mar26 ?
00:00:04 ora_smon_orcl
oracle
12251
1 0 Mar26 ?
00:00:00 ora_reco_orcl
oracle
12253
1 0 Mar26 ?
00:00:05 ora_mmon_orcl
oracle
12255
1 0 Mar26 ?
00:00:01 ora_mmnl_orcl
oracle
12257
1 0 Mar26 ?
00:00:00 ora_d000_orcl
oracle
12259
1 0 Mar26 ?
00:00:00 ora_s000_orcl
oracle
12282
1 0 Mar26 ?
00:00:00 ora_fbda_orcl

Oracle Database 11g: SQL Tuning Workshop A - 6

Practice 1-1: Exploring the Database Architecture (continued)


oracle
12284
1 0 Mar26 ?
00:00:00 ora_smco_orcl
oracle
12290
1 0 Mar26 ?
00:00:00 ora_qmnc_orcl
oracle
12302
1 0 Mar26 ?
00:00:03 oracleorcl
(LOCAL=NO)
oracle
12325
1 0 Mar26 ?
00:00:00 ora_q000_orcl
oracle
12329
1 0 Mar26 ?
00:01:59 oracleorcl
(LOCAL=NO)
oracle
12331
1 0 Mar26 ?
00:00:12 oracleorcl
(LOCAL=NO)
oracle
12333
1 0 Mar26 ?
00:00:05 oracleorcl
(LOCAL=NO)
oracle
12335
1 0 Mar26 ?
00:00:37 oracleorcl
(LOCAL=NO)
oracle
12340
1 0 Mar26 ?
00:01:51 oracleorcl
(LOCAL=NO)
oracle
12346
1 0 Mar26 ?
00:00:01 oracleorcl
(LOCAL=NO)
oracle
12362
1 0 Mar26 ?
00:00:00 ora_q001_orcl
oracle
12570
1 0 Mar26 ?
00:00:01 ora_cjq0_orcl
oracle
20119
1 0 14:50 ?
00:00:00 oracleorcl
(LOCAL=NO)
oracle
20482 20480 0 14:56 pts/2
00:00:00 grep orcl
oracle
22002
1 0 Mar26 pts/4
00:00:08
/u01/app/oracle/product/11.1.0/db_1/perl/bin/perl
/u01/app/oracle/product/11.1.0/db_1/bin/emwd.pl dbconsole
/u01/app/oracle/product/11.1.0/db_1/edrsr33p1.us.oracle.com_orcl/sys
man/log/emdb.nohup
[oracle@edrsr33p1-orcl Database_Architecture]$
-------------------------------------------------------------#!/bin/bash
ps -ef | grep orcl

15) From a terminal session connected as the oracle user, execute the files.sh
script located in your $HOME/solutions/Database_Architecture
directory. What does this script show you?
a) This script shows you the location and names of all database files, the
initialization file, the password file, and trace files.
[oracle@edrsr33p1-orcl Database_Architecture]$ ./files.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Thu Mar 27 17:58:56
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

Oracle Database 11g: SQL Tuning Workshop A - 7

Practice 1-1: Exploring the Database Architecture (continued)


SQL>
SQL> col name format a45
SQL>
SQL> select name from v$controlfile;
NAME
--------------------------------------------/u01/app/oracle/oradata/orcl/control01.ctl
/u01/app/oracle/oradata/orcl/control02.ctl
/u01/app/oracle/oradata/orcl/control03.ctl
SQL>
SQL>
SQL> col member format a45
SQL>
SQL> select group#,member from v$logfile;
GROUP#
---------3
2
1

MEMBER
--------------------------------------------/u01/app/oracle/oradata/orcl/redo03.log
/u01/app/oracle/oradata/orcl/redo02.log
/u01/app/oracle/oradata/orcl/redo01.log

SQL>
SQL>
SQL> col tablespace_name format a20
SQL> col file_name format a45
SQL>
SQL> select tablespace_name, file_name from dba_data_files;
TABLESPACE_NAME
-------------------USERS
UNDOTBS1
SYSAUX
SYSTEM
EXAMPLE

FILE_NAME
--------------------------------------------/u01/app/oracle/oradata/orcl/users01.dbf
/u01/app/oracle/oradata/orcl/undotbs01.dbf
/u01/app/oracle/oradata/orcl/sysaux01.dbf
/u01/app/oracle/oradata/orcl/system01.dbf
/u01/app/oracle/oradata/orcl/example01.dbf

SQL>
SQL> select tablespace_name, file_name from dba_temp_files;
TABLESPACE_NAME
FILE_NAME
-------------------- --------------------------------------------TEMP
/u01/app/oracle/oradata/orcl/temp01.dbf
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
-rw-rw---- 1 oracle oinstall 1544 Aug 22 2007
/u01/app/oracle/product/11.1.0/db_1/dbs/hc_orcl.dat
-rw-r----- 1 oracle oinstall 1536 Mar 26 22:03
/u01/app/oracle/product/11.1.0/db_1/dbs/orapworcl
-rw-r----- 1 oracle oinstall 2560 Mar 27 03:13
/u01/app/oracle/product/11.1.0/db_1/dbs/spfileorcl.ora

Oracle Database 11g: SQL Tuning Workshop A - 8

Practice 1-1: Exploring the Database Architecture (continued)


alert cdump hm incident incpkg ir lck metadata stage
trace
-rw-r--r-- 1 oracle oinstall 557386 Mar 27 13:00
/u01/app/oracle/diag/rdbms/orcl/orcl/trace/alert_orcl.log
[oracle@edrsr33p1-orcl Database_Architecture]$

sweep

-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Database_Architecture
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

sqlplus / as sysdba @files.sql


ls -l $ORACLE_HOME/dbs/*orcl*
ls /u01/app/oracle/diag/rdbms/orcl/orcl
ls -l /u01/app/oracle/diag/rdbms/orcl/orcl/trace/alert*
-------------------------------------------------------------set echo on
col name format a45
select name from v$controlfile;

col member format a45


select group#,member from v$logfile;

col tablespace_name format a20


col file_name format a45
select tablespace_name, file_name from dba_data_files;
select tablespace_name, file_name from dba_temp_files;
exit;

16) From a terminal session connected as the oracle user, execute the sga.sh script
located in your $HOME/solutions/Database_Architecture directory.
What does this script show you?

Oracle Database 11g: SQL Tuning Workshop A - 9

Practice 1-1: Exploring the Database Architecture (continued)


a) This script prints the various pools held in your SGA.
[oracle@edrsr33p1-orcl Database_Architecture]$ ./sga.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Thu Mar 27 18:15:02
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> select * from v$sgainfo;
NAME
BYTES RES
-------------------------------- ---------- --Fixed SGA Size
1303188 No
Redo Buffers
5181440 No
Buffer Cache Size
335544320 Yes
Shared Pool Size
276824064 Yes
Large Pool Size
4194304 Yes
Java Pool Size
12582912 Yes
Streams Pool Size
0 Yes
Shared IO Pool Size
0 Yes
Granule Size
4194304 No
Maximum SGA Size
845348864 No
Startup overhead in Shared Pool
46137344 No
NAME
BYTES RES
-------------------------------- ---------- --Free SGA Memory Available
209715200
12 rows selected.
SQL>
SQL> col component format a30
SQL>
SQL> select component,current_size,min_size,max_size from
v$sga_dynamic_components;
COMPONENT
CURRENT_SIZE
MIN_SIZE
MAX_SIZE
------------------------------ ------------ ---------- ---------shared pool
276824064 226492416 276824064
large pool
4194304
4194304
4194304
java pool
12582912
12582912
12582912
streams pool
0
0
0
DEFAULT buffer cache
335544320 335544320 385875968
KEEP buffer cache
0
0
0
RECYCLE buffer cache
0
0
0
DEFAULT 2K buffer cache
0
0
0
DEFAULT 4K buffer cache
0
0
0
DEFAULT 8K buffer cache
0
0
0

Oracle Database 11g: SQL Tuning Workshop A - 10

Practice 1-1: Exploring the Database Architecture (continued)


DEFAULT 16K buffer cache

COMPONENT
CURRENT_SIZE
MIN_SIZE
MAX_SIZE
------------------------------ ------------ ---------- ---------DEFAULT 32K buffer cache
0
0
0
Shared IO Pool
0
0
0
ASM Buffer Cache
0
0
0
14 rows selected.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Database_Architecture]$

-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Database_Architecture
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

sqlplus / as sysdba @sga.sql


-------------------------------------------------------------set echo on
select * from v$sgainfo;
col component format a30
select component,current_size,min_size,max_size from
v$sga_dynamic_components;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 11

Practices for Lesson 2

Oracle Database 11g: SQL Tuning Workshop A - 12

Practice 2-1: Avoiding Common Mistakes


In this practice, you examine some common mistakes in writing SQL statements. You
have to find a workaround to enhance performance.
1) You analyze a correlated subquery first. Before executing the culprit statement,
execute the correlation_setup.sh script to set up the environment for this
example. Make sure you run the script from a terminal session connected as the
oracle user. You can find the scripts for all the following cases in your
$HOME/solutions/Common_Mistakes directory.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./correlation_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 20:21:50
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL>
User altered.
SQL> SQL>
Grant succeeded.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba <<EOF
alter user sh identified by sh account unlock;
grant dba to sh;
EOF

Oracle Database 11g: SQL Tuning Workshop A - 13

Practice 2-1: Avoiding Common Mistakes (continued)


2) Connected as the SH user from a SQL*Plus session (stay connected to that session
until the end of this case), make sure you execute the following command:
set timing on
@flush
The goal of the first command is to tell you how long the next command takes to
execute. The flush.sql script flushes both the shared pool and the buffer cache to
avoid most caching effects so that you can have good comparisons between two
executions. Note: You should not use commands found in this script on a production
system.
[oracle@edrsr33p1-orcl Common_Mistakes]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 20:37:49
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> set timing on
SQL> @flush
System altered.
Elapsed: 00:00:00.21
System altered.
Elapsed: 00:00:00.49
SQL>
-------------------------------------------------------------alter system flush shared_pool;
alter system flush buffer_cache;

3) From the same SQL*Plus session, execute the following statement and note the time
it takes to execute (You can use the correlation.sql script.):
SELECT COUNT(*)
FROM
products p
WHERE prod_list_price < 1.15 * (SELECT avg(unit_cost)
FROM costs c
WHERE c.prod_id = p.prod_id);
SQL>
SQL>
SQL>
2
3

@correlation
SELECT COUNT(*)
FROM
products p
WHERE prod_list_price < 1.15 * (SELECT avg(unit_cost)

Oracle Database 11g: SQL Tuning Workshop A - 14

Practice 2-1: Avoiding Common Mistakes (continued)


4
5

FROM costs c
WHERE c.prod_id = p.prod_id);

COUNT(*)
---------46
Elapsed: 00:00:01.21
SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT COUNT(*)
FROM
products p
WHERE prod_list_price < 1.15 * (SELECT avg(unit_cost)
FROM costs c
WHERE c.prod_id = p.prod_id);

4) Before trying to fix the previous statement, flush your environment again using the
flush.sql script from the SQL*Plus session.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.17
SQL>

5) How do you rewrite this statement to enhance performance? Test your solution. You
should discuss this with your instructor.
SQL> @nocorrelation
SQL>
SQL> set timing on
SQL> set echo on
SQL>
SQL> SELECT COUNT(*)
2 FROM
products p, (SELECT prod_id, AVG(unit_cost) ac FROM
costs GROUP BY prod_id) c
3 WHERE p.prod_id = c.prod_id AND
4
p.prod_list_price < 1.15 * c.ac;
COUNT(*)
---------46
Elapsed: 00:00:00.16

Oracle Database 11g: SQL Tuning Workshop A - 15

Practice 2-1: Avoiding Common Mistakes (continued)


SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT COUNT(*)
FROM
products p, (SELECT prod_id, AVG(unit_cost) ac FROM costs
GROUP BY prod_id) c
WHERE p.prod_id = c.prod_id AND
p.prod_list_price < 1.15 * c.ac;

6) Exit from your SQL*Plus session.


SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$

7) From your terminal session, execute the correlation_cleanup.sh script to


clean up your environment.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./correlation_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 20:49:22
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL>
Revoke succeeded.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl

Oracle Database 11g: SQL Tuning Workshop A - 16

Practice 2-1: Avoiding Common Mistakes (continued)


export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba <<EOF
revoke dba from sh;
EOF

8) Before continuing, execute the setup_rest.sh script to set up the environment


for all the examples that follow. Make sure you run the script from a terminal session
connected as the oracle user. You can find the scripts for all the following cases in
your $HOME/solutions/Common_Mistakes directory.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./setup_rest.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 20:59:53
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> drop user jfv cascade
*
ERROR at line 1:
ORA-01918: user 'JFV' does not exist

SQL> SQL>
User created.
SQL> SQL>
Grant succeeded.
SQL> SQL> Connected.
SQL> SQL> drop table orders purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> SQL>
Table created.
SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 17

Practice 2-1: Avoiding Common Mistakes (continued)


SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.
SQL> SQL> drop table employees purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> drop table job_history purge


*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> SQL>
Table created.
SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.
SQL> SQL>
Table created.
SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.
SQL> SQL>
Index created.
SQL> SQL> SQL> drop table old purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> drop table new purge


*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> SQL>
Table created.
SQL>
Table created.
SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.
SQL> SQL>
2
3
4
5
6
7
PL/SQL procedure successfully completed.
SQL> SQL> SQL> Disconnected from Oracle Database 11g Enterprise
Edition Release 11.1.0.6.0 - Production

Oracle Database 11g: SQL Tuning Workshop A - 18

Practice 2-1: Avoiding Common Mistakes (continued)


With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba <<EOF
set echo on
drop user jfv cascade;
create user jfv identified by jfv default tablespace users temporary
tablespace temp;
grant connect, resource, dba to jfv;
connect jfv/jfv
drop table orders purge;
create table orders (order_id_char varchar2(50) primary key,
order_total number, customer_name varchar2(300));
begin
for i in 1..500000 loop
insert into orders
values(i,100,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
begin
for i in 1..500000 loop
insert into orders
values(500000+i,100,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;

Oracle Database 11g: SQL Tuning Workshop A - 19

Practice 2-1: Avoiding Common Mistakes (continued)


end;
/
drop table employees purge;
drop table job_history purge;
create table employees (employee_id number primary key, name
varchar2(500));
begin
for i in 1..500000 loop
insert into employees
values(i,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
create table job_history (employee_id number, job varchar2(500));
begin
for i in 1..500000 loop
insert into job_history
values(mod(i,1000),'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
create index job_history_empid_indx on job_history(employee_id);

drop table old purge;


drop table new purge;
create table old(name varchar2(10), other varchar2(500));
create table new(name varchar2(10), other varchar2(500));
begin
for i in 1..500000 loop
insert into old
values(i,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
begin

Oracle Database 11g: SQL Tuning Workshop A - 20

Practice 2-1: Avoiding Common Mistakes (continued)


for i in 1..500000 loop
insert into new
values(500000+i,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/

EOF

9) Connect as the JFV user from a SQL*Plus session and stay connected in that session
until further notice. In the session, set SQL*Plus timings on and flush your
environment again before starting the second case. You can use the set timing
on command and the flush.sql script for this.
[oracle@edrsr33p1-orcl Common_Mistakes]$ sqlplus jfv/jfv
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 21:06:33
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> set timing on
SQL> @flush
System altered.
Elapsed: 00:00:00.33
System altered.
Elapsed: 00:00:07.12
SQL>
-------------------------------------------------------------alter system flush shared_pool;
alter system flush buffer_cache;

Oracle Database 11g: SQL Tuning Workshop A - 21

Practice 2-1: Avoiding Common Mistakes (continued)


10) The second case you analyze is a join case. From your SQL*Plus session, execute the
following query and note the time it takes to complete:
SELECT count(*)
FROM
job_history jh, employees e
WHERE
substr(to_char(e.employee_id),1)=substr(to_char(jh.employee_id),1);
SQL> @join
SQL>
SQL> SELECT count(*)
2 FROM
job_history jh, employees e
3 WHERE substr(to_char(e.employee_id),1) =
substr(to_char(jh.employee_id),1);
COUNT(*)
---------499500
Elapsed: 00:00:03.03
SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT count(*)
FROM
job_history jh, employees e
WHERE substr(to_char(e.employee_id),1) =
substr(to_char(jh.employee_id),1);

11) Before trying to fix the previous statement, flush your environment again using the
flush.sql script from your SQL*Plus session.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.12
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.72
SQL>
-------------------------------------------------------------alter system flush shared_pool;
alter system flush buffer_cache;

12) How would you rewrite the previous query for better performance? Test your
solution. You should discuss this with your instructor.

Oracle Database 11g: SQL Tuning Workshop A - 22

Practice 2-1: Avoiding Common Mistakes (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@better_join
set timing on
set echo on
SELECT count(*)
FROM
job_history jh, employees e
WHERE e.employee_id = jh.employee_id;

COUNT(*)
---------499500
Elapsed: 00:00:00.70
SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT count(*)
FROM
job_history jh, employees e
WHERE e.employee_id = jh.employee_id;

13) Before analyzing the third case, flush your environment again using the flush.sql
script from your SQL*Plus session.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.11
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.23
SQL>
-------------------------------------------------------------alter system flush shared_pool;
alter system flush buffer_cache;

14) The third case you analyze is a simple predicate case. Still connected as the JFV user
from your SQL*Plus session, execute the following query and note the time it takes
to complete:
SELECT * FROM orders WHERE order_id_char = 1205;
SQL> @simple_predicate
SQL>
SQL> set timing on
SQL> set echo on

Oracle Database 11g: SQL Tuning Workshop A - 23

Practice 2-1: Avoiding Common Mistakes (continued)


SQL>
SQL> SELECT * FROM orders WHERE order_id_char = 1205;
ORDER_ID_CHAR
ORDER_TOTAL
-------------------------------------------------- ----------CUSTOMER_NAME
------------------------------------------------------------------------------1205
100
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Elapsed: 00:00:07.62
SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT * FROM orders WHERE order_id_char = 1205;

15) Before trying to fix the SELECT statement in step 14, flush your environment again
using the flush.sql script.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.13
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.20
SQL>

16) How would you rewrite the previous statement for better performance? Test your
solution. You should discuss this with your instructor.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@better_predicate
set timing on
set echo on
SELECT * FROM orders WHERE order_id_char = '1205';

ORDER_ID_CHAR
ORDER_TOTAL
-------------------------------------------------- ----------CUSTOMER_NAME

Oracle Database 11g: SQL Tuning Workshop A - 24

Practice 2-1: Avoiding Common Mistakes (continued)


------------------------------------------------------------------------------1205
100
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Elapsed: 00:00:00.04
SQL>
-------------------------------------------------------------set timing on
set echo on
SELECT * FROM orders WHERE order_id_char = '1205';

17) Before proceeding with the next analysis, flush your environment again using the
flush.sql script.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.11
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.11
SQL>
-------------------------------------------------------------alter system flush shared_pool;
alter system flush buffer_cache;

18) The fourth case is a UNION case. Execute the following query and note the time it
takes to complete:
select count(*)
from (select name from old union select name from new);
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2

@union
set timing on
set echo on
select count(*)
from (select name from old union select name from new);

COUNT(*)

Oracle Database 11g: SQL Tuning Workshop A - 25

Practice 2-1: Avoiding Common Mistakes (continued)


---------1000000
Elapsed: 00:00:06.39
SQL>

19) Before investigating a better solution, flush your environment again using the
flush.sql script.
SQL> @flush
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.09
SQL>

20) How would you rewrite the previous statement for better performance? Test your
solution. You should discuss this with your instructor.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2

@better_union
set timing on
set echo on
select count(*)
from (select name from old union all select name from new);

COUNT(*)
---------1000000
Elapsed: 00:00:00.42
SQL>

21) Execute the multiple_setup.sql script to set up the environment for this case.
SQL> @multiple_setup
SQL> create table myemp as select * from hr.employees;
Table created.
SQL> insert into myemp select * from myemp;
/
/
/
/
/
/
/
/
/
/

Oracle Database 11g: SQL Tuning Workshop A - 26

Practice 2-1: Avoiding Common Mistakes (continued)


/
/
/
/
/
commit;
107 rows created.
SQL>
214 rows created.
SQL>
428 rows created.
SQL>
856 rows created.
SQL>
1712 rows created.
SQL>
3424 rows created.
SQL>
6848 rows created.
SQL>
13696 rows created.
SQL>
27392 rows created.
SQL>
54784 rows created.
SQL>
109568 rows created.
SQL>
219136 rows created.
SQL>
438272 rows created.
SQL>
876544 rows created.
SQL>
1753088 rows created.
SQL>
3506176 rows created.
SQL>
Commit complete.

Oracle Database 11g: SQL Tuning Workshop A - 27

Practice 2-1: Avoiding Common Mistakes (continued)


SQL> insert into myemp select * from myemp;
commit;
7012352 rows created.
SQL>
Commit complete.
SQL>

22) Execute the multiple1.sql script and note the total time it takes to execute.
SQL> @multiple1
SQL> set timing on
SQL>
SQL>
SQL> SELECT COUNT (*)
FROM myemp
WHERE salary < 2000;
2
3

COUNT(*)
---------0
Elapsed: 00:00:15.49
SQL> SELECT COUNT (*)
FROM myemp
WHERE salary BETWEEN 2000 AND 4000;
2
3
COUNT(*)
---------5636096
Elapsed: 00:00:17.14
SQL> SELECT COUNT (*)
FROM myemp
WHERE salary>4000;
2
3
COUNT(*)
---------8388608
Elapsed: 00:00:18.12
SQL>

23) How would you rewrite the statements found in multiple1.sql script for better
performance?
SQL> @multiple2
SQL> SELECT COUNT (CASE WHEN salary < 2000
2
THEN 1 ELSE null END) count1,

Oracle Database 11g: SQL Tuning Workshop A - 28

Practice 2-1: Avoiding Common Mistakes (continued)


3
4
5
6
7

COUNT (CASE WHEN


THEN
COUNT (CASE WHEN
THEN
FROM myemp;

salary
1 ELSE
salary
1 ELSE

BETWEEN 2001 AND 4000


null END) count2,
> 4000
null END) count3

COUNT1
COUNT2
COUNT3
---------- ---------- ---------0
5636096
8388608
Elapsed: 00:00:18.19
SQL>

24) Exit from your SQL*Plus session.


SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$

25) You now analyze a fifth case that deals with database connections. Execute the
bad_connect.sh script from your terminal window connected as the oracle
user. Note the time it takes to complete.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./bad_connect.sh
Wed Mar 26 21:56:42 GMT-7 2008
Wed Mar 26 21:57:07 GMT-7 2008
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

STREAM_NUM=0
MAX_STREAM=500
date
while [ $STREAM_NUM -lt $MAX_STREAM ]; do
# one more
let STREAM_NUM="STREAM_NUM+1"
# start one more stream

Oracle Database 11g: SQL Tuning Workshop A - 29

Practice 2-1: Avoiding Common Mistakes (continued)


sqlplus -s jfv/jfv @select.sql >> /tmp/bad_connect.log 2>&1
done
date
--------------------------------------------------------------

select count(*) from dba_users;


exit;

26) Analyze the bad_connect.sh script and try to find a better solution to enhance
the performance of that application. Test your solution. You should discuss this with
your instructor.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./better_connect.sh
Wed Mar 26 22:00:48 GMT-7 2008
Wed Mar 26 22:00:50 GMT-7 2008
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

date
sqlplus -s jfv/jfv @select2.sql >> /tmp/better_connect.log 2>&1
date
-------------------------------------------------------------declare
c number;
begin
for i in 1..500 loop
select count(*) into c from dba_users;
end loop;
end;
/
exit;

Oracle Database 11g: SQL Tuning Workshop A - 30

Practice 2-1: Avoiding Common Mistakes (continued)


27) Clean up your environment by executing the cleanup_rest.sh script from your
terminal session.
[oracle@edrsr33p1-orcl Common_Mistakes]$ ./cleanup_rest.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Mar 26 22:03:10
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
User dropped.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Common_Mistakes]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Common_Mistakes
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba <<EOF
set echo on
drop user jfv cascade;
EOF

Oracle Database 11g: SQL Tuning Workshop A - 31

Practices for Lesson 3

Oracle Database 11g: SQL Tuning Workshop A - 32

Practice 3-1: Understanding Optimizer Decisions


In this practice, you try to understand optimizer decisions relating to which execution
plan to use. All the scripts needed for this practice can be found in your
$HOME/solutions/Trace_Event directory.
1) Execute the te_setup.sh script. This script executes the following query and
generates a trace file that contains all optimizer decisions.
SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND
s.channel_id = ch.channel_id AND
c.cust_state_province = 'CA' AND
ch.channel_desc IN ('Internet','Catalog') AND
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;
[oracle@edrsr33p1-orcl Trace_Event]$ ./te_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 9 22:16:53 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter user sh identified by sh account unlock;
User altered.
SQL>
SQL> connect sh/sh
Connected.
SQL>
SQL> ALTER SESSION SET TRACEFILE_IDENTIFIER = 'MYOPTIMIZER';
Session altered.
SQL>
SQL> alter session set events '10053 trace name context forever,
level 1';
Session altered.
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 33

Practice 3-1: Understanding Optimizer Decisions (continued)


SQL> SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
2 FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
3 WHERE s.time_id = t.time_id AND
4
s.cust_id = c.cust_id AND
5
s.channel_id = ch.channel_id AND
6
c.cust_state_province = 'CA' AND
7
ch.channel_desc IN ('Internet','Catalog') AND
8
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')
9 GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc;
no rows selected
SQL>
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
orcl_ora_1801_MYOPTIMIZER.trc
orcl_ora_1801_MYOPTIMIZER.trm
[oracle@edrsr33p1-orcl Trace_Event]$

2) With the help of your instructor, see the generated trace and interpret the important
parts of the trace file. Note: This lab is only for demonstration purposes. Do not use it
on your production system unless explicitly asked by Oracle Support Services.
a) The 10053 trace output is broken down into a number of sections that broadly
reflect the stages that the optimizer goes through in evaluating a plan. These
stages are as follows: query, parameters used by the optimizer, base statistical
information, base table access cost, join order and method computations,
recosting for special features, such as query transformations.
[oracle@edrsr33p1-orcl Trace_Event]$ cat myoptimizer.trc
Trace file
/u01/app/oracle/diag/rdbms/orcl/orcl/trace/orcl_ora_32643_MYOPTIMIZE
R.trc
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
ORACLE_HOME = /u01/app/oracle/product/11.1.0/db_1
System name:
Linux
Node name:
edrsr33p1.us.oracle.com
Release:
2.6.9-55.0.0.0.2.ELsmp
Version:
#1 SMP Wed May 2 14:59:56 PDT 2007
Machine:
i686
Instance name: orcl
Redo thread mounted by this instance: 1
Oracle process number: 18
Unix process pid: 32643, image: oracle@edrsr33p1.us.oracle.com (TNS
V1-V3)

Oracle Database 11g: SQL Tuning Workshop A - 34

Practice 3-1: Understanding Optimizer Decisions (continued)


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

2008-04-09 21:46:51.950
SESSION ID:(134.35423) 2008-04-09 21:46:51.950
CLIENT ID:() 2008-04-09 21:46:51.950
SERVICE NAME:(SYS$USERS) 2008-04-09 21:46:51.950
MODULE NAME:(SQL*Plus) 2008-04-09 21:46:51.950
ACTION NAME:() 2008-04-09 21:46:51.950

Registered qb: SEL$1 0xb7e0905c (PARSER)


--------------------QUERY BLOCK SIGNATURE
--------------------signature (): qb_name=SEL$1 nbfros=4 flg=0
fro(0): flg=4 objn=72254 hint_alias="C"@"SEL$1"
fro(1): flg=4 objn=72252 hint_alias="CH"@"SEL$1"
fro(2): flg=4 objn=72192 hint_alias="S"@"SEL$1"
fro(3): flg=4 objn=72250 hint_alias="T"@"SEL$1"
SPM: statement not found in SMB
DOP: Automatic degree of parallelism is disabled: Parameter.
PM: Considering predicate move-around in query block SEL$1 (#0)
**************************
Predicate Move-Around (PM)
**************************
OPTIMIZER INFORMATION
******************************************
----- Current SQL Statement for this session (sql_id=70fqjd9u1zk7c)
----SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND
s.channel_id = ch.channel_id AND
c.cust_state_province = 'CA' AND
ch.channel_desc IN ('Internet','Catalog') AND
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc
*******************************************
Legend
The following abbreviations are used by optimizer trace.
CBQT - cost-based query transformation
JPPD - join predicate push-down
OJPPD - old-style (non-cost-based) JPPD
FPD - filter push-down
PM - predicate move-around
CVM - complex view merging
SPJ - select-project-join
SJC - set join conversion
SU - subquery unnesting
OBYE - order by elimination
ST - star transformation
CNT - count(col) to count(*) transformation
JE - Join Elimination
qb - query block
LB - leaf blocks
DK - distinct keys

Oracle Database 11g: SQL Tuning Workshop A - 35

Practice 3-1: Understanding Optimizer Decisions (continued)


LB/K - average number of leaf blocks per key
DB/K - average number of data blocks per key
CLUF - clustering factor
NDV - number of distinct values
Resp - response cost
Card - cardinality
Resc - resource cost
NL - nested loops (join)
SM - sort merge (join)
HA - hash (join)
CPUSPEED - CPU Speed
IOTFRSPEED - I/O transfer speed
IOSEEKTIM - I/O seek time
SREADTIM - average single block read time
MREADTIM - average multiblock read time
MBRC - average multiblock read count
MAXTHR - maximum I/O system throughput
SLAVETHR - average slave I/O throughput
dmeth - distribution method
1: no partitioning required
2: value partitioned
4: right is random (round-robin)
128: left is random (round-robin)
8: broadcast right and partition left
16: broadcast left and partition right
32: partition left using partitioning of right
64: partition right using partitioning of left
256: run the join in serial
0: invalid distribution method
sel - selectivity
ptn - partition
***************************************
PARAMETERS USED BY THE OPTIMIZER
********************************
*************************************
PARAMETERS WITH ALTERED VALUES
******************************
Compilation Environment Dump
_smm_min_size
= 204 KB
_smm_max_size
= 40960 KB
_smm_px_max_size
= 102400 KB
Bug Fix Control Environment

*************************************
PARAMETERS WITH DEFAULT VALUES
******************************
Compilation Environment Dump
optimizer_mode_hinted
= false
optimizer_features_hinted
= 0.0.0
parallel_execution_enabled
= true
parallel_query_forced_dop
= 0
parallel_dml_forced_dop
= 0
parallel_ddl_forced_degree
= 0
parallel_ddl_forced_instances
= 0
_query_rewrite_fudge
= 90
optimizer_features_enable
= 11.1.0.6

Oracle Database 11g: SQL Tuning Workshop A - 36

Practice 3-1: Understanding Optimizer Decisions (continued)


_optimizer_search_limit

= 5

optimizer_use_invisible_indexes
= false
flashback_data_archive_internal_cursor = 0
_optimizer_extended_stats_usage_control = 240
Bug Fix Control Environment
fix 3834770 = 1
fix 3746511 = enabled
fix 4519016 = enabled
fix 3118776 = enabled
fix 4488689 = enabled
fix 2194204 = disabled

fix 6133948 = enabled


fix 6239909 = enabled

***************************************
PARAMETERS IN OPT_PARAM HINT
****************************
***************************************
Column Usage Monitoring is ON: tracking level = 1
***************************************

Considering Query Transformations on query block SEL$1 (#0)


**************************
Query transformations (QT)
**************************
CBQT: Validity checks passed for 70fqjd9u1zk7c.
CSE: Considering common sub-expression elimination in query block
SEL$1 (#0)
*************************
Common Subexpression elimination (CSE)
*************************
CSE:
CSE not performed on query block SEL$1 (#0).
OBYE:
Considering Order-by Elimination from view SEL$1 (#0)
***************************
Order-by elimination (OBYE)
***************************
OBYE:
OBYE bypassed: no order by to eliminate.
JE:
Considering Join Elimination on query block SEL$1 (#0)
*************************
Join Elimination (JE)
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "CH"."CHANNEL_CLASS" "CHANNEL_CLASS","C"."CUST_CITY"
"CUST_CITY","T"."CALENDAR_QUARTER_DESC"
"CALENDAR_QUARTER_DESC",SUM("S"."AMOUNT_SOLD") "SALES_AMOUNT" FROM
"SH"."SALES" "S","SH"."TIMES" "T","SH"."CUSTOMERS"
"C","SH"."CHANNELS" "CH" WHERE "S"."TIME_ID"="T"."TIME_ID" AND
"S"."CUST_ID"="C"."CUST_ID" AND "S"."CHANNEL_ID"="CH"."CHANNEL_ID"
AND "C"."CUST_STATE_PROVINCE"='CA' AND
("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Catalog')
AND ("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR

Oracle Database 11g: SQL Tuning Workshop A - 37

Practice 3-1: Understanding Optimizer Decisions (continued)


"T"."CALENDAR_QUARTER_DESC"='1999-Q2') GROUP BY
"CH"."CHANNEL_CLASS","C"."CUST_CITY","T"."CALENDAR_QUARTER_DESC"
Query block SEL$1 (#0) unchanged
CNT:
Considering count(col) to count(*) on query block SEL$1 (#0)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:
COUNT() to COUNT(*) not done.
JE:
Considering Join Elimination on query block SEL$1 (#0)
*************************
Join Elimination (JE)
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "CH"."CHANNEL_CLASS" "CHANNEL_CLASS","C"."CUST_CITY"
"CUST_CITY","T"."CALENDAR_QUARTER_DESC"
"CALENDAR_QUARTER_DESC",SUM("S"."AMOUNT_SOLD") "SALES_AMOUNT" FROM
"SH"."SALES" "S","SH"."TIMES" "T","SH"."CUSTOMERS"
"C","SH"."CHANNELS" "CH" WHERE "S"."TIME_ID"="T"."TIME_ID" AND
"S"."CUST_ID"="C"."CUST_ID" AND "S"."CHANNEL_ID"="CH"."CHANNEL_ID"
AND "C"."CUST_STATE_PROVINCE"='CA' AND
("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Catalog')
AND ("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR
"T"."CALENDAR_QUARTER_DESC"='1999-Q2') GROUP BY
"CH"."CHANNEL_CLASS","C"."CUST_CITY","T"."CALENDAR_QUARTER_DESC"
Query block SEL$1 (#0) unchanged
query block SEL$1 (#0) unchanged
Considering Query Transformations on query block SEL$1 (#0)
**************************
Query transformations (QT)
**************************
CSE: Considering common sub-expression elimination in query block
SEL$1 (#0)
*************************
Common Subexpression elimination (CSE)
*************************
CSE:
CSE not performed on query block SEL$1 (#0).
query block SEL$1 (#0) unchanged
apadrv-start sqlid=8087006336042125548
:
call(in-use=62016, alloc=81864), compile(in-use=64064,
alloc=66868), execution(in-use=3376, alloc=4060)
*******************************************
Peeked values of the binds in SQL statement
*******************************************
CBQT: Considering cost-based transformation on query block SEL$1
(#0)
********************************
COST-BASED QUERY TRANSFORMATIONS
********************************
FPD: Considering simple filter push (pre rewrite) in query block
SEL$1 (#0)
FPD: Current where clause predicates "S"."TIME_ID"="T"."TIME_ID"
AND "S"."CUST_ID"="C"."CUST_ID" AND
"S"."CHANNEL_ID"="CH"."CHANNEL_ID" AND
"C"."CUST_STATE_PROVINCE"='CA' AND ("CH"."CHANNEL_DESC"='Internet'

Oracle Database 11g: SQL Tuning Workshop A - 38

Practice 3-1: Understanding Optimizer Decisions (continued)


OR "CH"."CHANNEL_DESC"='Catalog') AND
("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR "T"."C
OBYE:
Considering Order-by Elimination from view SEL$1 (#0)
***************************
Order-by elimination (OBYE)
***************************
OBYE:
OBYE bypassed: no order by to eliminate.
Considering Query Transformations on query block SEL$1 (#0)
**************************
Query transformations (QT)
**************************
CSE: Considering common sub-expression elimination in query block
SEL$1 (#0)
*************************
Common Subexpression elimination (CSE)
*************************
CSE:
CSE not performed on query block SEL$1 (#0).
kkqctdrvTD-start on query block SEL$1 (#0)
kkqctdrvTD-start: :
call(in-use=62016, alloc=81864), compile(in-use=105980,
alloc=109196), execution(in-use=3376, alloc=4060)
kkqctdrvTD-cleanup: transform(in-use=0, alloc=0) :
call(in-use=62016, alloc=81864), compile(in-use=106488,
alloc=109196), execution(in-use=3376, alloc=4060)
kkqctdrvTD-end:
call(in-use=62016, alloc=81864), compile(in-use=106808,
alloc=109196), execution(in-use=3376, alloc=4060)
SJC: Considering set-join conversion in query block SEL$1 (#1)
*************************
Set-Join Conversion (SJC)
*************************
SJC: not performed
CNT:
Considering count(col) to count(*) on query block SEL$1 (#1)
*************************
Count(col) to Count(*) (CNT)
*************************
CNT:
COUNT() to COUNT(*) not done.
JE:
Considering Join Elimination on query block SEL$1 (#1)
*************************
Join Elimination (JE)
*************************
SQL:******* UNPARSED QUERY IS *******
SELECT "CH"."CHANNEL_CLASS" "CHANNEL_CLASS","C"."CUST_CITY"
"CUST_CITY","T"."CALENDAR_QUARTER_DESC"
"CALENDAR_QUARTER_DESC",SUM("S"."AMOUNT_SOLD") "SALES_AMOUNT" FROM
"SH"."SALES" "S","SH"."TIMES" "T","SH"."CUSTOMERS"
"C","SH"."CHANNELS" "CH" WHERE "S"."TIME_ID"="T"."TIME_ID" AND
"S"."CUST_ID"="C"."CUST_ID" AND "S"."CHANNEL_ID"="CH"."CHANNEL_ID"
AND "C"."CUST_STATE_PROVINCE"='CA' AND
("CH"."CHANNEL_DESC"='Internet' OR "CH"."CHANNEL_DESC"='Catalog')
AND ("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR
"T"."CALENDAR_QUARTER_DESC"='1999-Q2') GROUP BY
"CH"."CHANNEL_CLASS","C"."CUST_CITY","T"."CALENDAR_QUARTER_DESC"

Oracle Database 11g: SQL Tuning Workshop A - 39

Practice 3-1: Understanding Optimizer Decisions (continued)


Query block SEL$1 (#1) unchanged
PM: Considering predicate move-around in query block SEL$1 (#1)
**************************
Predicate Move-Around (PM)
**************************
PM:
PM bypassed: Outer query contains no views.
PM:
PM bypassed: Outer query contains no views.
kkqctdrvTD-start on query block SEL$1 (#1)
kkqctdrvTD-start: :
call(in-use=81660, alloc=98240), compile(in-use=109508,
alloc=113320), execution(in-use=3376, alloc=4060)
kkqctdrvTD-cleanup: transform(in-use=0, alloc=0) :
call(in-use=81660, alloc=98240), compile(in-use=109984,
alloc=113320), execution(in-use=3376, alloc=4060)
kkqctdrvTD-end:
call(in-use=81660, alloc=98240), compile(in-use=110304,
alloc=113320), execution(in-use=3376, alloc=4060)
kkqctdrvTD-start on query block SEL$1 (#1)
kkqctdrvTD-start: :
call(in-use=81660, alloc=98240), compile(in-use=110304,
alloc=113320), execution(in-use=3376, alloc=4060)
Registered qb: SEL$1 0xb7e01c6c (COPY SEL$1)
--------------------QUERY BLOCK SIGNATURE
--------------------signature(): NULL
***********************************
Cost-Based Group By Placement
***********************************
GBP: Checking validity of GBP for query block SEL$1 (#1)
GBP: Checking validity of group-by placement for query block SEL$1
(#1)
GBP: Bypassed: QB has disjunction.
kkqctdrvTD-cleanup: transform(in-use=9008, alloc=9460) :
call(in-use=81684, alloc=98240), compile(in-use=129100,
alloc=132448), execution(in-use=3376, alloc=4060)
kkqctdrvTD-end:
call(in-use=81684, alloc=98240), compile(in-use=119884,
alloc=132448), execution(in-use=3376, alloc=4060)
GBP: Applying transformation directives
GBP: Checking validity of group-by placement for query block SEL$1
(#1)
GBP: Bypassed: QB has disjunction.
JPPD: Considering Cost-based predicate pushdown from query block
SEL$1 (#1)
************************************
Cost-based predicate pushdown (JPPD)
************************************
kkqctdrvTD-start on query block SEL$1 (#1)
kkqctdrvTD-start: :

Oracle Database 11g: SQL Tuning Workshop A - 40

Practice 3-1: Understanding Optimizer Decisions (continued)


call(in-use=81708, alloc=98240), compile(in-use=122632,
alloc=132448), execution(in-use=3376, alloc=4060)
kkqctdrvTD-cleanup: transform(in-use=0, alloc=0) :
call(in-use=81708, alloc=98240), compile(in-use=123108,
alloc=132448), execution(in-use=3376, alloc=4060)
kkqctdrvTD-end:
call(in-use=81708, alloc=98240), compile(in-use=123428,
alloc=132448), execution(in-use=3376, alloc=4060)
JPPD: Applying transformation directives
query block SEL$1 (#1) unchanged
FPD: Considering simple filter push in query block SEL$1 (#1)
"S"."TIME_ID"="T"."TIME_ID" AND "S"."CUST_ID"="C"."CUST_ID" AND
"S"."CHANNEL_ID"="CH"."CHANNEL_ID" AND
"C"."CUST_STATE_PROVINCE"='CA' AND ("CH"."CHANNEL_DESC"='Internet'
OR "CH"."CHANNEL_DESC"='Catalog') AND
("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR "T"."C
try to generate transitive predicate from check constraints for
query block SEL$1 (#1)
finally: "S"."TIME_ID"="T"."TIME_ID" AND "S"."CUST_ID"="C"."CUST_ID"
AND "S"."CHANNEL_ID"="CH"."CHANNEL_ID" AND
"C"."CUST_STATE_PROVINCE"='CA' AND ("CH"."CHANNEL_DESC"='Internet'
OR "CH"."CHANNEL_DESC"='Catalog') AND
("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR "T"."C
kkoqbc: optimizing query block SEL$1 (#1)
:
call(in-use=81716, alloc=98240), compile(in-use=124640,
alloc=132448), execution(in-use=3376, alloc=4060)
kkoqbc-subheap (create addr=0xb7d47960)
****************
QUERY BLOCK TEXT
****************
SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND
s.channel_id = ch.channel_id
--------------------QUERY BLOCK SIGNATURE
--------------------signature (optimizer): qb_name=SEL$1 nbfros=4 flg=0
fro(0): flg=0 objn=72254 hint_alias="C"@"SEL$1"
fro(1): flg=0 objn=72252 hint_alias="CH"@"SEL$1"
fro(2): flg=0 objn=72192 hint_alias="S"@"SEL$1"
fro(3): flg=0 objn=72250 hint_alias="T"@"SEL$1"
----------------------------SYSTEM STATISTICS INFORMATION
----------------------------Using NOWORKLOAD Stats
CPUSPEED: 1263 millions instructions/sec

Oracle Database 11g: SQL Tuning Workshop A - 41

Practice 3-1: Understanding Optimizer Decisions (continued)


IOTFRSPEED: 4096 bytes per millisecond (default is 4096)
IOSEEKTIM: 10 milliseconds (default is 10)
***************************************
BASE STATISTICAL INFORMATION
***********************
Table Stats::
Table: CHANNELS Alias: CH
#Rows: 5 #Blks: 4 AvgRowLen: 40.00
Index Stats::
Index: CHANNELS_PK Col#: 1
LVLS: 0 #LB: 1 #DK: 5 LB/K: 1.00 DB/K: 1.00 CLUF: 1.00
***********************
Table Stats::
Table: CUSTOMERS Alias: C
#Rows: 55500 #Blks: 1486 AvgRowLen: 180.00
Index Stats::
Index: CUSTOMERS_GENDER_BIX Col#: 4
LVLS: 1 #LB: 3 #DK: 2 LB/K: 1.00 DB/K: 2.00 CLUF: 5.00
Index: CUSTOMERS_MARITAL_BIX Col#: 6
LVLS: 1 #LB: 5 #DK: 11 LB/K: 1.00 DB/K: 1.00 CLUF: 18.00
Index: CUSTOMERS_PK Col#: 1
LVLS: 1 #LB: 115 #DK: 55500 LB/K: 1.00 DB/K: 1.00 CLUF:
54405.00
Index: CUSTOMERS_YOB_BIX Col#: 5
LVLS: 1 #LB: 19 #DK: 75 LB/K: 1.00 DB/K: 1.00 CLUF: 75.00
***********************
Table Stats::
Table: TIMES Alias: T
#Rows: 1826 #Blks: 59 AvgRowLen: 197.00
Index Stats::
Index: TIMES_PK Col#: 1
LVLS: 1 #LB: 5 #DK: 1826 LB/K: 1.00 DB/K: 1.00 CLUF: 53.00
***********************
Table Stats::
Table: SALES Alias: S (Using composite stats)
#Rows: 918843 #Blks: 1769 AvgRowLen: 29.00
Index Stats::
Index: SALES_CHANNEL_BIX Col#: 4
USING COMPOSITE STATS
LVLS: 1 #LB: 47 #DK: 4 LB/K: 11.00 DB/K: 23.00 CLUF: 92.00
Index: SALES_CUST_BIX Col#: 2
USING COMPOSITE STATS
LVLS: 1 #LB: 475 #DK: 7059 LB/K: 1.00 DB/K: 5.00 CLUF:
35808.00
Index: SALES_PROD_BIX Col#: 1
USING COMPOSITE STATS
LVLS: 1 #LB: 32 #DK: 72 LB/K: 1.00 DB/K: 14.00 CLUF:
1074.00
Index: SALES_PROMO_BIX Col#: 5
USING COMPOSITE STATS
LVLS: 1 #LB: 30 #DK: 4 LB/K: 7.00 DB/K: 13.00 CLUF: 54.00
Index: SALES_TIME_BIX Col#: 3
USING COMPOSITE STATS
LVLS: 1 #LB: 59 #DK: 1460 LB/K: 1.00 DB/K: 1.00 CLUF:
1460.00
Access path analysis for SALES

Oracle Database 11g: SQL Tuning Workshop A - 42

Practice 3-1: Understanding Optimizer Decisions (continued)


***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for SALES[S]
Table: SALES Alias: S
Card: Original: 918843.000000 Rounded: 918843 Computed:
918843.00 Non Adjusted: 918843.00
Access Path: TableScan
Cost: 498.20 Resp: 498.20 Degree: 0
Cost_io: 481.00 Cost_cpu: 260685437
Resp_io: 481.00 Resp_cpu: 260685437
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: SALES_CHANNEL_BIX
resc_io: 75.00 resc_cpu: 552508
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 75.04 Resp: 75.04 Degree: 0
Access Path: index (FullScan)
Index: SALES_CUST_BIX
resc_io: 503.00 resc_cpu: 10743684
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 503.71 Resp: 503.71 Degree: 0
Access Path: index (FullScan)
Index: SALES_PROD_BIX
resc_io: 60.00 resc_cpu: 642086
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 60.04 Resp: 60.04 Degree: 0
Access Path: index (FullScan)
Index: SALES_PROMO_BIX
resc_io: 58.00 resc_cpu: 423844
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 58.03 Resp: 58.03 Degree: 0
Access Path: index (FullScan)
Index: SALES_TIME_BIX
resc_io: 60.00 resc_cpu: 719286
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 60.05 Resp: 60.05 Degree: 0
Access Path: index (FullScan)
Index: SALES_PROMO_BIX
resc_io: 58.00 resc_cpu: 423844
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 58.03
Access path: Bitmap index - accepted
Cost: 2895.306181 Cost_io: 2869.400000 Cost_cpu:
392576474.936000 Sel: 1.000000
Not Believed to be index-only
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: TableScan
Cost: 498.20 Degree: 1 Resp: 498.20 Card: 918843.00
Bytes: 0
Access path analysis for TIMES
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for TIMES[T]
Table: TIMES Alias: T

Oracle Database 11g: SQL Tuning Workshop A - 43

Practice 3-1: Understanding Optimizer Decisions (continued)


Card: Original: 1826.000000 Rounded: 183 Computed: 182.60 Non
Adjusted: 182.60
Access Path: TableScan
Cost: 18.15 Resp: 18.15 Degree: 0
Cost_io: 18.00 Cost_cpu: 2314640
Resp_io: 18.00 Resp_cpu: 2314640
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: TIMES_PK
resc_io: 6.00 resc_cpu: 407929
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 6.03 Resp: 6.03 Degree: 0
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: TableScan
Cost: 18.15 Degree: 1 Resp: 18.15 Card: 182.60 Bytes: 0
Access path analysis for CUSTOMERS
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for CUSTOMERS[C]
Table: CUSTOMERS Alias: C
Card: Original: 55500.000000 Rounded: 383 Computed: 382.76
Non Adjusted: 382.76
Access Path: TableScan
Cost: 406.16 Resp: 406.16 Degree: 0
Cost_io: 404.00 Cost_cpu: 32782460
Resp_io: 404.00 Resp_cpu: 32782460
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: CUSTOMERS_GENDER_BIX
resc_io: 4.00 resc_cpu: 29486
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 4.00 Resp: 4.00 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_MARITAL_BIX
resc_io: 6.00 resc_cpu: 46329
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 6.00 Resp: 6.00 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_PK
resc_io: 116.00 resc_cpu: 11926087
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 116.79 Resp: 116.79 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_YOB_BIX
resc_io: 20.00 resc_cpu: 157429
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 20.01 Resp: 20.01 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_GENDER_BIX
resc_io: 4.00 resc_cpu: 29486
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 4.00 Resp: 4.00 Degree: 0
Access path: Bitmap index - accepted
Cost: 2367.447569 Cost_io: 2364.560000 Cost_cpu: 43757572.166400
Sel: 1.000000
Not Believed to be index-only

Oracle Database 11g: SQL Tuning Workshop A - 44

Practice 3-1: Understanding Optimizer Decisions (continued)


****** finished trying bitmap/domain indexes ******
Best:: AccessPath: TableScan
Cost: 406.16 Degree: 1 Resp: 406.16 Card: 382.76

Bytes:

0
Access path analysis for CHANNELS
***************************************
SINGLE TABLE ACCESS PATH
Single Table Cardinality Estimation for CHANNELS[CH]
Table: CHANNELS Alias: CH
Card: Original: 5.000000 Rounded: 2 Computed: 2.00 Non
Adjusted: 2.00
Access Path: TableScan
Cost: 3.00 Resp: 3.00 Degree: 0
Cost_io: 3.00 Cost_cpu: 29826
Resp_io: 3.00 Resp_cpu: 29826
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: CHANNELS_PK
resc_io: 1.00 resc_cpu: 8121
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 1.00 Resp: 1.00 Degree: 0
****** finished trying bitmap/domain indexes ******
Best:: AccessPath: TableScan
Cost: 3.00 Degree: 1 Resp: 3.00 Card: 2.00 Bytes: 0
Grouping column cardinality [CHANNEL_CL]
Grouping column cardinality [ CUST_CITY]
Grouping column cardinality [CALENDAR_Q]
***************************************

2
286
2

OPTIMIZER STATISTICS AND COMPUTATIONS


***************************************
GENERAL PLANS
***************************************
Considering cardinality-based initial join order.
Permutations for Starting Table :0
Join order[1]: CHANNELS[CH]#0 TIMES[T]#1 CUSTOMERS[C]#2
SALES[S]#3
***************
Now joining: TIMES[T]#1
***************
NL Join
Outer table: Card: 2.00 Cost: 3.00 Resp: 3.00
21
Access path analysis for TIMES
Inner table: TIMES Alias: T
Access Path: TableScan
NL Join: Cost: 37.31 Resp: 37.31 Degree: 1
Cost_io: 37.00 Cost_cpu: 4659106
Resp_io: 37.00 Resp_cpu: 4659106
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: TIMES_PK
resc_io: 6.00 resc_cpu: 407929

Degree: 1

Oracle Database 11g: SQL Tuning Workshop A - 45

Bytes:

Practice 3-1: Understanding Optimizer Decisions (continued)


ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 6.03 Resp: 6.03 Degree: 0
****** finished trying bitmap/domain indexes ******
Best NL cost: 37.31
resc: 37.31 resc_io: 37.00 resc_cpu: 4659106
resp: 37.31 resp_io: 37.00 resc_cpu: 4659106
Join Card: 365.200000 = = outer (2.000000) * inner (182.600000) *
sel (1.000000)
Join Card - Rounded: 365 Computed: 365.20
Grouping column cardinality [CHANNEL_CL]
2
Grouping column cardinality [ CUST_CITY]
286
Grouping column cardinality [CALENDAR_Q]
2
Best:: JoinMethod: NestedLoop
Cost: 37.31 Degree: 1 Resp: 37.31 Card: 365.20 Bytes: 37
***************
Now joining: CUSTOMERS[C]#2
***************
NL Join
Outer table: Card: 365.20 Cost: 37.31 Resp: 37.31 Degree: 1
Bytes: 37
Access path analysis for CUSTOMERS
Inner table: CUSTOMERS Alias: C
Access Path: TableScan
NL Join: Cost: 147725.92 Resp: 147725.92 Degree: 1
Cost_io: 146936.00 Cost_cpu: 11970256947
Resp_io: 146936.00 Resp_cpu: 11970256947
****** trying bitmap/domain indexes ******
Access Path: index (FullScan)
Index: CUSTOMERS_GENDER_BIX
resc_io: 4.00 resc_cpu: 29486
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 4.00 Resp: 4.00 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_MARITAL_BIX
resc_io: 6.00 resc_cpu: 46329
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 6.00 Resp: 6.00 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_PK
resc_io: 116.00 resc_cpu: 11926087
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 116.79 Resp: 116.79 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_YOB_BIX
resc_io: 20.00 resc_cpu: 157429
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
Cost: 20.01 Resp: 20.01 Degree: 0
Access Path: index (FullScan)
Index: CUSTOMERS_GENDER_BIX
resc_io: 4.00 resc_cpu: 29486
ix_sel: 1.000000 ix_sel_with_filters: 1.000000
NL Join : Cost: 1498.02 Resp: 1498.02 Degree: 1
Cost_io: 1497.00 Cost_cpu: 15421408
Resp_io: 1497.00 Resp_cpu: 15421408
Access path: Bitmap index - accepted

Oracle Database 11g: SQL Tuning Workshop A - 46

Practice 3-1: Understanding Optimizer Decisions (continued)


Cost: 864192.977522 Cost_io: 863138.400000 Cost_cpu:
15980832052.096001 Sel: 1.000000
Not Believed to be index-only
****** finished trying bitmap/domain indexes ******
Best NL cost: 147725.92
resc: 147725.92 resc_io: 146936.00 resc_cpu: 11970256947
resp: 147725.92 resp_io: 146936.00 resc_cpu: 11970256947
Join Card: 139783.448276 = = outer (365.200000) * inner
(382.758621) * sel (1.000000)
Join Card - Rounded: 139783 Computed: 139783.45
Grouping column cardinality [CHANNEL_CL]
2
Grouping column cardinality [ CUST_CITY]
286
Grouping column cardinality [CALENDAR_Q]
2
Best:: JoinMethod: NestedLoop
Cost: 147725.92 Degree: 1 Resp: 147725.92 Card: 139783.45
Bytes: 63
***************
Now joining: SALES[S]#3
***************
NL Join
Outer table: Card: 139783.45 Cost: 147725.92 Resp: 147725.92
Degree: 1 Bytes: 63
Access path analysis for SALES
Inner table: SALES Alias: S
Access Path: TableScan
NL Join: Cost: 2625413.78 Resp: 2625413.78 Degree: 1
Cost_io: 2538743.82 Cost_cpu: 1313377131608
Resp_io: 2538743.82 Resp_cpu: 1313377131608
****** trying bitmap/domain indexes ******
Access Path: index (AllEqJoinGuess)
Index: SALES_CHANNEL_BIX
resc_io: 11.00 resc_cpu: 83786
ix_sel: 0.250000 ix_sel_with_filters: 0.250000
NL Join : Cost: 1686111.78 Resp: 1686111.78 Degree: 1
Cost_io: 1684549.00 Cost_cpu: 23682093020
Resp_io: 1684549.00 Resp_cpu: 23682093020
Access Path: index (AllEqJoinGuess)
Index: SALES_CUST_BIX
resc_io: 1.00 resc_cpu: 9171
ix_sel: 0.000142 ix_sel_with_filters: 0.000142
NL Join : Cost: 287593.52 Resp: 287593.52 Degree: 1
Cost_io: 286719.00 Cost_cpu: 13252268345
Resp_io: 286719.00 Resp_cpu: 13252268345
Access Path: index (AllEqJoinGuess)
Index: SALES_TIME_BIX
resc_io: 1.00 resc_cpu: 8171
ix_sel: 0.000685 ix_sel_with_filters: 0.000685
NL Join : Cost: 287584.29 Resp: 287584.29 Degree: 1
Cost_io: 286719.00 Cost_cpu: 13112485345
Resp_io: 286719.00 Resp_cpu: 13112485345
Access path: Bitmap index - accepted
Cost: 723020.120275 Cost_io: 720497.059076 Cost_cpu:
38233905490.990532 Sel: 0.000000
Not Believed to be index-only
****** finished trying bitmap/domain indexes ******

Oracle Database 11g: SQL Tuning Workshop A - 47

Practice 3-1: Understanding Optimizer Decisions (continued)


Best NL cost: 723020.12
resc: 723020.12 resc_io: 720497.06 resc_cpu: 38233905491
resp: 723020.12 resp_io: 720497.06 resc_cpu: 38233905491
Join Card: 3115.595241 = = outer (139783.448276) * inner
(918843.000000) * sel (0.000000)
Join Card - Rounded: 3116 Computed: 3115.60
Grouping column cardinality [CHANNEL_CL]
2
Grouping column cardinality [ CUST_CITY]
286
Grouping column cardinality [CALENDAR_Q]
2
Outer table: CUSTOMERS Alias: C
resc: 147725.92 card 139783.45 bytes: 63 deg: 1 resp:
147725.92
Inner table: SALES Alias: S
resc: 498.20 card: 918843.00 bytes: 21 deg: 1 resp: 498.20
using dmeth: 2 #groups: 1
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 1370 Row size:
80 Total Rows:
139783
Initial runs:
2 Merge passes: 1 IO Cost / pass:
744
Total IO sort cost: 2114
Total CPU sort cost: 156539686
Total Temp space used: 21423000
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 3825 Row size:
34 Total Rows:
918843
Initial runs:
2 Merge passes: 1 IO Cost / pass:
2074
Total IO sort cost: 5899
Total CPU sort cost: 929421655
Total Temp space used: 66626000
SM join: Resc: 156308.78 Resp: 156308.78 [multiMatchCost=0.00]
SM Join
SM cost: 156308.78
resc: 156308.78 resc_io: 155430.00 resc_cpu: 13316903726
resp: 156308.78 resp_io: 155430.00 resp_cpu: 13316903726
Outer table: CUSTOMERS Alias: C
resc: 147725.92 card 139783.45 bytes: 63 deg: 1 resp:
147725.92
Inner table: SALES Alias: S
resc: 498.20 card: 918843.00 bytes: 21 deg: 1 resp: 498.20
using dmeth: 2 #groups: 1
Cost per ptn: 1944.36 #ptns: 1
hash_area: 124 (max=10240) buildfrag: 1280 probefrag: 3702
ppasses: 1
Hash join: Resc: 150168.48 Resp: 150168.48 [multiMatchCost=0.00]
HA Join
HA cost: 150168.48
resc: 150168.48 resc_io: 149346.00 resc_cpu: 12463693737
resp: 150168.48 resp_io: 149346.00 resp_cpu: 12463693737
GROUP BY sort
GROUP BY adjustment factor: 0.500000
GROUP BY cardinality: 572.000000, TABLE cardinality: 3116.000000
SORT ressource
Sort statistics

Oracle Database 11g: SQL Tuning Workshop A - 48

Practice 3-1: Understanding Optimizer Decisions (continued)


Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 40 Row size:
103 Total Rows:
3116
Initial runs:
1 Merge passes: 0 IO Cost / pass:
0
Total IO sort cost: 0
Total CPU sort cost: 16783070
Total Temp space used: 0
Best:: JoinMethod: Hash
Cost: 150169.59 Degree: 1 Resp: 150169.59 Card: 3115.60
Bytes: 84
***********************
Best so far: Table#: 0 cost: 3.0020 card: 2.0000 bytes: 42
Table#: 1 cost: 37.3075 card: 365.2000 bytes: 13505
Table#: 2 cost: 147725.9191 card: 139783.4483
bytes: 8806329
Table#: 3 cost: 150169.5886 card: 3115.5952 bytes:
261744
***********************
Join order[2]: CHANNELS[CH]#0 TIMES[T]#1 SALES[S]#3
CUSTOMERS[C]#2
***************
Now joining: SALES[S]#3
***************
NL Join
Outer table: Card: 365.20 Cost: 37.31 Resp: 37.31
Bytes: 37
Access path analysis for SALES
Inner table: SALES Alias: S
Access Path: TableScan
NL Join: Cost: 6507.09 Resp: 6507.09 Degree: 1

Degree: 1

***********************
Join order[22]: SALES[S]#3
CHANNELS[CH]#0

CUSTOMERS[C]#2

TIMES[T]#1

***************
Now joining: TIMES[T]#1
***************
NL Join
Outer table: Card: 49822.22 Cost: 910.93 Resp: 910.93
Bytes: 47
Access path analysis for TIMES
Inner table: TIMES Alias: T
Access Path: TableScan
NL Join: Cost: 804636.92 Resp: 804636.92 Degree: 1
Cost_io: 797001.00 Cost_cpu: 115712978623
Resp_io: 797001.00 Resp_cpu: 115712978623
Access Path: index (UniqueScan)
Index: TIMES_PK
resc_io: 1.00 resc_cpu: 10059
ix_sel: 0.000548 ix_sel_with_filters: 0.000548
NL Join : Cost: 50766.00 Resp: 50766.00 Degree: 1
Cost_io: 50707.00 Cost_cpu: 894143044

Oracle Database 11g: SQL Tuning Workshop A - 49

Degree: 1

Practice 3-1: Understanding Optimizer Decisions (continued)


Resp_io: 50707.00 Resp_cpu: 894143044
Access Path: index (AllEqUnique)
Index: TIMES_PK
resc_io: 1.00 resc_cpu: 10059
ix_sel: 0.000548 ix_sel_with_filters: 0.000548
NL Join : Cost: 50766.00 Resp: 50766.00 Degree: 1
Cost_io: 50707.00 Cost_cpu: 894143044
Resp_io: 50707.00 Resp_cpu: 894143044
****** trying bitmap/domain indexes ******
****** finished trying bitmap/domain indexes ******
Best NL cost: 50766.00
resc: 50766.00 resc_io: 50707.00 resc_cpu: 894143044
resp: 50766.00 resp_io: 50707.00 resc_cpu: 894143044
Join Card: 6231.190483 = = outer (49822.224013) * inner
(182.600000) * sel (0.000685)
Join Card - Rounded: 6231 Computed: 6231.19
Grouping column cardinality [CHANNEL_CL]
2
Grouping column cardinality [ CUST_CITY]
286
Grouping column cardinality [CALENDAR_Q]
2
Outer table: CUSTOMERS Alias: C
resc: 910.93 card 49822.22 bytes: 47 deg: 1 resp: 910.93
Inner table: TIMES Alias: T
resc: 18.15 card: 182.60 bytes: 16 deg: 1 resp: 18.15
using dmeth: 2 #groups: 1
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 379 Row size:
62 Total Rows:
49822
Initial runs:
2 Merge passes: 1 IO Cost / pass:
206
Total IO sort cost: 585
Total CPU sort cost: 59514576
Total Temp space used: 6022000
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 1 Row size:
28 Total Rows:
183
Initial runs:
1 Merge passes: 0 IO Cost / pass:
0
Total IO sort cost: 0
Total CPU sort cost: 15215743
Total Temp space used: 0
SM join: Resc: 1519.02 Resp: 1519.02 [multiMatchCost=0.00]
SM Join
SM cost: 1519.02
resc: 1519.02 resc_io: 1488.00 resc_cpu: 470031494
resp: 1519.02 resp_io: 1488.00 resp_cpu: 470031494
Outer table: CUSTOMERS Alias: C
resc: 910.93 card 49822.22 bytes: 47 deg: 1 resp: 910.93
Inner table: TIMES Alias: T
resc: 18.15 card: 182.60 bytes: 16 deg: 1 resp: 18.15
using dmeth: 2 #groups: 1
Cost per ptn: 141.09 #ptns: 1
hash_area: 124 (max=10240) buildfrag: 359 probefrag: 1
ppasses: 1
Hash join: Resc: 1070.22 Resp: 1070.22 [multiMatchCost=0.04]
Outer table: TIMES Alias: T

Oracle Database 11g: SQL Tuning Workshop A - 50

Practice 3-1: Understanding Optimizer Decisions (continued)


resc: 18.15 card 182.60 bytes: 16 deg: 1 resp: 18.15
Inner table: CUSTOMERS Alias: C
resc: 910.93 card: 49822.22 bytes: 47 deg: 1 resp: 910.93
using dmeth: 2 #groups: 1
Cost per ptn: 0.83 #ptns: 1
hash_area: 124 (max=10240) buildfrag: 1 probefrag: 359
ppasses: 1
Hash join: Resc: 929.92 Resp: 929.92 [multiMatchCost=0.00]
HA Join
HA cost: 929.92 swapped
resc: 929.92 resc_io: 903.00 resc_cpu: 407887714
resp: 929.92 resp_io: 903.00 resp_cpu: 407887714
Best:: JoinMethod: Hash
Cost: 929.92 Degree: 1 Resp: 929.92 Card: 6231.19 Bytes:
63
***************
Now joining: CHANNELS[CH]#0
***************
NL Join
Outer table: Card: 6231.19 Cost: 929.92 Resp: 929.92
Bytes: 63
Access path analysis for CHANNELS
Inner table: CHANNELS Alias: CH
Access Path: TableScan
NL Join: Cost: 7694.18 Resp: 7694.18 Degree: 1
Cost_io: 7655.00 Cost_cpu: 593732024
Resp_io: 7655.00 Resp_cpu: 593732024
Access Path: index (UniqueScan)
Index: CHANNELS_PK
resc_io: 1.00 resc_cpu: 8451
ix_sel: 0.200000 ix_sel_with_filters: 0.200000
NL Join : Cost: 7164.39 Resp: 7164.39 Degree: 1
Cost_io: 7134.00 Cost_cpu: 460548636
Resp_io: 7134.00 Resp_cpu: 460548636
Access Path: index (AllEqUnique)
Index: CHANNELS_PK
resc_io: 1.00 resc_cpu: 8451
ix_sel: 0.200000 ix_sel_with_filters: 0.200000
NL Join : Cost: 7164.39 Resp: 7164.39 Degree: 1
Cost_io: 7134.00 Cost_cpu: 460548636
Resp_io: 7134.00 Resp_cpu: 460548636
****** trying bitmap/domain indexes ******
****** finished trying bitmap/domain indexes ******

Degree: 1

Best NL cost: 7164.39


resc: 7164.39 resc_io: 7134.00 resc_cpu: 460548636
resp: 7164.39 resp_io: 7134.00 resc_cpu: 460548636
Join Card: 3115.595241 = = outer (6231.190483) * inner (2.000000) *
sel (0.250000)
Join Card - Rounded: 3116 Computed: 3115.60
Grouping column cardinality [CHANNEL_CL]
2
Grouping column cardinality [ CUST_CITY]
286
Grouping column cardinality [CALENDAR_Q]
2
Outer table: TIMES Alias: T
resc: 929.92 card 6231.19 bytes: 63 deg: 1 resp: 929.92
Inner table: CHANNELS Alias: CH

Oracle Database 11g: SQL Tuning Workshop A - 51

Practice 3-1: Understanding Optimizer Decisions (continued)


resc: 3.00 card: 2.00 bytes: 21 deg: 1 resp: 3.00
using dmeth: 2 #groups: 1
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 62 Row size:
80 Total Rows:
6231
Initial runs:
2 Merge passes: 1 IO Cost / pass:
36
Total IO sort cost: 98
Total CPU sort cost: 20219323
Total Temp space used: 926000
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 1 Row size:
34 Total Rows:
2
Initial runs:
1 Merge passes: 0 IO Cost / pass:
0
Total IO sort cost: 0
Total CPU sort cost: 15153867
Total Temp space used: 0
SM join: Resc: 1033.25 Resp: 1033.25 [multiMatchCost=0.00]
SM Join
SM cost: 1033.25
resc: 1033.25 resc_io: 1004.00 resc_cpu: 443290729
resp: 1033.25 resp_io: 1004.00 resp_cpu: 443290729
Outer table: TIMES Alias: T
resc: 929.92 card 6231.19 bytes: 63 deg: 1 resp: 929.92
Inner table: CHANNELS Alias: CH
resc: 3.00 card: 2.00 bytes: 21 deg: 1 resp: 3.00
using dmeth: 2 #groups: 1
Cost per ptn: 0.56 #ptns: 1
hash_area: 124 (max=10240) buildfrag: 58 probefrag: 1 ppasses:
1
Hash join: Resc: 933.50 Resp: 933.50 [multiMatchCost=0.02]
Outer table: CHANNELS Alias: CH
resc: 3.00 card 2.00 bytes: 21 deg: 1 resp: 3.00
Inner table: TIMES Alias: T
resc: 929.92 card: 6231.19 bytes: 63 deg: 1 resp: 929.92
using dmeth: 2 #groups: 1
Cost per ptn: 0.54 #ptns: 1
hash_area: 124 (max=10240) buildfrag: 1 probefrag: 58 ppasses:
1
Hash join: Resc: 933.46 Resp: 933.46 [multiMatchCost=0.00]
HA Join
HA cost: 933.46 swapped
resc: 933.46 resc_io: 906.00 resc_cpu: 416117828
resp: 933.46 resp_io: 906.00 resp_cpu: 416117828
GROUP BY sort
GROUP BY adjustment factor: 0.500000
GROUP BY cardinality: 572.000000, TABLE cardinality: 3116.000000
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 40 Row size:
103 Total Rows:
3116
Initial runs:
1 Merge passes: 0 IO Cost / pass:
0
Total IO sort cost: 0
Total CPU sort cost: 16783070
Total Temp space used: 0

Oracle Database 11g: SQL Tuning Workshop A - 52

Practice 3-1: Understanding Optimizer Decisions (continued)


Join order aborted: cost > best plan cost
***********************
(newjo-stop-1) k:0, spcnt:0, perm:22, maxperm:2000
*********************************
Number of join permutations tried: 22
*********************************
(newjo-save)
[1 3 2 0 ]
GROUP BY adjustment factor: 0.500000
GROUP BY cardinality: 572.000000, TABLE cardinality: 3116.000000
SORT ressource
Sort statistics
Sort width:
238 Area size:
208896 Max Area size:
41943040
Degree:
1
Blocks to Sort: 40 Row size:
103 Total Rows:
3116
Initial runs:
1 Merge passes: 0 IO Cost / pass:
0
Total IO sort cost: 0
Total CPU sort cost: 16783070
Total Temp space used: 0
Trying or-Expansion on query block SEL$1 (#1)
Transfer Optimizer annotations for query block SEL$1 (#1)
id=0 frofand predicate="C"."CUST_STATE_PROVINCE"='CA'
id=0 frofkksm[i] (sort-merge/hash)
predicate="S"."CUST_ID"="C"."CUST_ID"
id=0 frosand (sort-merge/hash) predicate="S"."CUST_ID"="C"."CUST_ID"
id=0 frofkksm[i] (sort-merge/hash)
predicate="S"."TIME_ID"="T"."TIME_ID"
id=0 frosand (sort-merge/hash) predicate="S"."TIME_ID"="T"."TIME_ID"
id=0 frofand predicate="T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR
"T"."CALENDAR_QUARTER_DESC"='1999-Q2'
id=0 frofkksm[i] (sort-merge/hash)
predicate="S"."CHANNEL_ID"="CH"."CHANNEL_ID"
id=0 frosand (sort-merge/hash)
predicate="S"."CHANNEL_ID"="CH"."CHANNEL_ID"
id=0 frofand predicate="CH"."CHANNEL_DESC"='Catalog' OR
"CH"."CHANNEL_DESC"='Internet'
GROUP BY adjustment factor: 1.000000
Final cost for query block SEL$1 (#1) - All Rows Plan:
Best join order: 16
Cost: 934.5672 Degree: 1 Card: 3116.0000 Bytes: 261744
Resc: 934.5672 Resc_io: 906.0000 Resc_cpu: 432900898
Resp: 934.5672 Resp_io: 906.0000 Resc_cpu: 432900898
kkoqbc-subheap (delete addr=0xb7d47960, in-use=105564, alloc=107812)
kkoqbc-end:
:
call(in-use=124380, alloc=246544), compile(in-use=132500,
alloc=136572), execution(in-use=3376, alloc=4060)
kkoqbc: finish optimizing query block SEL$1 (#1)
apadrv-end
:
call(in-use=124380, alloc=246544), compile(in-use=133196,
alloc=136572), execution(in-use=3376, alloc=4060)

Starting SQL statement dump

Oracle Database 11g: SQL Tuning Workshop A - 53

Practice 3-1: Understanding Optimizer Decisions (continued)


user_id=92 user_name=SH module=SQL*Plus action=
sql_id=70fqjd9u1zk7c plan_hash_value=1647000731 problem_type=3
----- Current SQL Statement for this session (sql_id=70fqjd9u1zk7c)
----SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND
s.channel_id = ch.channel_id AND
c.cust_state_province = 'CA' AND
ch.channel_desc IN ('Internet','Catalog') AND
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc
sql_text_length=473
sql=SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM
sh.sales s,sh.times t,sh.customers c,sh.channels ch
WHERE s.time_id = t.time_id AND
s.cust_id = c.cust_id AND
s.channel_id = ch.channel_id
sql=AND
c.cust_state_province = 'CA' AND
ch.channel_desc IN ('Internet','Catalog') AND
t.calendar_quarter_desc IN ('1999-Q1','1999-Q2')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc
----- Explain Plan Dump --------- Plan Table ----============
Plan Table
============
----------------------------------------------------+----------------------------------+---------------+
| Id | Operation
| Name
| Rows | Bytes
| Cost | Time
| Pstart| Pstop |
----------------------------------------------------+----------------------------------+---------------+
| 0
| SELECT STATEMENT
|
|
|
|
935 |
|
|
|
| 1
| HASH GROUP BY
|
|
572 |
47K
|
935 | 00:00:12 |
|
|
| 2
|
HASH JOIN
|
| 3116 | 256K
|
933 | 00:00:12 |
|
|
| 3
|
TABLE ACCESS FULL
| CHANNELS |
2 |
42
|
3 | 00:00:01 |
|
|
| 4
|
HASH JOIN
|
| 6231 | 383K
|
930 | 00:00:12 |
|
|
| 5
|
PART JOIN FILTER CREATE
| :BF0000 |
183 | 2928
|
18 | 00:00:01 |
|
|
| 6
|
TABLE ACCESS FULL
| TIMES
|
183 | 2928
|
18 | 00:00:01 |
|
|
| 7
|
HASH JOIN
|
|
49K | 2287K
|
911 | 00:00:11 |
|
|
| 8
|
TABLE ACCESS FULL
| CUSTOMERS|
383 | 9958
|
406 | 00:00:05 |
|
|

Oracle Database 11g: SQL Tuning Workshop A - 54

Practice 3-1: Understanding Optimizer Decisions (continued)


| 9
|
PARTITION RANGE JOIN-FILTER |
| 897K |
18M
|
498 | 00:00:06 | :BF0000| :BF0000|
| 10 |
TABLE ACCESS FULL
| SALES
| 897K |
18M
|
498 | 00:00:06 | :BF0000| :BF0000|
----------------------------------------------------+----------------------------------+---------------+
Predicate Information:
---------------------2 - access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
3 - filter(("CH"."CHANNEL_DESC"='Catalog' OR
"CH"."CHANNEL_DESC"='Internet'))
4 - access("S"."TIME_ID"="T"."TIME_ID")
6 - filter(("T"."CALENDAR_QUARTER_DESC"='1999-Q1' OR
"T"."CALENDAR_QUARTER_DESC"='1999-Q2'))
7 - access("S"."CUST_ID"="C"."CUST_ID")
8 - filter("C"."CUST_STATE_PROVINCE"='CA')
Content of other_xml column
===========================
db_version
: 11.1.0.6
parse_schema
: SH
plan_hash
: 1647000731
plan_hash_2
: 94088042
Outline Data:
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.6')
DB_VERSION('11.1.0.6')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
FULL(@"SEL$1" "C"@"SEL$1")
FULL(@"SEL$1" "S"@"SEL$1")
FULL(@"SEL$1" "T"@"SEL$1")
FULL(@"SEL$1" "CH"@"SEL$1")
LEADING(@"SEL$1" "C"@"SEL$1" "S"@"SEL$1" "T"@"SEL$1"
"CH"@"SEL$1")
USE_HASH(@"SEL$1" "S"@"SEL$1")
USE_HASH(@"SEL$1" "T"@"SEL$1")
USE_HASH(@"SEL$1" "CH"@"SEL$1")
SWAP_JOIN_INPUTS(@"SEL$1" "T"@"SEL$1")
SWAP_JOIN_INPUTS(@"SEL$1" "CH"@"SEL$1")
USE_HASH_AGGREGATION(@"SEL$1")
END_OUTLINE_DATA
*/
Optimizer state dump:
Compilation Environment Dump
optimizer_mode_hinted
optimizer_features_hinted
parallel_execution_enabled
parallel_query_forced_dop
parallel_dml_forced_dop
parallel_ddl_forced_degree
parallel_ddl_forced_instances
_query_rewrite_fudge
optimizer_features_enable

=
=
=
=
=
=
=
=
=

false
0.0.0
true
0
0
0
0
90
11.1.0.6

Oracle Database 11g: SQL Tuning Workshop A - 55

Practice 3-1: Understanding Optimizer Decisions (continued)

Bug Fix Control Environment


fix 3834770 = 1
fix 3746511 = enabled
fix 4519016 = enabled
fix 3118776 = enabled
fix 4488689 = enabled
fix 2194204 = disabled
fix 2660592 = enabled

Query Block Registry:


SEL$1 0xb7e0905c (PARSER) [FINAL]
:
call(in-use=143844, alloc=246544), compile(in-use=166732,
alloc=224788), execution(in-use=13796, alloc=16288)
End of Optimizer State Dump
====================== END SQL Statement Dump ======================
[oracle@edrsr33p1-orcl Trace_Event]$

3) Execute the te_cleanup.sh script to clean up your environment for this lab.
[oracle@edrsr33p1-orcl Trace_Event]$ ./te_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 9 22:18:01 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Trace_Event]$

Oracle Database 11g: SQL Tuning Workshop A - 56

Practices for Lesson 4

Oracle Database 11g: SQL Tuning Workshop A - 57

Practice 4-1: Using Different Access Paths


In this practice, you explore various access paths the optimizer can use, and compare
them. You have the possibility of exploring 16 different scenarios, each of which are selfcontained. All scripts needed for this lab can be found in your
$HOME/solutions/Access_Paths directory.
1) Case 1: Connected as the oracle user from a terminal session, execute the
ap_setup.sh script.
[oracle@edrsr33p1-orcl Access_Paths]$ ./ap_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 9 14:24:51 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> alter user sh identified by sh account unlock;
User altered.
SQL>
SQL> grant dba to sh;
Grant succeeded.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Access_Paths]$

2) In the same terminal session, connect as the SH user in the SQL*Plus session.
[oracle@edrsr33p1-orcl Access_Paths]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 9 14:24:58 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 58

Practice 4-1: Using Different Access Paths (continued)


3) Unless otherwise notified, stay connected to your SQL*Plus session as the SH user.
Execute the idx_setup.sql script to set up your environment for case 1.
SQL> @idx_setup
SQL>
SQL> drop table mysales purge;
drop table mysales purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table mysales as select * from sh.sales;
Table created.
SQL>
SQL> insert into mysales select * from mysales;
918843 rows created.
SQL> commit;
Commit complete.
SQL>
SQL>
SQL> insert into mysales select * from mysales;
1837686 rows created.
SQL> commit;
Commit complete.
SQL>
SQL> insert into mysales select * from mysales;
3675372 rows created.
SQL> commit;
Commit complete.
SQL>
SQL> insert into mysales select * from mysales;
7350744 rows created.
SQL> commit;
Commit complete.
SQL>
SQL> insert into mysales select * from mysales;

Oracle Database 11g: SQL Tuning Workshop A - 59

Practice 4-1: Using Different Access Paths (continued)


14701488 rows created.
SQL> commit;
Commit complete.
SQL>
SQL> insert into mysales values (0,0,sysdate,0,0,0,0);
1 row created.
SQL> commit;
Commit complete.
SQL>
SQL> exec dbms_stats.gather_schema_stats('SH');
PL/SQL procedure successfully completed.
SQL>

4) Set up your session with the following commands: set timing on, set
autotrace trace only, and set linesize 200. After this, execute the
select * from mysales where prod_id=0; query. What do you observe?
a) Basically, there are no indexes on the MYSALES table.
b) .The only possibility for the optimizer is to use the full table scan to retrieve only
one row. You can see that the scan takes a long time.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@with_and_without_index
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
alter system flush shared_pool;

System altered.
Elapsed: 00:00:00.61
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:15.04
SQL>
SQL> select * from mysales where prod_id=0;
Elapsed: 00:00:28.85
Execution Plan
---------------------------------------------------------Plan hash value: 3597614299

Oracle Database 11g: SQL Tuning Workshop A - 60

Practice 4-1: Using Different Access Paths (continued)


---------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
---------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
402K|
11M| 40249
(2)|
00:08:03 |
|* 1 | TABLE ACCESS FULL| MYSALES |
402K|
11M| 40249
(2)|
00:08:03 |
---------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("PROD_ID"=0)

Statistics
---------------------------------------------------------421 recursive calls
0 db block gets
141606 consistent gets
141529 physical reads
0 redo size
790 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

5) How do you enhance the performance of the query in step 4? Implement your
solution.
SQL> @create_mysales_index
SQL> set echo on
SQL>
SQL> create index mysales_prodid_idx on mysales(prod_id) nologging
compute statistics;
Index created.
SQL>

6) Execute the same query again and verify that performance is enhanced now.
a) After implementing the index, the optimizer can use it to speed up the query
execution time. You can see a dramatic improvement in performance.
SQL> @with_and_without_index
SQL> set echo on

Oracle Database 11g: SQL Tuning Workshop A - 61

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
alter system flush shared_pool;

System altered.
Elapsed: 00:00:00.29
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:01.71
SQL>
SQL> select * from mysales where prod_id=0;
Elapsed: 00:00:00.88
Execution Plan
---------------------------------------------------------Plan hash value: 3009203711
------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
402K|
11M| 6079
(1)| 00:01:13 |
|
1 | TABLE ACCESS BY INDEX ROWID| MYSALES
|
402K|
11M| 6079
(1)| 00:01:13 |
|* 2 |
INDEX RANGE SCAN
| MYSALES_PRODID_IDX |
402K|
|
822
(1)| 00:00:10 |
------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("PROD_ID"=0)

Statistics
---------------------------------------------------------496 recursive calls
0 db block gets
104 consistent gets
21 physical reads
0 redo size
794 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
6 sorts (memory)
0 sorts (disk)

Oracle Database 11g: SQL Tuning Workshop A - 62

Practice 4-1: Using Different Access Paths (continued)


1

rows processed

SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

7) Clean up your environment for case 1 by executing the idx_cleanup.sql script.


SQL> @idx_cleanup
SQL> set echo on
SQL>
SQL> drop table mysales purge;
Table dropped.
SQL>
SQL>

8) Case 2: Drop all indexes currently created on the CUSTOMERS table except its
primary key index.
SQL> @drop_customers_indexes
SQL> set
termout off
SQL>
-------------------------------------------------------------set
store
save
set

termout off
set sqlplus_settings replace
buffer.sql replace
timing off heading off verify off autotrace off feedback off

spool

dait.sql

SELECT 'drop index '||i.index_name||';'


FROM
user_indexes i
WHERE i.table_name = 'CUSTOMERS'
AND
NOT EXISTS
(SELECT 'x'
FROM
user_constraints c
WHERE c.index_name = i.index_name
AND
c.table_name = i.table_name
AND
c.status = 'ENABLED');
spool
@dait

off

get
buffer.sql nolist
@sqlplus_settings
set
termout on

Oracle Database 11g: SQL Tuning Workshop A - 63

Practice 4-1: Using Different Access Paths (continued)


9) Execute the following query:
SELECT /*+ FULL(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) The optimizer uses a full table scan and the cost for this query is relatively high.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6

@query00
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
SELECT /*+ FULL(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.
Elapsed: 00:00:00.29
Execution Plan
---------------------------------------------------------Plan hash value: 2008213504
-----------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
-----------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
6 | 1080 |
407
(1)|
00:00:05 |
|* 1 | TABLE ACCESS FULL| CUSTOMERS |
6 | 1080 |
407
(1)|
00:00:05 |
-----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')

Statistics
---------------------------------------------------------1067 recursive calls
0 db block gets
1722 consistent gets

Oracle Database 11g: SQL Tuning Workshop A - 64

Practice 4-1: Using Different Access Paths (continued)


1461
116
2570
420
2
27
0
6

physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

10) Create three B*-tree indexes on the following CUSTOMERS table columns:
cust_gender
cust_postal_code
cust_credit_limit
SQL>
SQL>
SQL>
SQL>
2
3

@create_cust_gender_index
set echo on
CREATE INDEX cust_cust_gender_idx
ON customers(cust_gender)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@create_cust_postal_code_index
set echo on
CREATE INDEX cust_cust_postal_code_idx
ON customers(cust_postal_code)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@create_cust_credit_limit_index
set echo on
CREATE INDEX cust_cust_credit_limit_idx
ON customers(cust_credit_limit)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@list_customers_indexes
set echo on
set linesize 200 pagesize 1000
SELECT
,

ui.table_name
decode(ui.index_type
,'NORMAL', ui.uniqueness
,ui.index_type) AS index_type

Oracle Database 11g: SQL Tuning Workshop A - 65

Practice 4-1: Using Different Access Paths (continued)


5
6
7
8
9

,
ui.index_name
FROM
user_indexes ui
WHERE
ui.table_name = 'CUSTOMERS'
ORDER BY ui.table_name
,
ui.uniqueness desc;

TABLE_NAME
INDEX_NAME
-------------------------------------------------CUSTOMERS
CUSTOMERS_PK
CUSTOMERS
CUST_CUST_GENDER_IDX
CUSTOMERS
CUST_CUST_CREDIT_LIMIT_IDX
CUSTOMERS
CUST_CUST_POSTAL_CODE_IDX

INDEX_TYPE
--------------------------- --------UNIQUE
NONUNIQUE
NONUNIQUE
NONUNIQUE

SQL>

11) Start monitoring all the CUSTOMERS indexes.


SQL> @start_monitoring_indexes
SQL> set echo on
SQL>
SQL> ALTER INDEX CUSTOMERS_PK MONITORING USAGE;
Index altered.
SQL>
SQL> ALTER INDEX CUST_CUST_POSTAL_CODE_IDX MONITORING USAGE;
Index altered.
SQL>
SQL> ALTER INDEX CUST_CUST_GENDER_IDX MONITORING USAGE;
Index altered.
SQL>
SQL> ALTER INDEX CUST_CUST_CREDIT_LIMIT_IDX MONITORING USAGE;
Index altered.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@show_index_usage
set echo on
set linesize 200
select * from v$object_usage;

INDEX_NAME
TABLE_NAME
MON
USE START_MONITORING
END_MONITORING
------------------------------ ------------------------------ --- -- ------------------- -------------------

Oracle Database 11g: SQL Tuning Workshop A - 66

Practice 4-1: Using Different Access Paths (continued)


CUSTOMERS_PK
04/09/2008 14:52:43
CUST_CUST_POSTAL_CODE_IDX
04/09/2008 14:52:43
CUST_CUST_GENDER_IDX
04/09/2008 14:52:43
CUST_CUST_CREDIT_LIMIT_IDX
04/09/2008 14:52:43

CUSTOMERS

YES NO

CUSTOMERS

YES NO

CUSTOMERS

YES NO

CUSTOMERS

YES NO

SQL>

12) Execute the following query:


SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) The optimizer chooses to use only one index to do a fast full scan. The cost is
lower than the full table scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6

@query01
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.
Elapsed: 00:00:00.04
Execution Plan
---------------------------------------------------------Plan hash value: 1928091631
-------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 2 |
INDEX FULL SCAN
| CUST_CUST_POSTAL_CODE_IDX |
89 |
|
134
(1)| 00:00:02 |

Oracle Database 11g: SQL Tuning Workshop A - 67

Practice 4-1: Using Different Access Paths (continued)


-------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')
2 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------395 recursive calls
3 db block gets
384 consistent gets
132 physical reads
604 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
6 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

13) Execute the following query:


SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) This time the optimizer uses multiple indexes and combines them to access the
table. However, the cost is higher than that from the previous step as well as the
full table scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6

@query02
set echo on
set linesize 200 pagesize 1000
set timing on
set autotrace traceonly
SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.

Oracle Database 11g: SQL Tuning Workshop A - 68

Practice 4-1: Using Different Access Paths (continued)


Elapsed: 00:00:00.06
Execution Plan
---------------------------------------------------------Plan hash value: 4093665856
-------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
466
(1)| 00:00:06 |
|* 1 | TABLE ACCESS BY INDEX ROWID
| CUSTOMERS
|
6 | 1080 |
466
(1)| 00:00:06 |
|
2 |
BITMAP CONVERSION TO ROWIDS
|
|
|
|
|
|
|
3 |
BITMAP AND
|
|
|
|
|
|
|
4 |
BITMAP CONVERSION FROM ROWIDS|
|
|
|
|
|
|* 5 |
INDEX RANGE SCAN
|
CUST_CUST_CREDIT_LIMIT_IDX |
|
|
14
(0)| 00:00:01 |
|
6 |
BITMAP CONVERSION FROM ROWIDS|
|
|
|
|
|
|* 7 |
INDEX RANGE SCAN
| CUST_CUST_GENDER_IDX
|
|
|
51
(0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)
5 - access("CUST_CREDIT_LIMIT"=10000)
7 - access("CUST_GENDER"='M')

Statistics
---------------------------------------------------------3 recursive calls
7 db block gets
894 consistent gets
81 physical reads
1020 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 69

Practice 4-1: Using Different Access Paths (continued)


14) Confirm the list of indexes that were accessed in this case.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@show_index_usage
set echo on
set linesize 200
select * from v$object_usage;

INDEX_NAME
TABLE_NAME
USE START_MONITORING
END_MONITORING
------------------------------ ------------------------------ ------------------- ------------------CUSTOMERS_PK
CUSTOMERS
04/09/2008 14:52:43
CUST_CUST_POSTAL_CODE_IDX
CUSTOMERS
YES 04/09/2008 14:52:43
CUST_CUST_GENDER_IDX
CUSTOMERS
YES 04/09/2008 14:52:43
CUST_CUST_CREDIT_LIMIT_IDX
CUSTOMERS
YES 04/09/2008 14:52:43

MON
--- -YES NO
YES
YES
YES

SQL>
SQL>

15) Case 3: Drop all the CUSTOMERS indexes except its primary key index. After this,
make sure you create a concatenated index on the following CUSTOMERS columns,
and in the order mentioned here:
cust_gender
cust_credit_limit
cust_postal_code
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_gender_limit_code_index
set echo on
CREATE INDEX cust_gender_limit_code_idx
ON customers(cust_gender,cust_credit_limit,cust_postal_code)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>

16) Execute the following query:


SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) The optimizer uses your concatenated index, and the resulting cost is by far the
best compared to the previous steps.
Oracle Database 11g: SQL Tuning Workshop A - 70

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6

@query01
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.
Elapsed: 00:00:00.01
Execution Plan
---------------------------------------------------------Plan hash value: 2871279522
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
7 | 1260 |
18
(0)| 00:00:01 |
|
1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
7 | 1260 |
18
(0)| 00:00:01 |
|* 2 |
INDEX RANGE SCAN
| CUST_GENDER_LIMIT_CODE_IDX |
6 |
|
12
(0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("CUST_GENDER"='M' AND "CUST_CREDIT_LIMIT"=10000)
filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
22 consistent gets
14 physical reads
0 redo size
2981 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed

Oracle Database 11g: SQL Tuning Workshop A - 71

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

17) Execute the following query:


SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_credit_limit = 10000;

What do you observe?


a) The query is almost the same as in the previous step, but the predicate is removed.
The optimizer can still use the concatenated index, but the resulting cost is much
higher because cust_credit_limit is not very selective.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@query03
set echo on
set linesize 200 pagesize 1000
set timing on
set autotrace traceonly
SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_credit_limit = 10000
/

4101 rows selected.


Elapsed: 00:00:00.08
Execution Plan
---------------------------------------------------------Plan hash value: 2871279522
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3469 |
609K| 3454
(1)| 00:00:42 |
|
1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
3469 |
609K| 3454
(1)| 00:00:42 |
|* 2 |
INDEX RANGE SCAN
| CUST_GENDER_LIMIT_CODE_IDX |
3469 |
|
12
(0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 72

Practice 4-1: Using Different Access Paths (continued)


2 - access("CUST_GENDER"='M' AND "CUST_CREDIT_LIMIT"=10000)
Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
4391 consistent gets
0 physical reads
0 redo size
795172 bytes sent via SQL*Net to client
3423 bytes received via SQL*Net from client
275 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4101 rows processed
SQL>
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

18) Execute the following query:


SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender = 'M'
AND
cust_postal_code = 40804;

What do you observe?


a) You replaced cust_credit_limit with cust_postal_code, which has
better selectivity. The index is used and the resulting cost is better.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@query04
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000
SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_gender = 'M'
AND
cust_postal_code = 40804
/

75 rows selected.
Elapsed: 00:00:00.02
Execution Plan
---------------------------------------------------------Plan hash value: 2871279522
---------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 73

Practice 4-1: Using Different Access Paths (continued)


| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
45 | 8100 |
133
(1)| 00:00:02 |
|
1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
45 | 8100 |
133
(1)| 00:00:02 |
|* 2 |
INDEX RANGE SCAN
| CUST_GENDER_LIMIT_CODE_IDX |
45 |
|
87
(0)| 00:00:02 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("CUST_GENDER"='M')
filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
196 consistent gets
101 physical reads
0 redo size
16237 bytes sent via SQL*Net to client
464 bytes received via SQL*Net from client
6 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
75 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

19) Execute the following query:


SELECT /*+ INDEX(c) */ c.*
FROM
customers c
WHERE cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) The leading part of the concatenated index is no longer part of the query.
However, the optimizer is still able to use the index by doing a fast full index
scan. Nevertheless, the resulting cost is not the best.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query05
set echo on
set timing on
set autotrace traceonly
set linesize 200 pagesize 1000

Oracle Database 11g: SQL Tuning Workshop A - 74

Practice 4-1: Using Different Access Paths (continued)


SQL>
2
3
4
5

SELECT
FROM
WHERE
AND
/

/*+ INDEX(c) */ c.*


customers c
cust_postal_code = 40804
cust_credit_limit = 10000

15 rows selected.
Elapsed: 00:00:00.02
Execution Plan
---------------------------------------------------------Plan hash value: 2438361736
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
11 | 1980 |
185
(1)| 00:00:03 |
|
1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
11 | 1980 |
185
(1)| 00:00:03 |
|* 2 |
INDEX FULL SCAN
| CUST_GENDER_LIMIT_CODE_IDX |
11 |
|
173
(1)| 00:00:03 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("CUST_CREDIT_LIMIT"=10000)
filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_CREDIT_LIMIT"=10000)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
189 consistent gets
56 physical reads
0 redo size
4617 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
15 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 75

Practice 4-1: Using Different Access Paths (continued)


20) Case 4: Drop all the CUSTOMERS indexes except its primary key index. After this,
create three different bitmap indexes on the following columns of the CUSTOMERS
table:
cust_gender
cust_postal_code
cust_credit_limit
SQL> @drop_customers_indexes
SQL> set
termout off
SQL>
SQL> @create_cust_gender_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_gender_bidx ON
customers(cust_gender)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL> @create_cust_postal_code_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_postal_code_bidx ON
customers(cust_postal_code)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL> @create_cust_credit_limit_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_credit_limit_bidx ON
customers(cust_credit_limit)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9

@list_customers_indexes
set echo on
set linesize 200 pagesize 1000
SELECT
,

ui.table_name
decode(ui.index_type
,'NORMAL', ui.uniqueness
,ui.index_type) AS index_type
,
ui.index_name
FROM
user_indexes ui
WHERE
ui.table_name = 'CUSTOMERS'
ORDER BY ui.table_name
,
ui.uniqueness desc;

TABLE_NAME
INDEX_NAME

INDEX_TYPE

Oracle Database 11g: SQL Tuning Workshop A - 76

Practice 4-1: Using Different Access Paths (continued)


-------------------------------------------------CUSTOMERS
CUSTOMERS_PK
CUSTOMERS
CUST_CUST_GENDER_BIDX
CUSTOMERS
CUST_CUST_CREDIT_LIMIT_BIDX
CUSTOMERS
CUST_CUST_POSTAL_CODE_BIDX

--------------------------- --------UNIQUE
BITMAP
BITMAP
BITMAP

SQL>

21) Execute the following query:


SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;

What do you observe?


a) The optimizer uses only two bitmap indexes to solve this query. However, the
cost is not a good one. It is a little lesser than cost of the full table scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6

@query02
set echo on
set linesize 200 pagesize 1000
set timing on
set autotrace traceonly
SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.
Elapsed: 00:00:00.01
Execution Plan
---------------------------------------------------------Plan hash value: 3047829365
----------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
----------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
402
(1)| 00:00:05 |
|* 1 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS
|
6 | 1080 |
402
(1)| 00:00:05 |

Oracle Database 11g: SQL Tuning Workshop A - 77

Practice 4-1: Using Different Access Paths (continued)


|
2 |
BITMAP CONVERSION TO ROWIDS|
|
|
|
|
|
|
3 |
BITMAP AND
|
|
|
|
|
|
|* 4 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_CREDIT_LIMIT_BIDX |
|
|
|
|
|* 5 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_GENDER_BIDX
|
|
|
|
|
----------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)
4 - access("CUST_CREDIT_LIMIT"=10000)
5 - access("CUST_GENDER"='M')

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
813 consistent gets
5 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

22) Case 5: Drop all the CUSTOMERS indexes except its primary key index. After this,
create two bitmap indexes on the following columns of the CUSTOMERS table:
cust_year_of_birth
cust_credit_limit
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_cust_year_of_birth_bindex
set echo on
CREATE BITMAP INDEX cust_cust_year_of_birth_bidx
ON customers(cust_year_of_birth)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 78

Practice 4-1: Using Different Access Paths (continued)


SQL> @create_cust_credit_limit_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_credit_limit_bidx ON
customers(cust_credit_limit)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>

23) Execute the following query:


SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE c.cust_year_of_birth
= 1953
OR
c.cust_credit_limit = 10000;

What do you observe?


a) The query uses an OR construct. The optimizer can use both bitmap indexes.
However, this resulting cost is not the best.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@query06
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT /*+ INDEX_COMBINE(c) */ c.*
FROM
customers c
WHERE c.cust_year_of_birth
= 1953
OR
c.cust_credit_limit = 10000
/

7158 rows selected.


Elapsed: 00:00:00.12
Execution Plan
---------------------------------------------------------Plan hash value: 1912490408
-----------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
|
-----------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 7585 | 1333K|
581
(1)| 00:00:07 |
|
1 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS
| 7585 | 1333K|
581
(1)| 00:00:07 |
|
2 |
BITMAP CONVERSION TO ROWIDS|
|
|
|
|
|
|
3 |
BITMAP OR
|
|
|
|
|
|

Oracle Database 11g: SQL Tuning Workshop A - 79

Practice 4-1: Using Different Access Paths (continued)


|* 4 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_CREDIT_LIMIT_BIDX
|
|
|
|
|
|* 5 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_YEAR_OF_BIRTH_BIDX
|
|
|
|
|
-----------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------4 - access("C"."CUST_CREDIT_LIMIT"=10000)
5 - access("C"."CUST_YEAR_OF_BIRTH"=1953)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
1683 consistent gets
3 physical reads
0 redo size
1391886 bytes sent via SQL*Net to client
5667 bytes received via SQL*Net from client
479 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
7158 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>
SQL>

24) Case 6: Drop all the CUSTOMERS indexes except its primary key index. After this,
create three different bitmap indexes on the following columns of the CUSTOMERS
table:
cust_year_of_birth
cust_credit_limit
cust_postal_code
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_cust_year_of_birth_bindex
set echo on
CREATE BITMAP INDEX cust_cust_year_of_birth_bidx
ON customers(cust_year_of_birth)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL> @create_cust_credit_limit_bindex
SQL> set echo on

Oracle Database 11g: SQL Tuning Workshop A - 80

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL> CREATE BITMAP INDEX cust_cust_credit_limit_bidx ON
customers(cust_credit_limit)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL> @create_cust_postal_code_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_postal_code_bidx ON
customers(cust_postal_code)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>

25) Execute the following query:


SELECT
c.*
FROM customers c
WHERE (c.cust_year_of_birth = '1970' And c.cust_postal_code
40804 )
AND NOT cust_credit_limit = 15000;

What do you observe?


a) The query has a complex WHERE clause that is well suited for using bitmap
indexes. The optimizer uses two bitmap indexes and the resulting cost is better
than the full table scan cost.
SQL> @query07
SQL> set echo on
SQL>
SQL> set timing on
SQL> set autotrace traceonly
SQL>
SQL> SELECT
c.*
2 FROM customers c
3 WHERE (c.cust_year_of_birth = '1970' And c.cust_postal_code
40804 )
4 AND NOT cust_credit_limit = 15000
5 /

Elapsed: 00:00:00.03
Execution Plan
---------------------------------------------------------Plan hash value: 576122600
------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 81

Practice 4-1: Using Different Access Paths (continued)


|
0 | SELECT STATEMENT
|
|
1 |
180 |
133
(0)| 00:00:02 |
|* 1 | TABLE ACCESS BY INDEX ROWID | CUSTOMERS
|
1 |
180 |
133
(0)| 00:00:02 |
|
2 |
BITMAP CONVERSION TO ROWIDS |
|
|
|
|
|
|
3 |
BITMAP MINUS
|
|
|
|
|
|
|
4 |
BITMAP MINUS
|
|
|
|
|
|
|* 5 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_YEAR_OF_BIRTH_BIDX
|
|
|
|
|
|* 6 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_CREDIT_LIMIT_BIDX
|
|
|
|
|
|* 7 |
BITMAP INDEX SINGLE VALUE | CUST_CUST_CREDIT_LIMIT_BIDX
|
|
|
|
|
------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1
5
6
7

filter(TO_NUMBER("C"."CUST_POSTAL_CODE")=40804)
access("C"."CUST_YEAR_OF_BIRTH"=1970)
access("CUST_CREDIT_LIMIT"=15000)
access("CUST_CREDIT_LIMIT" IS NULL)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
773 consistent gets
3 physical reads
0 redo size
2024 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

26) Make sure the optimizer can no longer use the bitmap index you created on the
cust_year_of_birth column.
a) The best solution is to render it invisible.
SQL> show parameter optimizer_use_invisible_indexes
NAME
TYPE
VALUE
------------------------------------ ----------- -----------------------------

Oracle Database 11g: SQL Tuning Workshop A - 82

Practice 4-1: Using Different Access Paths (continued)


optimizer_use_invisible_indexes
boolean
FALSE
SQL> alter index cust_cust_year_of_birth_bidx invisible;
Index altered.
SQL> select index_name, visibility from user_indexes where
table_owner='SH' and table_name='CUSTOMERS';
INDEX_NAME
-----------------------------CUST_CUST_CREDIT_LIMIT_BIDX
CUST_CUST_YEAR_OF_BIRTH_BIDX
CUST_CUST_POSTAL_CODE_BIDX
CUSTOMERS_PK

VISIBILIT
--------VISIBLE
INVISIBLE
VISIBLE
VISIBLE

4 rows selected.
SQL>

27) Execute the following query:


SELECT
c.*
FROM customers c
WHERE (c.cust_year_of_birth = '1970' And c.cust_postal_code
40804 )
AND NOT cust_credit_limit = 15000;

What do you observe?


a) This is the same query as in the previous step. However, the optimizer can no
longer find a good plan that uses bitmap indexes.
SQL> @query07
SQL> set echo on
SQL>
SQL> set timing on
SQL> set autotrace traceonly
SQL>
SQL> SELECT
c.*
2 FROM customers c
3 WHERE (c.cust_year_of_birth = '1970' And c.cust_postal_code
40804 )
4 AND NOT cust_credit_limit = 15000
5 /

Elapsed: 00:00:00.03
Execution Plan
---------------------------------------------------------Plan hash value: 2008213504
-----------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
-----------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
1 |
180 |
407
(1)|
00:00:05 |

Oracle Database 11g: SQL Tuning Workshop A - 83

Practice 4-1: Using Different Access Paths (continued)


|* 1 | TABLE ACCESS FULL| CUSTOMERS |
1 |
180 |
407
(1)|
00:00:05 |
-----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("C"."CUST_POSTAL_CODE")=40804 AND
"C"."CUST_YEAR_OF_BIRTH"=1970 AND
"CUST_CREDIT_LIMIT"<>15000)

Statistics
---------------------------------------------------------393 recursive calls
0 db block gets
1590 consistent gets
0 physical reads
0 redo size
2024 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
6 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

28) Case 7: Execute the following query:


SELECT c.*
FROM
customers c
WHERE cust_id IN (88340,104590,44910);

What do you observe?


a) The optimizer can use the CUSTOMERS primary key index to solve this query.
The cost is very low for the resulting plan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query08
set echo on
set timing on
set linesize 200
set autotrace traceonly
SELECT c.*
FROM
customers c
WHERE cust_id IN (88340,104590,44910)
/

Elapsed: 00:00:00.07
Execution Plan
----------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 84

Practice 4-1: Using Different Access Paths (continued)


Plan hash value: 293792914
-------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes
| Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3 |
540
|
7
(0)| 00:00:01 |
|
1 | INLIST ITERATOR
|
|
|
|
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
3 |
540
|
7
(0)| 00:00:01 |
|* 3 |
INDEX UNIQUE SCAN
| CUSTOMERS_PK |
3 |
|
4
(0)| 00:00:01 |
-------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("CUST_ID"=44910 OR "CUST_ID"=88340 OR
"CUST_ID"=104590)

Statistics
---------------------------------------------------------2 recursive calls
3 db block gets
9 consistent gets
4 physical reads
560 redo size
2022 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

29) Case 8: Drop all the indexes on the CUSTOMERS table except its primary key index.
After this, create a concatenated B*-tree index on the following columns of the
CUSTOMERS table, and in the order here:
cust_last_name
cust_first_name
SQL>
SQL>
SQL>
SQL>
SQL>

@drop_customers_indexes
set
termout off
@create_last_first_name_index
set echo on

Oracle Database 11g: SQL Tuning Workshop A - 85

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL> CREATE INDEX cust_last_first_name_idx
2 ON customers(cust_last_name,cust_first_name)
3 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>

30) Execute the following query:


SELECT c.cust_last_name
,
c.cust_first_name
FROM
customers c;

What do you observe?


a) The optimizer can use only the index without accessing the table itself. The
resulting cost is very good.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query09
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT c.cust_last_name
,
c.cust_first_name
FROM
customers c
/

55500 rows selected.


Elapsed: 00:00:00.19
Execution Plan
---------------------------------------------------------Plan hash value: 445338993
-----------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
-----------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 55500 |
812K|
55
(2)| 00:00:01 |
|
1 | INDEX FAST FULL SCAN| CUST_LAST_FIRST_NAME_IDX | 55500 |
812K|
55
(2)| 00:00:01 |
------------------------------------------------------------------------------------------------

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
3886 consistent gets

Oracle Database 11g: SQL Tuning Workshop A - 86

Practice 4-1: Using Different Access Paths (continued)


193
0
792248
41109
3701
0
0
55500

physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

31) Case 9: Drop all the indexes on the CUSTOMERS table except its primary key index.
After this, create two B*-tree indexes on the following columns of the CUSTOMERS
table:
cust_last_name
cust_first_name
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_last_name_index
set echo on
CREATE INDEX cust_cust_last_name_idx
ON customers(cust_last_name)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@create_first_name_index
set echo on
CREATE INDEX cust_cust_first_name_idx
ON customers(cust_first_name)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>

32) Execute the following query:


SELECT /*+ INDEX_JOIN(c cust_cust_first_name_idx
cust_cust_last_name_idx) */ c.cust_last_name
,
c.cust_first_name
FROM
customers c;

What do you observe?


a) Although the optimizer can use both the indexes, the resulting cost is not better
than the concatenated index case.

Oracle Database 11g: SQL Tuning Workshop A - 87

Practice 4-1: Using Different Access Paths (continued)


SQL> @query10
SQL> set echo on
SQL>
SQL> set linesize 200
SQL> set timing on
SQL> set autotrace traceonly
SQL>
SQL> SELECT /*+ INDEX_JOIN(c cust_cust_first_name_idx
cust_cust_last_name_idx) */ c.cust_last_name
2 ,
c.cust_first_name
3 FROM
customers c
4 /
55500 rows selected.
Elapsed: 00:00:00.27
Execution Plan
---------------------------------------------------------Plan hash value: 3557918892
-------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 55500 |
812K|
511
(1)| 00:00:07 |
|
1 | VIEW
| index$_join$_001
| 55500 |
812K|
511
(1)| 00:00:07 |
|* 2 |
HASH JOIN
|
|
|
|
|
|
|
3 |
INDEX FAST FULL SCAN| CUST_CUST_FIRST_NAME_IDX | 55500 |
812K|
175
(1)| 00:00:03 |
|
4 |
INDEX FAST FULL SCAN| CUST_CUST_LAST_NAME_IDX | 55500 |
812K|
178
(0)| 00:00:03 |
-------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access(ROWID=ROWID)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
3985 consistent gets
279 physical reads
0 redo size
811440 bytes sent via SQL*Net to client
41109 bytes received via SQL*Net from client
3701 SQL*Net roundtrips to/from client
0 sorts (memory)

Oracle Database 11g: SQL Tuning Workshop A - 88

Practice 4-1: Using Different Access Paths (continued)


0
55500

sorts (disk)
rows processed

SQL>
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

33) CASE 10: Drop all the indexes on the CUSTOMERS table except its primary key
index. Then, create one bitmap index on the following column of the CUSTOMERS
table:
cust_credit_limit
SQL> @drop_customers_indexes
SQL> set
termout off
SQL>
SQL> @create_cust_credit_limit_bindex
SQL> set echo on
SQL>
SQL> CREATE BITMAP INDEX cust_cust_credit_limit_bidx ON
customers(cust_credit_limit)
2 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>

34) Execute the following query:


SELECT count(*) credit_limit
FROM
customers
WHERE cust_credit_limit = 10000;

What do you observe?


a) Although cust_credit_limit is not a selective column, the COUNT
operation on its bitmap index is very efficient.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query11
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT count(*) credit_limit
FROM
customers
WHERE cust_credit_limit = 10000
/

Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 37937133

Oracle Database 11g: SQL Tuning Workshop A - 89

Practice 4-1: Using Different Access Paths (continued)


---------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
4 |
1
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
4 |
|
|
|
2 |
BITMAP CONVERSION COUNT
|
|
6938 | 27752 |
1
(0)| 00:00:01 |
|* 3 |
BITMAP INDEX SINGLE VALUE| CUST_CUST_CREDIT_LIMIT_BIDX |
|
|
|
|
---------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("CUST_CREDIT_LIMIT"=10000)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
3 consistent gets
2 physical reads
0 redo size
423 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

35) Case 11: Drop all the CUSTOMERS indexes except its primary key index. After this,
create one B*-tree index on the following column of the CUSTOMERS table:
cust_credit_limit
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_cust_credit_limit_index
set echo on
CREATE INDEX cust_cust_credit_limit_idx
ON customers(cust_credit_limit)
NOLOGGING COMPUTE STATISTICS;

Oracle Database 11g: SQL Tuning Workshop A - 90

Practice 4-1: Using Different Access Paths (continued)


Index created.
SQL>

36) Execute the following query:


SELECT count(*) credit_limit
FROM
customers
WHERE cust_credit_limit = 10000;

What do you observe?


a) The optimizer can still use the index; however, this is less efficient compared to
the corresponding bitmap index from the previous case.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query11
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT count(*) credit_limit
FROM
customers
WHERE cust_credit_limit = 10000
/

Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 3421880850
----------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
----------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
1 |
4 |
14
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
4 |
|
|
|* 2 |
INDEX RANGE SCAN| CUST_CUST_CREDIT_LIMIT_IDX | 6938 |
27752 |
14
(0)| 00:00:01 |
----------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("CUST_CREDIT_LIMIT"=10000)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets

Oracle Database 11g: SQL Tuning Workshop A - 91

Practice 4-1: Using Different Access Paths (continued)


14
13
0
423
420
2
0
0
1

consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

37) Case 12: Drop all the indexes on the CUSTOMERS table except its primary key index.
After this, create one B*-tree index on the following column of the CUSTOMERS
table:
cust_last_name
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@drop_customers_indexes
set
termout off
@create_last_name_index
set echo on
CREATE INDEX cust_cust_last_name_idx
ON customers(cust_last_name)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>

38) Execute the following query:


SELECT cust_id, country_id
FROM customers
WHERE LOWER( cust_last_name) LIKE 'gentle';

What do you observe?


a) Although there is an index, it cannot be used because its column is modified by a
function.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query12
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT cust_id, country_id
FROM customers
WHERE LOWER( cust_last_name) LIKE 'gentle'
/

Oracle Database 11g: SQL Tuning Workshop A - 92

Practice 4-1: Using Different Access Paths (continued)


80 rows selected.
Elapsed: 00:00:00.01
Execution Plan
---------------------------------------------------------Plan hash value: 2008213504
-----------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
-----------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
555 | 9990 |
406
(1)|
00:00:05 |
|* 1 | TABLE ACCESS FULL| CUSTOMERS |
555 | 9990 |
406
(1)|
00:00:05 |
-----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(LOWER("CUST_LAST_NAME")='gentle')

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
1464 consistent gets
0 physical reads
0 redo size
2077 bytes sent via SQL*Net to client
475 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
80 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

39) How can you enhance the performance of the previous query without modifying the
statement itself? Implement your solution.
a) You can create a function-based index.
SQL>
SQL>
SQL>
SQL>
2
3

@create_lower_cust_last_name_index
set echo on
CREATE INDEX lower_cust_last_name_idx ON
customers(LOWER(cust_last_name))
/

Oracle Database 11g: SQL Tuning Workshop A - 93

Practice 4-1: Using Different Access Paths (continued)


Index created.
SQL>

40) Check if your solution executes faster than in the case of the query in step 38.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

@query12
set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT cust_id, country_id
FROM customers
WHERE LOWER( cust_last_name) LIKE 'gentle'
/

80 rows selected.
Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 967065894
------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
555 | 17760 |
41
(0)| 00:00:01 |
|
1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
555 | 17760 |
41
(0)| 00:00:01 |
|* 2 |
INDEX RANGE SCAN
| LOWER_CUST_LAST_NAME_IDX |
222 |
|
1
(0)| 00:00:01 |
------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access(LOWER("CUST_LAST_NAME")='gentle')

Statistics
---------------------------------------------------------24 recursive calls
0 db block gets
23 consistent gets
1 physical reads
0 redo size
2077 bytes sent via SQL*Net to client
475 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client

Oracle Database 11g: SQL Tuning Workshop A - 94

Practice 4-1: Using Different Access Paths (continued)


0
0
80

sorts (memory)
sorts (disk)
rows processed

SQL>
SQL> set autotrace off
SQL> set timing off
SQL>
SQL>

41) Case 13: Execute the iot_setup.sql script to set up the environment for this
case.
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10

@iot_setup
set echo on
CREATE table promotions_iot
(promo_id number primary key
, promo_name VARCHAR2(40)
, promo_subcategory VARCHAR2 (30)
, promo_category VARCHAR2 (30)
, promo_cost NUMBER
, promo_begin_date DATE
, promo_end_date DATE)
ORGANIZATION INDEX
/

Table created.
SQL>
SQL> INSERT INTO promotions_iot
2 SELECT promo_id, promo_name, promo_subcategory, promo_category,
promo_cost, promo_begin_date, promo_end_date
3 FROM promotions
4 /
503 rows created.
SQL>

42) Execute the following two queries:


SELECT *
FROM promotions
WHERE promo_id > 300;
SELECT /*+ INDEX(promotions) */ *
FROM promotions
WHERE promo_id > 300;

What do you observe?


a) The first lets the optimizer decide the plan, and the best it can find is to do a full
table scan. Forcing the use of the index is not a good idea, as it takes longer to
execute.
SQL> @query13
SQL> set echo on
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 95

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

set linesize 200


set timing on
set autotrace traceonly
SELECT *
FROM promotions
WHERE promo_id > 300
/

235 rows selected.


Elapsed: 00:00:00.02
Execution Plan
---------------------------------------------------------Plan hash value: 4106015420
------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
364 | 35308 |
17
(0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| PROMOTIONS |
364 | 35308 |
17
(0)| 00:00:01 |
------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("PROMO_ID">300)

Statistics
---------------------------------------------------------123 recursive calls
0 db block gets
88 consistent gets
3 physical reads
0 redo size
21829 bytes sent via SQL*Net to client
585 bytes received via SQL*Net from client
17 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
235 rows processed
SQL>
SQL>
2
3
4

SELECT /*+ INDEX(promotions) */ *


FROM promotions
WHERE promo_id > 300
/

235 rows selected.

Oracle Database 11g: SQL Tuning Workshop A - 96

Practice 4-1: Using Different Access Paths (continued)


Elapsed: 00:00:00.01
Execution Plan
---------------------------------------------------------Plan hash value: 4044283270
----------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes |
Cost (%CPU)| Time
|
----------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
364 | 35308 |
353
(0)| 00:00:05 |
|
1 | TABLE ACCESS BY INDEX ROWID| PROMOTIONS |
364 | 35308 |
353
(0)| 00:00:05 |
|* 2 |
INDEX RANGE SCAN
| PROMO_PK
|
364 |
|
1
(0)| 00:00:01 |
----------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("PROMO_ID">300)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
243 consistent gets
1 physical reads
0 redo size
27052 bytes sent via SQL*Net to client
585 bytes received via SQL*Net from client
17 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
235 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

43) Execute the following query:


SELECT *
FROM promotions_iot
WHERE promo_id > 300;

What do you observe?


a) The optimizer directly uses the index-organized structure, which is extremely
efficient in this case compared to the previous step.
SQL> @query14

Oracle Database 11g: SQL Tuning Workshop A - 97

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

set echo on
set linesize 200
set timing on
set autotrace traceonly
SELECT *
FROM promotions_iot
WHERE promo_id > 300
/

235 rows selected.


Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 1463021396
------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
235 | 23500 |
2
(0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| SYS_IOT_TOP_72170 |
235 | 23500 |
2
(0)| 00:00:01 |
------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("PROMO_ID">300)
Note
----- dynamic sampling used for this statement

Statistics
---------------------------------------------------------5 recursive calls
0 db block gets
42 consistent gets
0 physical reads
116 redo size
19922 bytes sent via SQL*Net to client
585 bytes received via SQL*Net from client
17 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
235 rows processed
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 98

Practice 4-1: Using Different Access Paths (continued)


SQL> set autotrace off
SQL> set timing off
SQL>

44) Execute the iot_cleanup.sql script to clean up your environment.


SQL> @iot_cleanup
SQL> set echo on
SQL>
SQL> drop table promotions_iot purge;
Table dropped.
SQL>
SQL>

45) Case 14: Execute shc_setup.sql to set up your environment for this lab.
SQL> @shc_setup
SQL> set echo on
SQL>
SQL> set linesize 200
SQL>
SQL> drop cluster bigemp_cluster including tables;
drop cluster bigemp_cluster including tables
*
ERROR at line 1:
ORA-00943: cluster does not exist

SQL>
SQL>
2
3
4
5

CREATE CLUSTER bigemp_cluster


(deptno number, sal number sort)
HASHKEYS 10000
single table HASH IS deptno SIZE 50
tablespace users;

Cluster created.
SQL>
SQL> create table bigemp_fact (
2 empno number primary key, sal number sort, job varchar2(12) not
null,
3 deptno number not null, hiredate date not null)
4 CLUSTER bigemp_cluster (deptno, sal);
Table created.
SQL>
SQL>
SQL>
2
3
4
5
6
7

begin
for i in 1..1400000 loop
insert into bigemp_fact values(i,i,'J1',10,sysdate);
end loop;
commit;
end;
/

Oracle Database 11g: SQL Tuning Workshop A - 99

Practice 4-1: Using Different Access Paths (continued)


PL/SQL procedure successfully completed.
SQL>
SQL>
2
3
4
5
6
7

begin
for i in 1..1400000 loop
insert into bigemp_fact values(1400000+i,i,'J1',20,sysdate);
end loop;
commit;
end;
/

PL/SQL procedure successfully completed.


SQL>
SQL>
SQL> exec dbms_stats.gather_schema_stats('SH');
PL/SQL procedure successfully completed.
SQL>

46) Execute the query15.sql script. What do you observe?


a) Because you may have a lot of memory on your system, the script first reduces
the amount of memory available to your session. The optimizer decides to use the
cluster access path to retrieve the data. The cost is minimal.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query15
set echo on
set linesize 200
set timing on
set autotrace traceonly
alter session set workarea_size_policy=manual;

Session altered.
Elapsed: 00:00:00.02
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.01
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.16
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:06.69
SQL>
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 100

Practice 4-1: Using Different Access Paths (continued)


SQL> select * from bigemp_fact where deptno=10;
1400000 rows selected.
Elapsed: 00:00:07.21
Execution Plan
---------------------------------------------------------Plan hash value: 865757019
-------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
| 1400K|
32M|
1
(0)| 00:00:01 |
|* 1 | TABLE ACCESS HASH| BIGEMP_FACT | 1400K|
32M|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("DEPTNO"=10)

Statistics
---------------------------------------------------------689 recursive calls
0 db block gets
99451 consistent gets
5999 physical reads
116 redo size
33605911 bytes sent via SQL*Net to client
1027083 bytes received via SQL*Net from client
93335 SQL*Net roundtrips to/from client
10 sorts (memory)
0 sorts (disk)
1400000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

47) Execute the query16.sql script. What do you observe?


a) Again, the script first ensures that the amount of memory available to your session
is reduced. Then the script executes the same query, but asks for ordering the
result based on the sorted sal column. The optimizer can still use the cluster
access path without sorting the data. The cost is still minimal.
SQL> @query16
SQL> set echo on

Oracle Database 11g: SQL Tuning Workshop A - 101

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

set linesize 200


set timing on
set autotrace traceonly
alter session set workarea_size_policy=manual;

Session altered.
Elapsed: 00:00:00.00
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.04
SQL>
SQL> select * from bigemp_fact where deptno=10 order by sal;
1400000 rows selected.
Elapsed: 00:00:07.41
Execution Plan
---------------------------------------------------------Plan hash value: 865757019
-------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
| 1400K|
32M|
1
(0)| 00:00:01 |
|* 1 | TABLE ACCESS HASH| BIGEMP_FACT | 1400K|
32M|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("DEPTNO"=10)

Statistics

Oracle Database 11g: SQL Tuning Workshop A - 102

Practice 4-1: Using Different Access Paths (continued)


---------------------------------------------------------1090 recursive calls
10 db block gets
99512 consistent gets
6012 physical reads
0 redo size
33605911 bytes sent via SQL*Net to client
1027083 bytes received via SQL*Net from client
93335 SQL*Net roundtrips to/from client
12 sorts (memory)
0 sorts (disk)
1400000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

48) Execute the query17.sql script. What do you observe?


a) Again, the script first ensures that the amount of memory available to your session
is reduced. Then the script executes the same query, but asks to order the result
based on the sorted sal column in the descending order. The optimizer can still
use the cluster access path without sorting the data. The cost is still minimal.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query17
set echo on
set linesize 200
set timing on
set autotrace traceonly
alter session set workarea_size_policy=manual;

Session altered.
Elapsed: 00:00:00.00
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.09
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.12
SQL>
SQL> select * from bigemp_fact where deptno=10 order by sal desc;
1400000 rows selected.

Oracle Database 11g: SQL Tuning Workshop A - 103

Practice 4-1: Using Different Access Paths (continued)


Elapsed: 00:00:07.35
Execution Plan
---------------------------------------------------------Plan hash value: 865757019
-------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
| 1400K|
32M|
1
(0)| 00:00:01 |
|* 1 | TABLE ACCESS HASH| BIGEMP_FACT | 1400K|
32M|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("DEPTNO"=10)

Statistics
---------------------------------------------------------1090 recursive calls
10 db block gets
99509 consistent gets
6005 physical reads
0 redo size
33605911 bytes sent via SQL*Net to client
1027083 bytes received via SQL*Net from client
93335 SQL*Net roundtrips to/from client
12 sorts (memory)
0 sorts (disk)
1400000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

49) Execute the query18.sql script. What do you observe?


a) Again, the script first ensures that the amount of memory available to your session
is reduced. Then the script executes the same query but this time asks to order the
result based on the nonsorted empno column. The optimizer can still make use of
the cluster access path, but must sort the data making the cost of the query higher.
SQL>
SQL>
SQL>
SQL>
SQL>

@query18
set echo on
set linesize 200
set timing on

Oracle Database 11g: SQL Tuning Workshop A - 104

Practice 4-1: Using Different Access Paths (continued)


SQL> set autotrace traceonly
SQL>
SQL> alter session set workarea_size_policy=manual;
Session altered.
Elapsed: 00:00:00.00
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.04
SQL>
SQL> select * from bigemp_fact where deptno=10 order by empno;
1400000 rows selected.
Elapsed: 00:00:10.01
Execution Plan
---------------------------------------------------------Plan hash value: 1775608660
----------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes |TempSpc|
Cost (%CPU)| Time
|
----------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 1400K|
32M|
|
47728
(1)| 00:09:33 |
|
1 | SORT ORDER BY
|
| 1400K|
32M|
107M|
47728
(1)| 00:09:33 |
|* 2 |
TABLE ACCESS HASH| BIGEMP_FACT | 1400K|
32M|
|
1
(0)| 00:00:01 |
----------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("DEPTNO"=10)

Statistics
----------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 105

Practice 4-1: Using Different Access Paths (continued)


1139
12
6178
12238
0
33605911
1027083
93335
12
1
1400000

recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

50) Execute the query19.sql script. What do you observe?


a) Again, the script first ensures that the amount of memory available to your session
is reduced. Then the script executes the same query, but this time asks to order the
result based on the sal, deptno key. The optimizer can still make use of the
cluster access path, but must sort the data making the cost of the query higher.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query19
set echo on
set linesize 200
set timing on
set autotrace traceonly
alter session set workarea_size_policy=manual;

Session altered.
Elapsed: 00:00:00.00
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.09
SQL>
SQL> select * from bigemp_fact where deptno=10 order by sal,empno;
1400000 rows selected.

Oracle Database 11g: SQL Tuning Workshop A - 106

Practice 4-1: Using Different Access Paths (continued)


Elapsed: 00:00:09.25
Execution Plan
---------------------------------------------------------Plan hash value: 1775608660
----------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes |TempSpc|
Cost (%CPU)| Time
|
----------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 1400K|
32M|
|
47728
(1)| 00:09:33 |
|
1 | SORT ORDER BY
|
| 1400K|
32M|
107M|
47728
(1)| 00:09:33 |
|* 2 |
TABLE ACCESS HASH| BIGEMP_FACT | 1400K|
32M|
|
1
(0)| 00:00:01 |
----------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("DEPTNO"=10)

Statistics
---------------------------------------------------------1139 recursive calls
12 db block gets
6178 consistent gets
12238 physical reads
0 redo size
33605911 bytes sent via SQL*Net to client
1027083 bytes received via SQL*Net from client
93335 SQL*Net roundtrips to/from client
12 sorts (memory)
1 sorts (disk)
1400000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

51) Execute the shc_cleanup.sql script to clean up your environment.


SQL> @shc_cleanup
SQL> set echo on
SQL>
SQL> drop cluster bigemp_cluster including tables;
Cluster dropped.
SQL>
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 107

Practice 4-1: Using Different Access Paths (continued)


52) Case 15: Execute the nic_setup.sql script to set up your environment for this
case.
SQL> @nic_setup
SQL> set echo on
SQL>
SQL> drop cluster emp_dept including tables;
drop cluster emp_dept including tables
*
ERROR at line 1:
ORA-00943: cluster does not exist

SQL>
SQL> drop table emp purge;
drop table emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> drop table dept purge;


drop table dept purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL>
SQL> CREATE TABLE
2
empno
3
ename
4
job
5
mgr
6
hiredate
7
sal
8
comm
9
deptno
10 );

emp (
NUMBER(7)
,
VARCHAR2(15) NOT NULL,
VARCHAR2(9)
,
NUMBER(7)
,
DATE
,
NUMBER(7)
,
NUMBER(7)
,
NUMBER(3)

Table created.
SQL>
SQL> CREATE TABLE dept (
2
deptno NUMBER(3) ,
3
dname VARCHAR2(14),
4
loc
VARCHAR2(14),
5
c
VARCHAR2(500)
6 );
Table created.
SQL>
SQL> CREATE INDEX emp_index
2
ON emp(deptno)
3
TABLESPACE users

Oracle Database 11g: SQL Tuning Workshop A - 108

Practice 4-1: Using Different Access Paths (continued)


4
5
6
7
8

STORAGE (INITIAL 50K


NEXT 50K
MINEXTENTS 2
MAXEXTENTS 10
PCTINCREASE 33);

Index created.
SQL>
SQL> CREATE INDEX dept_index
2
ON dept(deptno)
3
TABLESPACE users
4
STORAGE (INITIAL 50K
5
NEXT 50K
6
MINEXTENTS 2
7
MAXEXTENTS 10
8
PCTINCREASE 33);
Index created.
SQL>
SQL>
SQL> begin
2
for i in 1..999 loop
3
insert into dept values
(i,'D'||i,'L'||i,dbms_random.string('u',500));
4
end loop;
5
commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> begin
2
for i in 1..500000 loop
3
insert into emp values
(i,dbms_random.string('u',15),dbms_random.string('u',9),i,sysdate,i,
i,mod(i,999));
4
end loop;
5
commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> exec dbms_stats.gather_schema_stats('SH');
PL/SQL procedure successfully completed.
SQL>

53) Execute the nic_query.sql script. What do you observe?

Oracle Database 11g: SQL Tuning Workshop A - 109

Practice 4-1: Using Different Access Paths (continued)


a) The script first ensures that the amount of memory available to your session is
reduced. Then the script executes a join between the EMP and DEPT tables. The
optimizer is able to make use of the index to resolve the join.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@nic_query
set echo on
set timing on
set autotrace traceonly
set linesize 200
alter session set workarea_size_policy=manual;

Session altered.
Elapsed: 00:00:00.00
SQL> alter session set sort_area_size=50000;
Session altered.
Elapsed: 00:00:00.00
SQL> alter session set hash_area_size=5000;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL>
SQL> select * from emp,dept where emp.deptno=dept.deptno and
emp.deptno > 800;
99000 rows selected.
Elapsed: 00:00:02.88
Execution Plan
---------------------------------------------------------Plan hash value: 128236434
-------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes
|TempSpc| Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 19780 |
10M|
| 3449
(1)| 00:00:42 |
|* 1 | HASH JOIN
|
| 19780 |
10M|
104K| 3449
(1)| 00:00:42 |
|
2 |
TABLE ACCESS BY INDEX ROWID| DEPT
|
199 |
99K|
|
18
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| DEPT_INDEX |
199 |
|
|
2
(0)| 00:00:01 |
|* 4 |
TABLE ACCESS FULL
| EMP
| 99198 | 5521K|
| 1207
(2)| 00:00:15 |
--------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 110

Practice 4-1: Using Different Access Paths (continued)


Predicate Information (identified by operation id):
--------------------------------------------------1 - access("EMP"."DEPTNO"="DEPT"."DEPTNO")
3 - access("DEPT"."DEPTNO">800)
4 - filter("EMP"."DEPTNO">800)

Statistics
---------------------------------------------------------9 recursive calls
0 db block gets
4365 consistent gets
10146 physical reads
0 redo size
57968984 bytes sent via SQL*Net to client
73009 bytes received via SQL*Net from client
6601 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
99000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off;
SQL>

54) How would you enhance the performance of the previous query? Implement your
solution.
a) Create a cluster to store the two tables.
SQL> @ic_setup
SQL> set echo on
SQL>
SQL> drop table emp purge;
Table dropped.
SQL> drop table dept purge;
Table dropped.
SQL>
SQL> drop cluster emp_dept including tables;
drop cluster emp_dept including tables
*
ERROR at line 1:
ORA-00943: cluster does not exist

SQL>
SQL> CREATE CLUSTER emp_dept (deptno NUMBER(3))
2
SIZE 600
3
TABLESPACE users
4
STORAGE (INITIAL 200K
5
NEXT 300K

Oracle Database 11g: SQL Tuning Workshop A - 111

Practice 4-1: Using Different Access Paths (continued)


6
7

MINEXTENTS 2
PCTINCREASE 33);

Cluster created.
SQL>
SQL> CREATE TABLE emp (
2
empno
NUMBER(7)
,
3
ename
VARCHAR2(15) NOT NULL,
4
job
VARCHAR2(9)
,
5
mgr
NUMBER(7)
,
6
hiredate DATE
,
7
sal
NUMBER(7)
,
8
comm
NUMBER(7)
,
9
deptno
NUMBER(3))
10
CLUSTER emp_dept (deptno);
Table created.
SQL>
SQL> CREATE TABLE dept (
2
deptno NUMBER(3) ,
3
dname VARCHAR2(14),
4
loc
VARCHAR2(14),
5
c
VARCHAR2(500))
6
CLUSTER emp_dept (deptno);
Table created.
SQL>
SQL> CREATE INDEX emp_dept_index
2
ON CLUSTER emp_dept
3
TABLESPACE users
4
STORAGE (INITIAL 50K
5
NEXT 50K
6
MINEXTENTS 2
7
MAXEXTENTS 10
8
PCTINCREASE 33);
Index created.
SQL>
SQL> begin
2
for i in 1..999 loop
3
insert into dept values
(i,'D'||i,'L'||i,dbms_random.string('u',500));
4
end loop;
5
commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> begin
2
for i in 1..500000 loop

Oracle Database 11g: SQL Tuning Workshop A - 112

Practice 4-1: Using Different Access Paths (continued)


3
insert into emp values
(i,dbms_random.string('u',15),dbms_random.string('u',9),i,sysdate,i,
i,mod(i,999));
4
end loop;
5
commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> exec dbms_stats.gather_schema_stats('SH');
PL/SQL procedure successfully completed.
SQL>

55) Execute the following query to confirm the performance enhancement of the previous
query:
select *
from emp,dept
where emp.deptno=dept.deptno and emp.deptno > 800;

What do you observe?


a) The optimizer is able to use the cluster access path that makes the query execute
faster.
SQL> @ic_query
SQL> set echo on
SQL>
SQL> set timing on
SQL> set autotrace traceonly
SQL> set linesize 200
SQL>
SQL> select * from emp,dept where emp.deptno=dept.deptno and
emp.deptno > 800;
99000 rows selected.
Elapsed: 00:00:01.20
Execution Plan
---------------------------------------------------------Plan hash value: 593050162
--------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes |
Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 19780 |
10M|
11515
(1)| 00:02:19 |
|
1 | NESTED LOOPS
|
| 19780 |
10M|
11515
(1)| 00:02:19 |
|
2 |
TABLE ACCESS CLUSTER| DEPT
|
199 |
99K|
167
(0)| 00:00:03 |

Oracle Database 11g: SQL Tuning Workshop A - 113

Practice 4-1: Using Different Access Paths (continued)


|* 3 |
INDEX RANGE SCAN
| EMP_DEPT_INDEX |
1 |
|
2
(0)| 00:00:01 |
|* 4 |
TABLE ACCESS CLUSTER| EMP
|
99 | 5643 |
57
(0)| 00:00:01 |
--------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPT"."DEPTNO">800)
4 - filter("EMP"."DEPTNO">800 AND "EMP"."DEPTNO"="DEPT"."DEPTNO")

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
30131 consistent gets
0 physical reads
0 redo size
6259293 bytes sent via SQL*Net to client
73009 bytes received via SQL*Net from client
6601 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
99000 rows processed
SQL>
SQL> set autotrace off
SQL> set timing off
SQL>

56) Execute the ic_cleanup.sql script to clean up your environment for this case.
SQL> @ic_cleanup
SQL> set echo on
SQL>
SQL> drop cluster emp_dept including tables;
Cluster dropped.
SQL>
SQL> drop table emp purge;
drop table emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> drop table dept purge;
drop table dept purge
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL>
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 114

Practice 4-1: Using Different Access Paths (continued)


57) Case 16: Execute the iss_setup.sql script to set up your environment for this
lab.
SQL> @iss_setup
SQL> set echo on
SQL>
SQL> create table t(c number, d number);
Table created.
SQL>
SQL> begin
2
for i in 1..10000 loop
3
insert into t values(1,i);
4
end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
SQL>
SQL> create index it on t(c,d);
Index created.
SQL>

58) Execute the following query:


select count(*) from t where d=1;

What do you observe?


a) The optimizer is not using the index and does a full table scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query20
set echo on
set linesize 200
set timing on
set autotrace on
select count(*) from t where d=1;

COUNT(*)
---------1
Elapsed: 00:00:00.01
Execution Plan
---------------------------------------------------------Plan hash value: 2966233522
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|

Oracle Database 11g: SQL Tuning Workshop A - 115

Practice 4-1: Using Different Access Paths (continued)


-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
13 |
7
(0)|
00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
13 |
|
|
|* 2 |
TABLE ACCESS FULL| T
|
1 |
13 |
7
(0)|
00:00:01 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("D"=1)
Note
----- dynamic sampling used for this statement

Statistics
---------------------------------------------------------5 recursive calls
0 db block gets
48 consistent gets
0 physical reads
0 redo size
418 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

59) How would you improve the performance of a query, such as the one in the previous
step? Implement your solution.
a) Make sure you gather correctly the statistics for your table so that the index skip
scan can be used.
SQL> @iss_gather_stats
SQL> set echo on
SQL>
SQL> execute dbms_stats.gather_table_stats('SH','T',cascade=>TRUE);
PL/SQL procedure successfully completed.
SQL>

60) Execute the following query:


select count(*) from t where d=1;

Oracle Database 11g: SQL Tuning Workshop A - 116

Practice 4-1: Using Different Access Paths (continued)


What do you observe?
a) The optimizer now uses the index to perform an index skip scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query20
set echo on
set linesize 200
set timing on
set autotrace on
select count(*) from t where d=1;

COUNT(*)
---------1
Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 2609927160
-----------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)| Time
|
-----------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
1 |
4 |
2
(0)|
00:00:01 |
|
1 | SORT AGGREGATE |
|
1 |
4 |
|
|
|* 2 |
INDEX SKIP SCAN| IT
|
1 |
4 |
2
(0)|
00:00:01 |
-----------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("D"=1)
filter("D"=1)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
23 consistent gets
0 physical reads
0 redo size
418 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)

Oracle Database 11g: SQL Tuning Workshop A - 117

Practice 4-1: Using Different Access Paths (continued)


0
1

sorts (disk)
rows processed

SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

61) Compare the result of executing the previous query with the result you obtain when
you execute the following query:
select /*+ INDEX_FFS(t it) */ count(*) from t where d=1;

What do you observe?


a) The optimizer uses a fast full index scan, but this is not better than the index skip
scan.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@query21
set echo on
set linesize 200
set timing on
set autotrace on
select /*+ INDEX_FFS(t it) */ count(*) from t where d=1;

COUNT(*)
---------1
Elapsed: 00:00:00.00
Execution Plan
---------------------------------------------------------Plan hash value: 273610729
----------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
----------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
4 |
9
(0)|
00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
4 |
|
|
|* 2 |
INDEX FAST FULL SCAN| IT
|
1 |
4 |
9
(0)|
00:00:01 |
----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("D"=1)

Oracle Database 11g: SQL Tuning Workshop A - 118

Practice 4-1: Using Different Access Paths (continued)


Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
31 consistent gets
0 physical reads
0 redo size
418 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set timing off
SQL> set autotrace off
SQL>

62) Execute the iss_cleanup.sql script to clean up your environment for this case.
SQL> @iss_cleanup
SQL> set echo on
SQL>
SQL> drop table t purge;
Table dropped.
SQL>
SQL>

63) Exit from your SQL*Plus session, and execute the ap_cleanup.sh script to clean
up your environment for this lab.
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Access_Paths]$ ./ap_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 9 15:22:25 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> revoke dba from sh;
Revoke succeeded.

Oracle Database 11g: SQL Tuning Workshop A - 119

Practice 4-1: Using Different Access Paths (continued)


SQL>
SQL> @sh_main sh example temp oracle
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/
/home/oracle/ v3
SQL> Rem
SQL> Rem $Header: sh_main.sql 06-mar-2008.15:00:45 cbauwens Exp $
SQL> Rem
....
<<<<< CHANNELS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< FINAL PROCESSING >>>>>
- Changes have been committed
PL/SQL procedure successfully completed.

Commit complete.

gathering statistics ...


PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.


Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Access_Paths]$

Oracle Database 11g: SQL Tuning Workshop A - 120

Practice 4-2: Using the Result Cache


In this practice, you explore the various possibilities of caching query results in the
System Global Area (SGA). Perform the following steps to understand the use of Query
Result Cache.
1) Change to the $HOME/solutions/Query_Result_Cache directory and
execute the result_cache_setup.sh script.
[oracle@edrsr33p1-orcl ~]$ cd $HOME/solutions/Query_Result_Cache
[oracle@edrsr33p1-orcl Query_Result_Cache]$ ./result_cache_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 25 14:46:14
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> drop user qrc cascade
*
ERROR at line 1:
ORA-01918: user 'QRC' does not exist

SQL> SQL>
2
User created.

SQL> SQL>
Grant succeeded.
SQL> SQL> Connected.
SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> drop table cachejfv purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> SQL>
Table created.
SQL> SQL>
1 row created.
SQL>
1 row created.
SQL>
2 rows created.

Oracle Database 11g: SQL Tuning Workshop A - 121

Practice 4-2: Using the Result Cache (continued)


SQL>
4 rows created.
SQL>
8 rows created.
SQL>
16 rows created.
SQL>
32 rows created.
SQL>
64 rows created.
SQL>
128 rows created.
SQL>
256 rows created.
SQL>
512 rows created.
SQL>
1024 rows created.
SQL>
2048 rows created.
SQL>
4096 rows created.
SQL>
8192 rows created.
SQL>
16384 rows created.
SQL>
32768 rows created.
SQL>
65536 rows created.
SQL>
131072 rows created.
SQL>
262144 rows created.
SQL>
524288 rows created.
SQL>
1048576 rows created.

Oracle Database 11g: SQL Tuning Workshop A - 122

Practice 4-2: Using the Result Cache (continued)


SQL> SQL>
1 row created.
SQL> SQL>
Commit complete.
SQL> SQL>
System altered.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Query_Result_Cache]$

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

#!/bin/bash
cd /home/oracle/solutions/Query_Result_Cache
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
set echo on
drop user qrc cascade;
create user qrc identified by qrc
default tablespace users
temporary tablespace temp;
grant connect, resource, dba to qrc;
connect qrc/qrc
exec dbms_result_cache.flush;
drop table cachejfv purge;
create table cachejfv(c varchar2(500)) tablespace users;
insert into cachejfv
values('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
insert into cachejfv select * from cachejfv;
insert into cachejfv select * from cachejfv;

Oracle Database 11g: SQL Tuning Workshop A - 123

Practice 4-2: Using the Result Cache (continued)


insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert
insert

into
into
into
into
into
into
into
into
into
into
into
into
into
into
into
into
into
into
into

cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv
cachejfv

select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select

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

from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from

cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;
cachejfv;

insert into cachejfv values('b');


commit;
alter system flush buffer_cache;
FIN!

2) Open a terminal window, invoke SQL*Plus, and connect as the qrc user. From now
on, do not disconnect from this session. Determine the current content of the query
cache using the following statement:
select type,status,name,object_no,row_count,row_size_avg
from v$result_cache_objects order by 1;
What do you observe?
a) Use the check_result_cache.sql script. Right now, the query cache
should be empty.
[oracle@edrsr33p1-orcl Query_Result_Cache]$ sqlplus qrc/qrc
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 25 14:49:02
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @check_result_cache
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

Oracle Database 11g: SQL Tuning Workshop A - 124

Practice 4-2: Using the Result Cache (continued)


no rows selected
SQL>
SQL>

-------------------------------------------------------------set echo on
select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

3) Set timing on and execute the query as follows. You can use the query1.sql
script. Note the time that it takes to execute.
select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv
c4,cachejfv c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
SQL> set timing on
SQL> @query1
COUNT(*)
---------1
Elapsed: 00:00:02.46
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

4) Determine the execution plan of the previous query by using the


explain_query1.sql script. What do you observe?
a) Because of the result_cache hint, the result of the query is computed using the
result cache.
SQL> @explain_query1
SQL> set echo on
SQL>
SQL> explain plan for
2 select /*+ result_cache q_name(Q1) */ count(*)
3 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
4 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';

Oracle Database 11g: SQL Tuning Workshop A - 125

Practice 4-2: Using the Result Cache (continued)


Explained.
Elapsed: 00:00:00.05
SQL>
SQL> set linesize 180
SQL> set pagesize 200
SQL>
SQL> select plan_table_output from
table(dbms_xplan.display('plan_table',null,'ALL'));
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Plan hash value: 2531260445
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 | 1764 | 7316P (1)|999:59:59 |
|
1 | RESULT CACHE
| b7rh5vw33py4ug4n8xaav6525g |
|
|
|
|
|
2 |
SORT AGGREGATE
|
|
1 | 1764 |
|
|
|
3 |
MERGE JOIN CARTESIAN
|
|
294P|
15E| 7316P (1)|999:59:59 |
|
4 |
MERGE JOIN CARTESIAN
|
|
941T| 1264P|
23P (1)|999:59:59 |
|
5 |
MERGE JOIN CARTESIAN
|
|
3007G| 3446T|
74T (1)|999:59:59 |
|
6 |
MERGE JOIN CARTESIAN |
|
9606M| 9018G|
238G (1)|999:59:59 |
|
7 |
MERGE JOIN CARTESIAN |
|
30M|
21G|
761M (1)|999:59:59 |
|
8 |
MERGE JOIN CARTESIAN|
|
98011 |
47M| 2432K (1)| 08:06:26 |
|* 9 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7748
(1)| 00:01:33 |
| 10 |
BUFFER SORT
|
|
313 | 78876 | 2424K (1)| 08:04:53 |
|* 11 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 12 |
BUFFER SORT
|
|
313 | 78876 |
761M (1)|999:59:59 |
|* 13 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 14 |
BUFFER SORT
|
|
313 | 78876 |
238G (1)|999:59:59 |
|* 15 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 16 |
BUFFER SORT
|
|
313 | 78876 |
74T (1)|999:59:59 |

Oracle Database 11g: SQL Tuning Workshop A - 126

Practice 4-2: Using the Result Cache (continued)


|* 17 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 18 |
BUFFER SORT
|
|
313 | 78876 |
23P (1)|999:59:59 |
|* 19 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 20 |
BUFFER SORT
|
|
313 | 78876 | 7316P (1)|999:59:59 |
|* 21 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
--------------------------------------------------------------------------------------------------------Query Block Name / Object Alias (identified by operation id):
------------------------------------------------------------1
9
11
13
15
17
19
21

SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1

/
/
/
/
/
/
/

C7@SEL$1
C6@SEL$1
C5@SEL$1
C4@SEL$1
C3@SEL$1
C2@SEL$1
C1@SEL$1

Predicate Information (identified by operation id):


--------------------------------------------------9
11
13
15
17
19
21

filter("C7"."C"='b')
filter("C6"."C"='b')
filter("C5"."C"='b')
filter("C4"."C"='b')
filter("C3"."C"='b')
filter("C2"."C"='b')
filter("C1"."C"='b')

Column Projection Information (identified by operation id):


----------------------------------------------------------1 - COUNT(*)[22]
2 - (#keys=0) COUNT(*)[22]
3 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500],
"C2"."C"[VARCHAR2,500], "C1"."C"[VARCHAR2,500]
4 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500],
"C2"."C"[VARCHAR2,500]
5 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500]
6 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500]
7 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500]

Oracle Database 11g: SQL Tuning Workshop A - 127

Practice 4-2: Using the Result Cache (continued)


8
9
10
11
12
13
14
15
16
17
18
19
20
21

(#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500]


"C7"."C"[VARCHAR2,500]
(#keys=0) "C6"."C"[VARCHAR2,500]
"C6"."C"[VARCHAR2,500]
(#keys=0) "C5"."C"[VARCHAR2,500]
"C5"."C"[VARCHAR2,500]
(#keys=0) "C4"."C"[VARCHAR2,500]
"C4"."C"[VARCHAR2,500]
(#keys=0) "C3"."C"[VARCHAR2,500]
"C3"."C"[VARCHAR2,500]
(#keys=0) "C2"."C"[VARCHAR2,500]
"C2"."C"[VARCHAR2,500]
(#keys=0) "C1"."C"[VARCHAR2,500]
"C1"."C"[VARCHAR2,500]

Result Cache Information (identified by operation id):


-----------------------------------------------------1 - column-count=1; dependencies=(QRC.CACHEJFV);
attributes=(single-row); parameters=(nls); name="select /*+
result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cac"

Note
----- dynamic sampling used for this statement
89 rows selected.
Elapsed: 00:00:00.03
SQL>
-------------------------------------------------------------set echo on
explain plan for
select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';
set linesize 180
set pagesize 200
select plan_table_output from
table(dbms_xplan.display('plan_table',null,'ALL'));

5) Determine the current content of the query cache by using the


check_result_cache.sql script. What do you observe?
a) You can now see the result of your query cached.
Oracle Database 11g: SQL Tuning Workshop A - 128

Practice 4-2: Using the Result Cache (continued)


SQL> @check_result_cache
SQL> set echo on
SQL>
SQL> set long 2000
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV
71474
0
0
Result
0
1

Published select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5

Elapsed: 00:00:00.00
SQL>
Elapsed: 00:00:00.00
SQL>
-------------------------------------------------------------set echo on
set long 2000
select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

6) Flush the buffer cache of your instance and rerun the query executed in step 3. What
do you observe?
a) The execution time for the query is now almost instantaneous.
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.05
SQL> @query1
SQL> select /*+ result_cache q_name(Q1) */ count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7

Oracle Database 11g: SQL Tuning Workshop A - 129

Practice 4-2: Using the Result Cache (continued)


3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:00.00
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

7) Insert a new row into the CACHEJFV table using the following statement:
insert into cachejfv values('c');

What do you observe?


a) The corresponding result cache entry is automatically invalidated.
SQL> insert into cachejfv values('c');
1 row created.
Elapsed: 00:00:00.00
SQL> commit;
Commit complete.
Elapsed: 00:00:00.00
SQL> @check_result_cache
SQL> set echo on
SQL>
SQL> set long 2000
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV
71474
0
0
Result
0
1

Invalid

select /*+ result_cache q_name(Q1) */ count(*)

Oracle Database 11g: SQL Tuning Workshop A - 130

Practice 4-2: Using the Result Cache (continued)


from cachejfv c1,cachejfv c2,cachejfv
c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5

Elapsed: 00:00:00.00
SQL>
-------------------------------------------------------------set echo on
set long 2000
select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

8) Execute your query from step 3 and step 6 again and check the result cache. What do
you observe?
a) Again, it takes some time to execute the query. The result cache shows that a new
entry has been added for the new result.
SQL> @query1
SQL> select /*+ result_cache q_name(Q1) */ count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:02.33
SQL>
SQL> @check_result_cache
SQL> set echo on
SQL>
SQL> set long 2000
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV
71474
0
0

Oracle Database 11g: SQL Tuning Workshop A - 131

Practice 4-2: Using the Result Cache (continued)


Result
0
1

Invalid

select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5
Result
0
1

Published select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5

Elapsed: 00:00:00.00
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

set echo on
set long 2000
select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

9) Generate a detailed result cache memory report.


SQL> set serveroutput on
SQL> EXEC DBMS_RESULT_CACHE.MEMORY_REPORT(detailed=>true);
R e s u l t
C a c h e
M e m o r y
R e p o r t
[Parameters]
Block Size
= 1K bytes
Maximum Cache Size = 2080K bytes (2080 blocks)
Maximum Result Size = 104K bytes (104 blocks)
[Memory]
Total Memory = 116008 bytes [0.035% of the Shared Pool]
... Fixed Memory = 5132 bytes [0.002% of the Shared Pool]
....... Cache Mgr = 108 bytes
....... Memory Mgr = 124 bytes
....... Bloom Fltr = 2K bytes
....... State Objs = 2852 bytes
... Dynamic Memory = 110876 bytes [0.033% of the Shared Pool]
....... Overhead = 78108 bytes
........... Hash Table
= 32K bytes (4K buckets)
........... Chunk Ptrs
= 12K bytes (3K slots)
........... Chunk Maps
= 12K bytes
........... Miscellaneous = 20764 bytes
....... Cache Memory = 32K bytes (32 blocks)

Oracle Database 11g: SQL Tuning Workshop A - 132

Practice 4-2: Using the Result Cache (continued)


........... Unused Memory = 29 blocks
........... Used Memory = 3 blocks
............... Dependencies = 1 blocks (1 count)
............... Results = 2 blocks
................... SQL
= 1 blocks (1 count)
................... Invalid = 1 blocks (1 count)
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.12
SQL>

10) Execute your query from step 3 and step 6 again. What do you observe?
a) The query again uses the result that was previously cached.
SQL> @query1
SQL> select /*+ result_cache q_name(Q1) */ count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:00.01
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';
11) Ensure that you bypass the result cache before performing the next step.
SQL>
SQL> exec DBMS_RESULT_CACHE.BYPASS(bypass_mode=>true);
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL>

12) Execute your query again. What do you observe?


a) The query again takes longer to execute because it no longer uses the result cache.
SQL> @query1
SQL> select /*+ result_cache q_name(Q1) */ count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';

Oracle Database 11g: SQL Tuning Workshop A - 133

Practice 4-2: Using the Result Cache (continued)


COUNT(*)
---------1
Elapsed: 00:00:02.34
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

13) Ensure that you no longer bypass the result cache and check that your query uses it
again.
SQL> exec DBMS_RESULT_CACHE.BYPASS(bypass_mode=>false);
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> @query1
SQL> select /*+ result_cache q_name(Q1) */ count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:00.00
SQL>
SQL>
-------------------------------------------------------------select /*+ result_cache q_name(Q1) */ count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

14) Execute the following query by using the query2.sql script:


select count(*) from cachejfv c1,cachejfv c2,cachejfv
c3,cachejfv c4,cachejfv c5,cachejfv c6, cachejfv c7 where
c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

What do you observe?

Oracle Database 11g: SQL Tuning Workshop A - 134

Practice 4-2: Using the Result Cache (continued)


a) Although the query is the same as the one used in step 3, it is not recognized as
cached because it does not contain the hint. So its execution time is long again.
SQL> @query2
SQL> select count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:02.41
SQL>
-------------------------------------------------------------select count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';

15) How would you force the previous query to use the cached result without using hints?
Use the force_query2.sql script and then verify that you successfully used the
cached result. Finally, undo your change.
SQL> @force_query2
SQL> set echo on
SQL>
SQL> show parameter result_cache_mode
NAME
TYPE
VALUE
------------------------------------ ----------- ----------------------------result_cache_mode
string
MANUAL
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV
71474
0
0
Result
0
1

Invalid

select /*+ result_cache q_name(Q1) */ count(*)

Oracle Database 11g: SQL Tuning Workshop A - 135

Practice 4-2: Using the Result Cache (continued)


from cachejfv c1,cachejfv c2,cachejfv
c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5
Result
0
1

Published select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5

Elapsed: 00:00:00.00
SQL>
SQL> alter session set result_cache_mode=force;
Session altered.
Elapsed: 00:00:00.04
SQL>
SQL> explain plan for
2 select count(*)
3 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
4 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
Explained.
Elapsed: 00:00:00.05
SQL>
SQL> set linesize 180
SQL> set pagesize 200
SQL>
SQL> select plan_table_output from
table(dbms_xplan.display('plan_table',null,'ALL'));
PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Plan hash value: 2531260445
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 | 1764 | 7316P (1)|999:59:59 |
|
1 | RESULT CACHE
| b7rh5vw33py4ug4n8xaav6525g |
|
|
|
|
|
2 |
SORT AGGREGATE
|
|
1 | 1764 |
|
|
|
3 |
MERGE JOIN CARTESIAN
|
|
294P|
15E| 7316P (1)|999:59:59 |

Oracle Database 11g: SQL Tuning Workshop A - 136

Practice 4-2: Using the Result Cache (continued)


|
4 |
MERGE JOIN CARTESIAN
|
|
941T| 1264P|
23P (1)|999:59:59 |
|
5 |
MERGE JOIN CARTESIAN
|
|
3007G| 3446T|
74T (1)|999:59:59 |
|
6 |
MERGE JOIN CARTESIAN |
|
9606M| 9018G|
238G (1)|999:59:59 |
|
7 |
MERGE JOIN CARTESIAN |
|
30M|
21G|
761M (1)|999:59:59 |
|
8 |
MERGE JOIN CARTESIAN|
|
98011 |
47M| 2432K (1)| 08:06:26 |
|* 9 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7748
(1)| 00:01:33 |
| 10 |
BUFFER SORT
|
|
313 | 78876 | 2424K (1)| 08:04:53 |
|* 11 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 12 |
BUFFER SORT
|
|
313 | 78876 |
761M (1)|999:59:59 |
|* 13 |
TABLE ACCESS FULL | CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 14 |
BUFFER SORT
|
|
313 | 78876 |
238G (1)|999:59:59 |
|* 15 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 16 |
BUFFER SORT
|
|
313 | 78876 |
74T (1)|999:59:59 |
|* 17 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 18 |
BUFFER SORT
|
|
313 | 78876 |
23P (1)|999:59:59 |
|* 19 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
| 20 |
BUFFER SORT
|
|
313 | 78876 | 7316P (1)|999:59:59 |
|* 21 |
TABLE ACCESS FULL
| CACHEJFV
|
313 | 78876 | 7746
(1)| 00:01:33 |
--------------------------------------------------------------------------------------------------------Query Block Name / Object Alias (identified by operation id):
------------------------------------------------------------1
9
11
13
15
17
19
21

SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1
SEL$1

/
/
/
/
/
/
/

C7@SEL$1
C6@SEL$1
C5@SEL$1
C4@SEL$1
C3@SEL$1
C2@SEL$1
C1@SEL$1

Predicate Information (identified by operation id):


--------------------------------------------------9 - filter("C7"."C"='b')
11 - filter("C6"."C"='b')
13 - filter("C5"."C"='b')

Oracle Database 11g: SQL Tuning Workshop A - 137

Practice 4-2: Using the Result Cache (continued)


15
17
19
21

filter("C4"."C"='b')
filter("C3"."C"='b')
filter("C2"."C"='b')
filter("C1"."C"='b')

Column Projection Information (identified by operation id):


----------------------------------------------------------1 - COUNT(*)[22]
2 - (#keys=0) COUNT(*)[22]
3 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500],
"C2"."C"[VARCHAR2,500], "C1"."C"[VARCHAR2,500]
4 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500],
"C2"."C"[VARCHAR2,500]
5 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500], "C3"."C"[VARCHAR2,500]
6 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500],
"C4"."C"[VARCHAR2,500]
7 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500],
"C5"."C"[VARCHAR2,500]
8 - (#keys=0) "C7"."C"[VARCHAR2,500], "C6"."C"[VARCHAR2,500]
9 - "C7"."C"[VARCHAR2,500]
10 - (#keys=0) "C6"."C"[VARCHAR2,500]
11 - "C6"."C"[VARCHAR2,500]
12 - (#keys=0) "C5"."C"[VARCHAR2,500]
13 - "C5"."C"[VARCHAR2,500]
14 - (#keys=0) "C4"."C"[VARCHAR2,500]
15 - "C4"."C"[VARCHAR2,500]
16 - (#keys=0) "C3"."C"[VARCHAR2,500]
17 - "C3"."C"[VARCHAR2,500]
18 - (#keys=0) "C2"."C"[VARCHAR2,500]
19 - "C2"."C"[VARCHAR2,500]
20 - (#keys=0) "C1"."C"[VARCHAR2,500]
21 - "C1"."C"[VARCHAR2,500]
Result Cache Information (identified by operation id):
-----------------------------------------------------1 - column-count=1; dependencies=(QRC.CACHEJFV);
attributes=(single-row); parameters=(nls); name="select count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2."

Note
----- dynamic sampling used for this statement
89 rows selected.

Oracle Database 11g: SQL Tuning Workshop A - 138

Practice 4-2: Using the Result Cache (continued)


Elapsed: 00:00:00.07
SQL>
SQL> select count(*)
2 from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
3 where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and
c5.c='b' and c6.c='b' and c7.c='b';
COUNT(*)
---------1
Elapsed: 00:00:00.04
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV
71474
0
0
Result
0
1

Invalid

select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5
Result
0
1

Published select /*+ result_cache q_name(Q1) */ count(*)

from cachejfv c1,cachejfv c2,cachejfv


c3,cachejfv c4,cachejfv c5,cachejfv c6, cac
5

Elapsed: 00:00:00.00
SQL>
SQL> alter session set result_cache_mode=manual;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL>
-------------------------------------------------------------set echo on
show parameter result_cache_mode

Oracle Database 11g: SQL Tuning Workshop A - 139

Practice 4-2: Using the Result Cache (continued)


select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
alter session set result_cache_mode=force;
explain plan for
select count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';
set linesize 180
set pagesize 200
select plan_table_output from
table(dbms_xplan.display('plan_table',null,'ALL'));
select count(*)
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c='b' and c2.c='b' and c3.c='b' and c4.c='b' and c5.c='b'
and c6.c='b' and c7.c='b';
select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
alter session set result_cache_mode=manual;

16) Clear the result cache. Query V$RESULT_CACHE_OBJECTS to verify the clear
operation.
SQL> exec dbms_result_cache.flush;
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
no rows selected
Elapsed: 00:00:00.00
SQL>

17) Create a PL/SQL function that uses the result cache by running the cre_func.sql
script.
SQL>
SQL>
2
3
4
5
6
7

@cre_func
create or replace function CACHEJFV_COUNT(v varchar2)
return number
result_cache relies_on (cachejfv)
is
cnt number;
begin
select count(*) into cnt

Oracle Database 11g: SQL Tuning Workshop A - 140

Practice 4-2: Using the Result Cache (continued)


8
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
9
where c1.c=v and c2.c=v and c3.c=v and c4.c=v and c5.c=v and
c6.c=v and c7.c=v;
10
return cnt;
11 end;
12 /
Function created.
Elapsed: 00:00:00.15
SQL>
-------------------------------------------------------------create or replace function CACHEJFV_COUNT(v varchar2)
return number
result_cache relies_on (cachejfv)
is
cnt number;
begin
select count(*) into cnt
from cachejfv c1,cachejfv c2,cachejfv c3,cachejfv c4,cachejfv
c5,cachejfv c6, cachejfv c7
where c1.c=v and c2.c=v and c3.c=v and c4.c=v and c5.c=v and c6.c=v
and c7.c=v;
return cnt;
end;
/

18) Determine what is in the result cache by querying V$RESULT_CACHE_OBJECTS.


SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
no rows selected
Elapsed: 00:00:00.00
SQL>

19) Call the new function with b as its argument. What do you observe?
a) It takes a long time to execute because the result is not cached yet. After
executing the function, the functions result for the b argument is cached.
SQL> select cachejfv_count('b') from dual;
CACHEJFV_COUNT('B')
------------------1
Elapsed: 00:00:01.56
SQL>
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;

Oracle Database 11g: SQL Tuning Workshop A - 141

Practice 4-2: Using the Result Cache (continued)


TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ---------ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV_COUNT
71475
0
0
Dependency Published QRC.CACHEJFV
71474
0
0
Result
Published
"QRC"."CACHEJFV_COUNT"::8."CACHEJFV_COUNT"#8440831613f0f5d3 #1
0
1
4

Elapsed: 00:00:00.00
SQL>
SQL>

20) Call the new function with b as its argument again. What do you observe?
a) This time the function executes almost instantaneously.
SQL> select cachejfv_count('b') from dual;
CACHEJFV_COUNT('B')
------------------1
Elapsed: 00:00:00.00
SQL>

21) Call the new function with c as its argument again. What do you observe?
a) Again it takes a long time to execute the function because of the new value for the
argument. After execution, the second result is cached.
SQL> select cachejfv_count('c') from dual;
CACHEJFV_COUNT('C')
------------------1
Elapsed: 00:00:04.15
SQL> select type,status,name,object_no,row_count,row_size_avg from
v$result_cache_objects order by 1;
TYPE
STATUS
NAME
OBJECT_NO ROW_COUNT
---------- --------- ------------------------------------------------------------------------------------------------------------------------------ ---------- ----------

Oracle Database 11g: SQL Tuning Workshop A - 142

Practice 4-2: Using the Result Cache (continued)


ROW_SIZE_AVG
-----------Dependency Published QRC.CACHEJFV_COUNT
71475
0
0
Dependency Published QRC.CACHEJFV
71474
0
0
Result
Published
"QRC"."CACHEJFV_COUNT"::8."CACHEJFV_COUNT"#8440831613f0f5d3 #1
0
1
4
Result
Published
"QRC"."CACHEJFV_COUNT"::8."CACHEJFV_COUNT"#8440831613f0f5d3 #1
0
1
4

Elapsed: 00:00:00.01
SQL>
SQL>
SQL> select cachejfv_count('c') from dual;
CACHEJFV_COUNT('C')
------------------1
Elapsed: 00:00:00.00
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 143

Practices for Lesson 5

Oracle Database 11g: SQL Tuning Workshop A - 144

Practice 5-1: Extracting Execution Plans


In this practice, you use various methods to extract the execution plan used by the
optimizer to execute a query. Note that all scripts needed for this lab can be found in your
$HOME/solutions/Explain_Plan directory.
1) Connected as the oracle user from a terminal session, execute the ep_setup.sh
script. This script creates a new user called EP and a table called TEST used
throughout this lab.
[oracle@edrsr33p1-orcl Explain_Plan]$ ./ep_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 2 20:11:45 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user ep cascade;
drop user ep cascade
*
ERROR at line 1:
ORA-01918: user 'EP' does not exist

SQL>
SQL> create user ep identified by ep default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant connect, resource, dba to ep;
Grant succeeded.
SQL>
SQL> connect ep/ep
Connected.
SQL>
SQL> drop table test purge;
drop table test purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table test(c number, d varchar2(500));
Table created.

Oracle Database 11g: SQL Tuning Workshop A - 145

Practice 5-1: Extracting Execution Plans (continued)


SQL>
SQL> begin
2 for i in 1..20000 loop
3 insert into test
values(1,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> create index test_c_indx on test(c);
Index created.
SQL>
SQL> exec dbms_stats.gather_schema_stats('EP');
PL/SQL procedure successfully completed.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> set echo off
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Explain_Plan]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Explain_Plan
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

Oracle Database 11g: SQL Tuning Workshop A - 146

Practice 5-1: Extracting Execution Plans (continued)


sqlplus / as sysdba @ep_setup.sql
-------------------------------------------------------------set echo on
drop user ep cascade;
create user ep identified by ep default tablespace users temporary
tablespace temp;
grant connect, resource, dba to ep;
connect ep/ep
drop table test purge;
create table test(c number, d varchar2(500));
begin
for i in 1..20000 loop
insert into test
values(1,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
create index test_c_indx on test(c);
exec dbms_stats.gather_schema_stats('EP');
alter system flush shared_pool;
alter system flush buffer_cache;
set echo off
set term off
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;
exit;

2) From the same terminal session (referred to as session 1 in the rest of this lab), be
ready to execute the ep_session_issue.sh script. Enter the command, but do
not execute it yet.
Session 1:
---------[oracle@edrsr33p1-orcl Explain_Plan]$ ./ep_session_issue.sh
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 147

Practice 5-1: Extracting Execution Plans (continued)


#!/bin/bash
cd /home/oracle/solutions/Explain_Plan
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus ep/ep @ep_session_issue.sql
-------------------------------------------------------------set echo off
set termout off
alter session set optimizer_mode=rule;
set termout on
set echo on
set timing on
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;
exit;

3) From a second terminal session (referred to as session 2 in the rest of this lab),
connect as the oracle user. After this, connect to a SQL*Plus session as the SYS
user. From that SQL*Plus session, be ready to use SQL Monitoring to monitor the
execution plan used by session 1. You can execute the ep_monitoring.sql
script for that purpose. Enter the command, but do not execute it yet. Note: Ensure
that you understand the coordination between both sessions by pre-reading steps 4
and 5 before you continue.
Session 2:
---------[oracle@edrsr33p1-orcl Explain_Plan]$ sqlplus / as sysdba
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 2 20:12:28 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @ep_monitor

Oracle Database 11g: SQL Tuning Workshop A - 148

Practice 5-1: Extracting Execution Plans (continued)


-------------------------------------------------------------set
set
set
set
set

echo on
long 10000000
longchunksize 10000000
linesize 200
pagesize 1000

exec dbms_lock.sleep(8);
select
dbms_sqltune.report_sql_monitor(sql_id=>'dkz7v96ym42c6',report_level
=>'ALL') from dual;

4) After you are ready in both the sessions, press [Enter] in session 1 to start the
execution of the ep_session_issue.sh script. Note: Do not wait. Proceed with
the next step immediately.
Session 1:
---------[oracle@edrsr33p1-orcl Explain_Plan]$ ./ep_session_issue.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 2 20:12:47 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> set timing on
SQL>
SQL> select count(*) from test t1, test t2 where t1.c=t2.c and
t1.c=1;

5) In session 2, enter return to start the execution of the ep_monitor.sql script.


After the execution, enter / and go back to your SQL*Plus session as many times
as necessary until session 1 is done with its execution. What do you observe?
a) You can see that session 1 uses NESTED LOOPS on top of two INDEX RANGE
SCANS to execute the query. It takes approximately 47 seconds to execute session
1s query. The time depends on your environment. The big advantage of SQL
Monitoring is that you can clearly see which steps in the execution plan take most
of the resources. In this case, you clearly see that you do only one scan of the
index, and that for each row returned, you execute another index scan to probe.
This is not really efficient. Also there is no costs information for this monitored
plan.

Oracle Database 11g: SQL Tuning Workshop A - 149

Practice 5-1: Extracting Execution Plans (continued)


Session 2:
---------SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@ep_monitor
set long 10000000
set longchunksize 10000000
set linesize 200
set pagesize 1000
exec dbms_lock.sleep(8);

PL/SQL procedure successfully completed.


SQL>
SQL> select
dbms_sqltune.report_sql_monitor(sql_id=>'dkz7v96ym42c6',report_level
=>'ALL') from dual;
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:12:56

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
8.10 |
8.09 |
0.01 |
134K |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
=================================================================

Oracle Database 11g: SQL Tuning Workshop A - 150

Practice 5-1: Extracting Execution Plans (continued)


| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
1 |
+6 |
1 |
0 |
12.50 | Cpu (1)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
3 |
+8 |
1 |
79096K |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
3 |
+8 |
1 |
3954 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
9 |
+2 |
3955 |
79096K |
87.50 | Cpu (7)
|
====================================================================
=================================================================

SQL>
SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:12:58

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
10 |
10 |
0.01 |
167K |
-----------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 151

Practice 5-1: Extracting Execution Plans (continued)


SQL Plan Monitoring Details
====================================================================
=================================================================
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
1 |
+6 |
1 |
0 |
10.00 | Cpu (1)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
5 |
+8 |
1 |
96151K |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
5 |
+8 |
1 |
4807 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
11 |
+2 |
4808 |
96151K |
90.00 | Cpu (9)
|
====================================================================
=================================================================

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:13:00

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |

Oracle Database 11g: SQL Tuning Workshop A - 152

Practice 5-1: Extracting Execution Plans (continued)


| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
12 |
12 |
0.01 |
201K |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
=================================================================
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
1 |
+6 |
1 |
0 |
8.33 | Cpu (1)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
7 |
+8 |
1 |
113M |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
7 |
+8 |
1 |
5664 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
13 |
+2 |
5665 |
113M |
91.67 | Cpu (11)
|
====================================================================
=================================================================

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time

:
:
:
:
:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54

Oracle Database 11g: SQL Tuning Workshop A - 153

Practice 5-1: Extracting Execution Plans (continued)


Last Refresh Time

04/02/2008 20:13:08

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
20 |
20 |
0.04 |
334K |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
=================================================================
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
1 |
+6 |
1 |
0 |
5.00 | Cpu (1)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
15 |
+8 |
1 |
182M |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
15 |
+8 |
1 |
9082 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
21 |
+2 |
9083 |
182M |
95.00 | Cpu (19)
|
====================================================================
=================================================================

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID

:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6

Oracle Database 11g: SQL Tuning Workshop A - 154

Practice 5-1: Extracting Execution Plans (continued)


SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:

16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:13:17

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
28 |
28 |
0.05 |
468K |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
=================================================================
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
1 |
+6 |
1 |
0 |
3.45 | Cpu (1)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
24 |
+8 |
1 |
250M |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
24 |
+8 |
1 |
12518 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
30 |
+2 | 12519 |
250M |
96.55 | Cpu (28)
|
====================================================================
=================================================================

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information

Oracle Database 11g: SQL Tuning Workshop A - 155

Practice 5-1: Extracting Execution Plans (continued)


Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:13:29

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
40 |
40 |
0.06 |
669K |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
=================================================================
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
=================================================================
|
0 | SELECT STATEMENT
|
|
|
|
|
|
1 |
|
|
|
|
1 |
SORT AGGREGATE
|
|
|
|
35 |
+6 |
1 |
0 |
4.88 | Cpu (2)
|
| -> 2 |
NESTED LOOPS
|
|
|
|
36 |
+8 |
1 |
353M |
|
|
| -> 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
36 |
+8 |
1 |
17663 |
|
|
| -> 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
42 |
+2 | 17664 |
353M |
95.12 | Cpu (39)
|
====================================================================
=================================================================

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1

Oracle Database 11g: SQL Tuning Workshop A - 156

Practice 5-1: Extracting Execution Plans (continued)


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

DONE (ALL ROWS)


1
138
dkz7v96ym42c6
16777222
1643938535
04/02/2008 20:12:46
04/02/2008 20:12:54
04/02/2008 20:13:34

------------------------------------------------| Elapsed |
Cpu
| Other
| Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
------------------------------------------------|
46 |
46 |
0.06 |
1 |
760K |
-------------------------------------------------

SQL Plan Monitoring Details


====================================================================
===============================================================
| Id |
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Activity | Activity Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (percent) |
(sample #)
|
====================================================================
===============================================================
| 0 | SELECT STATEMENT
|
|
|
|
1 |
+48 |
1 |
1 |
|
|
| 1 |
SORT AGGREGATE
|
|
|
|
43 |
+6 |
1 |
1 |
4.35 | Cpu (2)
|
| 2 |
NESTED LOOPS
|
|
|
|
41 |
+8 |
1 |
400M |
|
|
| 3 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
41 |
+8 |
1 |
20000 |
|
|
| 4 |
INDEX RANGE SCAN | TEST_C_INDX |
|
|
47 |
+2 | 20000 |
400M |
95.65 | Cpu (44)
|
====================================================================
===============================================================

SQL>

6) After approximately 47 seconds (depending on your environment), you should see the
following output in your session 1:
Session 1:
----------

Oracle Database 11g: SQL Tuning Workshop A - 157

Practice 5-1: Extracting Execution Plans (continued)

COUNT(*)
---------400000000
Elapsed: 00:00:47.21
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

[oracle@edrsr33p1-orcl Explain_Plan]$

7) From session 1, connect as the EP user in the SQL*Plus session.


Session 1:
---------[oracle@edrsr33p1-orcl Explain_Plan]$ sqlplus ep/ep
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 2 20:14:03 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

8) Use PLAN_TABLE to determine the execution plan of the query that was executed in
step 4. What do you observe?
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;

a) This time the execution plan uses a hash join on top of two index fast full scans.
Session 1:
---------SQL> @ep_explain
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> explain plan for
2 select count(*) from test t1, test t2 where t1.c=t2.c and
t1.c=1;
Explained.
SQL>
SQL> select * from table(dbms_xplan.display);

Oracle Database 11g: SQL Tuning Workshop A - 158

Practice 5-1: Extracting Execution Plans (continued)


PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Plan hash value: 3253233075
------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
6 | 2131
(99)| 00:00:22 |
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|
|* 2 |
HASH JOIN
|
|
400M| 2288M| 2131
(99)| 00:00:22 |
|* 3 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
|* 4 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("T1"."C"="T2"."C")
3 - filter("T1"."C"=1)
4 - filter("T2"."C"=1)
18 rows selected.
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
explain plan for
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;
select * from table(dbms_xplan.display);

9) Now, you want to monitor the previous execution plan to compare it with the one
generated in step 4. In addition, you want to make sure you use the correct plan this
time. So, in your session 1, start autotrace, and be ready to execute the following
query. Do not execute it yet as you need to start SQL Monitoring in your session 2:
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;

Oracle Database 11g: SQL Tuning Workshop A - 159

Practice 5-1: Extracting Execution Plans (continued)


Session 1:
---------SQL> set autotrace on
SQL> @ep_execute

10) From your session 2, be ready to execute your SQL Monitoring command again. Do
not execute it yet though.
Session 2:
---------SQL> @ep_monitor
-------------------------------------------------------------set
set
set
set
set

echo on
long 10000000
longchunksize 10000000
linesize 200
pagesize 1000

exec dbms_lock.sleep(8);
select
dbms_sqltune.report_sql_monitor(sql_id=>'dkz7v96ym42c6',report_level
=>'ALL') from dual;

11) Start the execution of your query from session 1 by pressing [Enter]. Note: Move to
next stepwithout waiting.
Session 1:
---------SQL> @ep_execute
SQL> set echo on
SQL>
SQL> set timing on
SQL>
SQL> select count(*) from test t1, test t2 where t1.c=t2.c and
t1.c=1;
-------------------------------------------------------------set echo on
set timing on
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;

Oracle Database 11g: SQL Tuning Workshop A - 160

Practice 5-1: Extracting Execution Plans (continued)


12) From your session 2, start monitoring your query by pressing the return key. After the
query is executed, enter / and go back to your SQL*Plus session as many times as
necessary until session 1 is done with its execution. What do you observe?
a) You can see that the optimizer uses a hash join on top of two index fast full scans.
Looking at the various reports, you can clearly see how the optimizer processes a
hash join by reading the driving index in memory first. This operation is quick.
Though you cannot see it run, it is already done the first time you can look at it.
Then the probe in performed on the index again. This operation takes more time.
Also note that cost information is provided in the execution plan.
Session 2:
---------SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@ep_monitor
set echo on
set long 10000000
set longchunksize 10000000
set linesize 200
set pagesize 1000
exec dbms_lock.sleep(8);

PL/SQL procedure successfully completed.


SQL>
SQL> select
dbms_sqltune.report_sql_monitor(sql_id=>'dkz7v96ym42c6',report_level
=>'ALL') from dual;
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time

:
:
:
:
:
:
:
:

EXECUTING
1
124
dkz7v96ym42c6
16777223
3253233075
04/02/2008 20:14:41
04/02/2008 20:14:49

Oracle Database 11g: SQL Tuning Workshop A - 161

Practice 5-1: Extracting Execution Plans (continued)


Last Refresh Time

04/02/2008 20:14:49

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
6.29 |
6.29 |
0.00 |
8 |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
====================================================================
==========
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Memory | Activity | Activity
Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) |
| (percent) |
(sample #)
|
====================================================================
====================================================================
==========
|
0 | SELECT STATEMENT
|
|
| 2131 |
|
|
1 |
|
|
|
|
|
1 |
SORT AGGREGATE
|
|
1 |
|
8 |
+0 |
1 |
0 |
|
25.00 | Cpu (2)
|
| -> 2 |
HASH JOIN
|
|
400M | 2131 |
8 |
+1 |
1 |
113M | 1138K |
75.00 | Cpu (6)
|
| -> 3 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
20000 |
|
|
|
| -> 4 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
5632 |
|
|
|
====================================================================
====================================================================
==========

SQL>
SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 162

Practice 5-1: Extracting Execution Plans (continued)


select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
124
dkz7v96ym42c6
16777223
3253233075
04/02/2008 20:14:41
04/02/2008 20:14:49
04/02/2008 20:14:51

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
7.86 |
7.86 |
0.00 |
10 |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
====================================================================
==========
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Memory | Activity | Activity
Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) |
| (percent) |
(sample #)
|
====================================================================
====================================================================
==========
|
0 | SELECT STATEMENT
|
|
| 2131 |
|
|
1 |
|
|
|
|
|
1 |
SORT AGGREGATE
|
|
1 |
|
8 |
+0 |
1 |
0 |
|
20.00 | Cpu (2)
|
| -> 2 |
HASH JOIN
|
|
400M | 2131 |
10 |
+1 |
1 |
133M | 1138K |
80.00 | Cpu (8)
|
|
3 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
20000 |
|
|
|
| -> 4 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
3 |
+8 |
1 |
6656 |
|
|
|
====================================================================
====================================================================
==========

Oracle Database 11g: SQL Tuning Workshop A - 163

Practice 5-1: Extracting Execution Plans (continued)


SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
124
dkz7v96ym42c6
16777223
3253233075
04/02/2008 20:14:41
04/02/2008 20:14:49
04/02/2008 20:14:55

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
12 |
12 |
0.00 |
15 |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
====================================================================
==========
| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Memory | Activity | Activity
Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) |
| (percent) |
(sample #)
|
====================================================================
====================================================================
==========
|
0 | SELECT STATEMENT
|
|
| 2131 |
|
|
1 |
|
|
|
|
|
1 |
SORT AGGREGATE
|
|
1 |
|
8 |
+0 |
1 |
0 |
|
14.29 | Cpu (2)
|

Oracle Database 11g: SQL Tuning Workshop A - 164

Practice 5-1: Extracting Execution Plans (continued)


| -> 2 |
HASH JOIN
|
|
400M | 2131 |
14 |
+1 |
1 |
184M | 1138K |
85.71 | Cpu (12)
|
|
3 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
20000 |
|
|
|
| -> 4 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
7 |
+8 |
1 |
9216 |
|
|
|
====================================================================
====================================================================
==========

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

EXECUTING
1
124
dkz7v96ym42c6
16777223
3253233075
04/02/2008 20:14:41
04/02/2008 20:14:49
04/02/2008 20:15:05

----------------------------------------| Elapsed |
Cpu
| Other
| Buffer |
| Time(s) | Time(s) | Waits(s) | Gets |
----------------------------------------|
22 |
22 |
0.01 |
28 |
-----------------------------------------

SQL Plan Monitoring Details


====================================================================
====================================================================
==========

Oracle Database 11g: SQL Tuning Workshop A - 165

Practice 5-1: Extracting Execution Plans (continued)


| Id
|
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Memory | Activity | Activity
Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) |
| (percent) |
(sample #)
|
====================================================================
====================================================================
==========
|
0 | SELECT STATEMENT
|
|
| 2131 |
|
|
1 |
|
|
|
|
|
1 |
SORT AGGREGATE
|
|
1 |
|
8 |
+0 |
1 |
0 |
|
8.33 | Cpu (2)
|
| -> 2 |
HASH JOIN
|
|
400M | 2131 |
24 |
+1 |
1 |
317M | 1138K |
91.67 | Cpu (22)
|
|
3 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
20000 |
|
|
|
| -> 4 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
17 |
+8 |
1 |
15872 |
|
|
|
====================================================================
====================================================================
==========

SQL> /
DBMS_SQLTUNE.REPORT_SQL_MONITOR(SQL_ID=>'DKZ7V96YM42C6',SESSION_ID=>
:SESSID,REPORT_LEVEL=>'ALL')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL Monitoring Report
SQL Text
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Global Information
Status
Instance ID
Session ID
SQL ID
SQL Execution ID
Plan Hash Value
Execution Started
First Refresh Time
Last Refresh Time

:
:
:
:
:
:
:
:
:

DONE (ALL ROWS)


1
124
dkz7v96ym42c6
16777223
3253233075
04/02/2008 20:14:41
04/02/2008 20:14:49
04/02/2008 20:15:11

Oracle Database 11g: SQL Tuning Workshop A - 166

Practice 5-1: Extracting Execution Plans (continued)


------------------------------------------------| Elapsed |
Cpu
| Other
| Fetch | Buffer |
| Time(s) | Time(s) | Waits(s) | Calls | Gets |
------------------------------------------------|
28 |
28 |
0.01 |
1 |
37 |
-------------------------------------------------

SQL Plan Monitoring Details


====================================================================
====================================================================
========
| Id |
Operation
|
Name
| Rows
| Cost |
Time
| Start | Starts |
Rows
| Memory | Activity | Activity
Detail |
|
|
|
| (Estim) |
|
Active(s) | Active |
| (Actual) | (Max) | (percent) |
(sample #)
|
====================================================================
====================================================================
========
| 0 | SELECT STATEMENT
|
|
| 2131 |
1 |
+30 |
1 |
1 |
|
|
|
| 1 |
SORT AGGREGATE
|
|
1 |
|
31 |
+0 |
1 |
1 |
|
10.00 | Cpu (3)
|
| 2 |
HASH JOIN
|
|
400M | 2131 |
30 |
+1 |
1 |
400M | 1138K |
90.00 | Cpu (27)
|
| 3 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
1 |
+8 |
1 |
20000 |
|
|
|
| 4 |
INDEX FAST FULL SCAN | TEST_C_INDX |
20000 |
13 |
23 |
+8 |
1 |
20000 |
|
|
|
====================================================================
====================================================================
========

SQL>

13) When your query is executed, what do you observe in your session 1?
a) Session 1 also reports the same execution plan as the one you observed in session
2.
Session 1:
---------
COUNT(*)

Oracle Database 11g: SQL Tuning Workshop A - 167

Practice 5-1: Extracting Execution Plans (continued)


---------400000000
Elapsed: 00:00:30.70
Execution Plan
---------------------------------------------------------Plan hash value: 3253233075
------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
6 | 2131
(99)| 00:00:22 |
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|
|* 2 |
HASH JOIN
|
|
400M| 2288M| 2131
(99)| 00:00:22 |
|* 3 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
|* 4 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("T1"."C"="T2"."C")
3 - filter("T1"."C"=1)
4 - filter("T2"."C"=1)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
90 consistent gets
0 physical reads
0 redo size
418 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>

14) In session 1, disable autotrace.


Session 1:
----------

Oracle Database 11g: SQL Tuning Workshop A - 168

Practice 5-1: Extracting Execution Plans (continued)


SQL> set autotrace off
SQL>

15) From your session 1, how can you ensure that you gather all execution plan statistics
for the following query without changing any session parameters? Implement your
solution.
select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1;
Session 1:
---------SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
test

@ep_execute_with_all
set echo on
set timing on
select /*+ gather_plan_statistics */ count(*) from test t1,
t2 where t1.c=t2.c and t1.c=1;

COUNT(*)
---------400000000
Elapsed: 00:00:59.95
SQL>

16) From your session 1, retrieve all execution plans corresponding to all the queries you
executed since the beginning of this lab. What is your conclusion?
a) The easiest way to find out all the plans is to look at the content of the SGA using
the dbms_xplan.display_cursor function. First, you must determine the
SQL_Ids used to represent your queries. You essentially have two queries, and
one that has two children. You should now understand what happened at step 4.
The fact that there was no cost information is probably due to the use of the rulebased optimizer instead of the cost-based one.
Session 1:
---------SQL> @ep_retrieve_all_plans
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> col sql_text format a50
SQL>
SQL> select sql_id,plan_hash_value,sql_text from v$sql where
sql_text like '%from test t1, test t2%';
SQL_ID
PLAN_HASH_VALUE SQL_TEXT
------------- --------------- -------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 169

Practice 5-1: Extracting Execution Plans (continued)


dkz7v96ym42c6
where t1.c=t

3253233075 select count(*) from test t1, test t2


2.c and t1.c=1

dkz7v96ym42c6
where t1.c=t

1643938535 select count(*) from test t1, test t2


2.c and t1.c=1

8w580dd6ncgqw
count(*) from

3253233075 select /*+ gather_plan_statistics */


test t1, test t2 where t1.c=t2.c and

t1.c=1
0w0va2d7hhtxa
test t1, tes

3253233075 explain plan for select count(*) from


t t2 where t1.c=t2.c and t1.c=1

dd09kf5dnp1gt
from v$sql

903671040 select sql_id,plan_hash_value,sql_text


where sql_text like '%from test t1,

test t2%'
32fqwuk16uf23
3253233075 EXPLAIN PLAN SET
STATEMENT_ID='PLUS2140495' FOR se
lect count(*) from test t1, test t2
where t1.c=t2.
c and t1.c=1

6 rows selected.
Elapsed: 00:00:00.02
SQL>
SQL> select * from
table(dbms_xplan.display_cursor('dkz7v96ym42c6',null,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID dkz7v96ym42c6, child number 0
------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
Plan hash value: 3253233075
------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
| 2131
(100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|

Oracle Database 11g: SQL Tuning Workshop A - 170

Practice 5-1: Extracting Execution Plans (continued)


|* 2 |
HASH JOIN
|
|
400M| 2288M| 2131
(99)| 00:00:22 |
|* 3 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
|* 4 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("T1"."C"="T2"."C")
3 - filter("T1"."C"=1)
4 - filter("T2"."C"=1)
SQL_ID dkz7v96ym42c6, child number 1
------------------------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
Plan hash value: 1643938535
-----------------------------------------| Id | Operation
| Name
|
-----------------------------------------|
0 | SELECT STATEMENT
|
|
|
1 | SORT AGGREGATE
|
|
|
2 |
NESTED LOOPS
|
|
|* 3 |
INDEX RANGE SCAN| TEST_C_INDX |
|* 4 |
INDEX RANGE SCAN| TEST_C_INDX |
-----------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("T1"."C"=1)
4 - access("T1"."C"="T2"."C")
Note
----- rule based optimizer used (consider using cbo)

49 rows selected.
Elapsed: 00:00:00.04
SQL>
SQL> select * from
table(dbms_xplan.display_cursor('8w580dd6ncgqw',null,'ADVANCED
ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 8w580dd6ncgqw, child number 0
-------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 171

Practice 5-1: Extracting Execution Plans (continued)


select /*+ gather_plan_statistics */ count(*) from test t1, test t2
where t1.c=t2.c and t1.c=1
Plan hash value: 3253233075
--------------------------------------------------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Starts | E-Rows |EBytes| Cost (%CPU)| E-Time
| A-Rows |
A-Time
| Buffers | OMem
| 1Mem | Used-Mem |
--------------------------------------------------------------------------------------------------------------------------------------------------------|
1 | SORT AGGREGATE
|
|
1 |
1 |
6 |
|
|
1 |00:00:59.94 |
90 |
|
|
|
|* 2 |
HASH JOIN
|
|
1 |
400M|
2288M| 2131 (99)| 00:00:22 |
400M|00:00:00.01 |
90 |
1155K| 1155K| 1115K (0)|
|* 3 |
INDEX FAST FULL SCAN| TEST_C_INDX |
1 | 20000 |
60000 |
13
(0)| 00:00:01 | 20000 |00:00:00.02 |
45 |
|
|
|
|* 4 |
INDEX FAST FULL SCAN| TEST_C_INDX |
1 | 20000 |
60000 |
13
(0)| 00:00:01 | 20000 |00:00:00.16 |
45 |
|
|
|
--------------------------------------------------------------------------------------------------------------------------------------------------------Query Block Name / Object Alias (identified by operation id):
------------------------------------------------------------1 - SEL$1
3 - SEL$1 / T1@SEL$1
4 - SEL$1 / T2@SEL$1
Outline Data
------------/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.1.0.6')
DB_VERSION('11.1.0.6')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX_FFS(@"SEL$1" "T1"@"SEL$1" ("TEST"."C"))
INDEX_FFS(@"SEL$1" "T2"@"SEL$1" ("TEST"."C"))
LEADING(@"SEL$1" "T1"@"SEL$1" "T2"@"SEL$1")
USE_HASH(@"SEL$1" "T2"@"SEL$1")
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 172

Practice 5-1: Extracting Execution Plans (continued)


2 - access("T1"."C"="T2"."C")
3 - filter("T1"."C"=1)
4 - filter("T2"."C"=1)
Column Projection Information (identified by operation id):
----------------------------------------------------------1
2
3
4

(#keys=0) COUNT(*)[22]
(#keys=1)
"T1"."C"[NUMBER,22]
"T2"."C"[NUMBER,22]

55 rows selected.
Elapsed: 00:00:00.12
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
col sql_text format a50
select sql_id,plan_hash_value,sql_text from v$sql where sql_text
like '%from test t1, test t2%';
select * from
table(dbms_xplan.display_cursor('dkz7v96ym42c6',null,'TYPICAL'));
select * from
table(dbms_xplan.display_cursor('8w580dd6ncgqw',null,'ADVANCED
ALLSTATS LAST'));

17) From session 1, try to retrieve your execution plans from the Automatic Workload
Repository. What happens and why?
a) You can use the previously found SQL_Ids to search through the
DBA_HIST_SQLTEXT view. You should see that right now, none of your
queries were stored in the AWR.
Session 1:
---------SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2

@ep_retrieve_awr
set echo on
set linesize 200
SELECT SQL_ID, SQL_TEXT FROM dba_hist_sqltext
WHERE SQL_ID in ('dkz7v96ym42c6','8w580dd6ncgqw');

no rows selected

Oracle Database 11g: SQL Tuning Workshop A - 173

Practice 5-1: Extracting Execution Plans (continued)


SQL>
Elapsed: 00:00:00.01
SQL>
-------------------------------------------------------------set echo on
set linesize 200
SELECT SQL_ID, SQL_TEXT FROM dba_hist_sqltext
WHERE SQL_ID in ('dkz7v96ym42c6','8w580dd6ncgqw');

18) How can you ensure that you retrieve your queries from the Automatic Workload
Repository? Implement your solution.
a) You must flush the SGA information to the AWR. You can use
DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT for this purpose.
Session 1:
---------SQL> @ep_save_awr
SQL> set echo on
SQL>
SQL> EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT('ALL');
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.27
SQL>
-------------------------------------------------------------set echo on
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT('ALL');

19) Verify that your solution works.


Session 1:
---------SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@ep_show_awr
set echo on
set linesize 200 pagesize 1000
SELECT PLAN_TABLE_OUTPUT
FROM
TABLE (DBMS_XPLAN.DISPLAY_AWR('dkz7v96ym42c6'));

Oracle Database 11g: SQL Tuning Workshop A - 174

Practice 5-1: Extracting Execution Plans (continued)


PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID dkz7v96ym42c6
-------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
Plan hash value: 1643938535
-----------------------------------------| Id | Operation
| Name
|
-----------------------------------------|
0 | SELECT STATEMENT
|
|
|
1 | SORT AGGREGATE
|
|
|
2 |
NESTED LOOPS
|
|
|
3 |
INDEX RANGE SCAN| TEST_C_INDX |
|
4 |
INDEX RANGE SCAN| TEST_C_INDX |
-----------------------------------------Note
----- rule based optimizer used (consider using cbo)
SQL_ID dkz7v96ym42c6
-------------------select count(*) from test t1, test t2 where t1.c=t2.c and t1.c=1
Plan hash value: 3253233075
------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
543
(100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|
|
2 |
HASH JOIN
|
|
100M|
572M|
543
(98)| 00:00:06 |
|
3 |
INDEX FAST FULL SCAN| TEST_C_INDX | 10000 | 30000 |
8
(0)| 00:00:01 |
|
4 |
INDEX FAST FULL SCAN| TEST_C_INDX | 10000 | 30000 |
8
(0)| 00:00:01 |
-------------------------------------------------------------------------------------

36 rows selected.
Elapsed: 00:00:00.04
SQL>
SQL> SELECT PLAN_TABLE_OUTPUT
2 FROM

Oracle Database 11g: SQL Tuning Workshop A - 175

Practice 5-1: Extracting Execution Plans (continued)


3 TABLE
(DBMS_XPLAN.DISPLAY_AWR('8w580dd6ncgqw',null,null,'TYPICAL ALLSTATS
LAST'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 8w580dd6ncgqw
-------------------select /*+ gather_plan_statistics */ count(*) from test t1, test t2
where t1.c=t2.c and t1.c=1
Plan hash value: 3253233075
-------------------------------------------------------------------------------------| Id | Operation
| Name
| E-Rows |E-Bytes| Cost
(%CPU)| E-Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2131 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|
|
2 |
HASH JOIN
|
|
400M| 2288M|
2131 (99)| 00:00:22 |
|
3 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
|
4 |
INDEX FAST FULL SCAN| TEST_C_INDX | 20000 | 60000 |
13
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Note
----- Warning: basic plan statistics not available. These are only
collected when:
* hint 'gather_plan_statistics' is used for the statement or
* parameter 'statistics_level' is set to 'ALL', at session or
system level

23 rows selected.
Elapsed: 00:00:00.02
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
SELECT PLAN_TABLE_OUTPUT
FROM
TABLE (DBMS_XPLAN.DISPLAY_AWR('dkz7v96ym42c6'));

Oracle Database 11g: SQL Tuning Workshop A - 176

Practice 5-1: Extracting Execution Plans (continued)


SELECT PLAN_TABLE_OUTPUT
FROM
TABLE (DBMS_XPLAN.DISPLAY_AWR('8w580dd6ncgqw',null,null,'TYPICAL
ALLSTATS LAST'));

20) Exit from both SQL*Plus sessions, and clean up your environment by executing the
ep_cleanup.sh script from one of your terminal sessions.
Session 1:
---------SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Explain_Plan]$ ./ep_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Wed Apr 2 20:19:37 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user ep cascade;
User dropped.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Explain_Plan]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Explain_Plan
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1

Oracle Database 11g: SQL Tuning Workshop A - 177

Practice 5-1: Extracting Execution Plans (continued)


export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @ep_cleanup.sql
-------------------------------------------------------------set echo on
drop user ep cascade;
exit;

Do not forget to exit from your session 2:


Session 2:
---------SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Explain_Plan]$

Oracle Database 11g: SQL Tuning Workshop A - 178

Practices for Lesson 6

Oracle Database 11g: SQL Tuning Workshop A - 179

Practice 6-1: Star Schema Tuning


In this practice, you optimize a query to use star transformation and access the benefits of
using this optimizer technique.
1) From a terminal session, connected as the oracle user, execute the
setup_star_schema_lab.sh script located in your
/home/oracle/solutions/Star_Schema_Tuning directory.
[oracle@edrsr33p1-orcl Star_Schema_Tuning]$
./setup_star_schema_lab.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Mar 21 00:17:20
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
Grant succeeded.
SQL> SQL>
User altered.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Star_Schema_Tuning]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Star_Schema_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
set echo on
grant dba to sh;

Oracle Database 11g: SQL Tuning Workshop A - 180

Practice 6-1: Star Schema Tuning (continued)


alter user sh identified by sh account unlock;
FIN!

2) From the same terminal window, start a SQL*Plus session connected as the SH user
and do not disconnect from it until this lab finishes. Before executing the following
SQL statement, ensure that you flush both the shared pool and the buffer cache to
avoid caching issues as much as possible. After this, analyze the execution of the
following query (You can use the query.sql script.):
SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog') AND
t.calendar_quarter_desc IN ('1999-01','1999-02','2000-03','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

What are your conclusions?


a) As you can see in the output of the execution plan, this query seems to use a large
number of bytes to access the SALES table. Basically, the optimizer performs a
full scan of this table. This might not be the best way to handle it.
[oracle@edrsr33p1-orcl Star_Schema_Tuning]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Mar 21 00:31:10
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @first_run
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> set pagesize 200
SQL> set linesize 250

Oracle Database 11g: SQL Tuning Workshop A - 181

Practice 6-1: Star Schema Tuning (continued)


SQL> set timing on
SQL> set autotrace on
SQL>
SQL>
2
3
4
5
6
7
8
9
10

SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,


SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','2000-03','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

CHANNEL_CLASS
SALES_AMOUNT
----------------------Indirect
987.3
Indirect

Montara
Indirect
412.83

CUST_CITY

CALENDA

------------------------------ ------- -------Quartzhill

1999-01

Arbuckle

1999-02

1999-02
Quartzhill

1618.01
1999-02

41 rows selected.
Elapsed: 00:00:00.68
Execution Plan
---------------------------------------------------------Plan hash value: 1647000731
------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
| Pstart| Pstop |
------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
| 1144 | 96096 |
935
(4)| 00:00:12 |
|
|
|
1 | HASH GROUP BY
|
| 1144 | 96096 |
935
(4)| 00:00:12 |
|
|
|* 2 |
HASH JOIN
|
| 6231 |
511K|
934
(3)| 00:00:12 |
|
|
|* 3 |
TABLE ACCESS FULL
| CHANNELS |
2 |
42 |
3
(0)| 00:00:01 |
|
|
|* 4 |
HASH JOIN
|
| 12462 |
766K|
930
(3)| 00:00:12 |
|
|
|
5 |
PART JOIN FILTER CREATE
| :BF0000
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 6 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 7 |
HASH JOIN
|
| 49822 | 2286K|
911
(3)| 00:00:11 |
|
|
|* 8 |
TABLE ACCESS FULL
| CUSTOMERS |
383 | 9958 |
406
(1)| 00:00:05 |
|
|
|
9 |
PARTITION RANGE JOIN-FILTER|
|
918K|
18M|
498
(4)| 00:00:06 |:BF0000|:BF0000|
| 10 |
TABLE ACCESS FULL
| SALES
|
918K|
18M|
498
(4)| 00:00:06 |:BF0000|:BF0000|
------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2
3
4
6

access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')
access("S"."TIME_ID"="T"."TIME_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
7 - access("S"."CUST_ID"="C"."CUST_ID")
8 - filter("C"."CUST_STATE_PROVINCE"='CA')

Statistics
---------------------------------------------------------16783 recursive calls
0 db block gets
5491 consistent gets
2021 physical reads
0 redo size
1888 bytes sent via SQL*Net to client
442 bytes received via SQL*Net from client

Oracle Database 11g: SQL Tuning Workshop A - 182

Practice 6-1: Star Schema Tuning (continued)


4
141
0
41

SQL*Net roundtrips to/from client


sorts (memory)
sorts (disk)
rows processed

SQL>
SQL>
-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;
set
set
set
set

pagesize 200
linesize 250
timing on
autotrace on

SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,


SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

3) Without modifying the SH schema, how can you improve the execution plan for the
query mentioned in step 2? Verify your solution and explain why it is probably a
better solution.
a) Enable star transformation in your session. In this step, you do not want to use a
temporary table for the star transformation. Looking at the previous execution
plan, the optimizer estimates the data that is to be manipulated in megabytes.
Using the star transformation as follows, the estimation is now expressed in
kilobytes. That is why this new execution plan is probably a much better
alternative. However, note that this time the CUSTOMERS table is accessed using
full scan twice. If the table is larger, the impact is significant.
SQL> @second_run
SQL> set echo on
SQL>
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;

Oracle Database 11g: SQL Tuning Workshop A - 183

Practice 6-1: Star Schema Tuning (continued)


System altered.
Elapsed: 00:00:00.20
SQL>
SQL> set pagesize 200
SQL> set linesize 250
SQL> set timing on
SQL> set autotrace on
SQL>
SQL>
SQL> ALTER SESSION SET star_transformation_enabled=TEMP_DISABLE;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL>
SQL> SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
2
SUM(s.amount_sold) sales_amount
3 FROM sales s, times t, customers c, channels ch
4 WHERE s.time_id = t.time_id
5 AND
s.cust_id = c.cust_id
6 AND
s.channel_id = ch.channel_id
7 AND
c.cust_state_province = 'CA'
8 AND
ch.channel_desc in ('Internet','Catalog')
9 AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
10 GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc;
CHANNEL_CLASS
SALES_AMOUNT
----------------------Indirect
13227.99
Indirect
1319
Indirect
7.27

Indirect
Indirect
1618.01
Indirect
412.83
Indirect
3058.27
Indirect
3263.93

CUST_CITY

CALENDA

------------------------------ ------- -------San Francisco

2000-04

Montara

2000-04

Cloverdale

2000-04

Pala
Montara

2000-03
1999-02

Quartzhill

1999-02

San Francisco

1999-01

Pala

1999-01

41 rows selected.
Elapsed: 00:00:00.33
Execution Plan
---------------------------------------------------------Plan hash value: 2525768690

Oracle Database 11g: SQL Tuning Workshop A - 184

Practice 6-1: Star Schema Tuning (continued)


--------------------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
|
Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3 |
252 |
983
(1)| 00:00:12 |
|
|
|
1 | HASH GROUP BY
|
|
3 |
252 |
983
(1)| 00:00:12 |
|
|
|* 2 |
HASH JOIN
|
|
3 |
252 |
982
(1)| 00:00:12 |
|
|
|* 3 |
HASH JOIN
|
|
7 |
441 |
978
(1)| 00:00:12 |
|
|
|* 4 |
HASH JOIN
|
|
27 | 1269 |
960
(1)| 00:00:12 |
|
|
|* 5 |
TABLE ACCESS FULL
| CUSTOMERS
|
383 | 9958 |
406
(1)| 00:00:05 |
|
|
|
6 |
PARTITION RANGE SUBQUERY
|
|
507 | 10647 |
553
(1)| 00:00:07
|KEY(SQ)|KEY(SQ)|
|
7 |
TABLE ACCESS BY LOCAL INDEX ROWID| SALES
|
507 | 10647 |
553
(1)| 00:00:07
|KEY(SQ)|KEY(SQ)|
|
8 |
BITMAP CONVERSION TO ROWIDS
|
|
|
|
|
|
|
|
|
9 |
BITMAP AND
|
|
|
|
|
|
|
|
| 10 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 11 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 12 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 13 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01 |
|
|
|* 14 |
BITMAP INDEX RANGE SCAN
| SALES_CHANNEL_BIX |
|
|
|
|KEY(SQ)|KEY(SQ)|
| 15 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 16 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 17 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 18 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 19 |
BITMAP INDEX RANGE SCAN
| SALES_TIME_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
| 20 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 21 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 22 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 23 |
TABLE ACCESS FULL
| CUSTOMERS
|
383 | 9958 |
406
(1)| 00:00:05 |
|
|
|* 24 |
BITMAP INDEX RANGE SCAN
| SALES_CUST_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
|* 25 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 26 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01 |
|
|
--------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2
3
4
5
13
14
18

19
23
24
25

26 -

access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
access("S"."TIME_ID"="T"."TIME_ID")
access("S"."CUST_ID"="C"."CUST_ID")
filter("C"."CUST_STATE_PROVINCE"='CA')
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')
access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
access("S"."TIME_ID"="T"."TIME_ID")
filter("C"."CUST_STATE_PROVINCE"='CA')
access("S"."CUST_ID"="C"."CUST_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')

Note
----- star transformation used for this statement

Statistics
---------------------------------------------------------17362 recursive calls
0 db block gets
36930 consistent gets

Oracle Database 11g: SQL Tuning Workshop A - 185

Practice 6-1: Star Schema Tuning (continued)


2130
0
1896
442
4
147
0
41

physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
-------------------------------------------------------------set echo on

alter system flush shared_pool;


alter system flush buffer_cache;
set
set
set
set

pagesize 200
linesize 250
timing on
autotrace on

ALTER SESSION SET star_transformation_enabled=TEMP_DISABLE;

SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,


SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

4) How would you enhance the previous optimization without changing the SH schema?
a) Let the optimizer decide if it is better to use a temporary table. You can try to set
the STAR_TRANSFORMATION_ENABLED parameter to TRUE.
SQL> @third_run
SQL> set echo on
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.10
SQL> alter system flush buffer_cache;

Oracle Database 11g: SQL Tuning Workshop A - 186

Practice 6-1: Star Schema Tuning (continued)


System altered.
Elapsed: 00:00:00.21
SQL>
SQL> set pagesize 200
SQL> set linesize 250
SQL> set timing on
SQL> set autotrace on
SQL>
SQL> ALTER SESSION SET star_transformation_enabled=TRUE;
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
2
SUM(s.amount_sold) sales_amount
3 FROM sales s, times t, customers c, channels ch
4 WHERE s.time_id = t.time_id
5 AND
s.cust_id = c.cust_id
6 AND
s.channel_id = ch.channel_id
7 AND
c.cust_state_province = 'CA'
8 AND
ch.channel_desc in ('Internet','Catalog')
9 AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
10 GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc;
CHANNEL_CLASS
SALES_AMOUNT
----------------------Indirect
13227.99
Indirect
1319

Indirect
3058.27
Indirect
3263.93

CUST_CITY

CALENDA

------------------------------ ------- -------San Francisco

2000-04

Montara

2000-04

San Francisco

1999-01

Pala

1999-01

41 rows selected.
Elapsed: 00:00:00.30
Execution Plan
---------------------------------------------------------Plan hash value: 163104418
----------------------------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
| Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3 |
219 |
985
(1)| 00:00:12
|
|
|
|
1 | TEMP TABLE TRANSFORMATION
|
|
|
|
|
|
|
|
|
2 |
LOAD AS SELECT
| SYS_TEMP_0FD9D660F_8947E |
|
|
|
|
|
|
|* 3 |
TABLE ACCESS FULL
| CUSTOMERS
|
383 | 9958 |
406
(1)| 00:00:05
|
|
|

Oracle Database 11g: SQL Tuning Workshop A - 187

Practice 6-1: Star Schema Tuning (continued)


|
4 |
HASH GROUP BY
|
|
3 |
219 |
579
(2)| 00:00:07
|
|
|
|* 5 |
HASH JOIN
|
|
3 |
219 |
578
(1)| 00:00:07
|
|
|
|* 6 |
HASH JOIN
|
|
7 |
364 |
574
(1)| 00:00:07
|
|
|
|* 7 |
HASH JOIN
|
|
28 | 1008 |
556
(1)| 00:00:07
|
|
|
|
8 |
TABLE ACCESS FULL
| SYS_TEMP_0FD9D660F_8947E |
383 | 5745 |
2
(0)| 00:00:01
|
|
|
|
9 |
PARTITION RANGE SUBQUERY
|
|
507 | 10647 |
553
(1)| 00:00:07
|KEY(SQ)|KEY(SQ)|
| 10 |
TABLE ACCESS BY LOCAL INDEX ROWID| SALES
|
507 | 10647 |
553
(1)| 00:00:07
|KEY(SQ)|KEY(SQ)|
| 11 |
BITMAP CONVERSION TO ROWIDS
|
|
|
|
|
|
|
|
| 12 |
BITMAP AND
|
|
|
|
|
|
|
|
| 13 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 14 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 15 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 16 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01
|
|
|
|* 17 |
BITMAP INDEX RANGE SCAN
| SALES_CHANNEL_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
| 18 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 19 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 20 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 21 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01
|
|
|
|* 22 |
BITMAP INDEX RANGE SCAN
| SALES_TIME_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
| 23 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 24 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 25 |
BUFFER SORT
|
|
|
|
|
|
|
|
| 26 |
TABLE ACCESS FULL
| SYS_TEMP_0FD9D660F_8947E |
1 |
13 |
2
(0)| 00:00:01
|
|
|
|* 27 |
BITMAP INDEX RANGE SCAN
| SALES_CUST_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
|* 28 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01
|
|
|
|* 29 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01
|
|
|
----------------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3
5
6
7
16
17
21

22 27 28 29 -

filter("C"."CUST_STATE_PROVINCE"='CA')
access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
access("S"."TIME_ID"="T"."TIME_ID")
access("S"."CUST_ID"="C0")
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')
access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
access("S"."TIME_ID"="T"."TIME_ID")
access("S"."CUST_ID"="C0")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')

Note
----- star transformation used for this statement

Statistics
---------------------------------------------------------17911 recursive calls
19 db block gets
35577 consistent gets
2157 physical reads
1616 redo size
1896 bytes sent via SQL*Net to client
442 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client

Oracle Database 11g: SQL Tuning Workshop A - 188

Practice 6-1: Star Schema Tuning (continued)


155
0
41

sorts (memory)
sorts (disk)
rows processed

SQL>
SQL>

-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;
set
set
set
set

pagesize 200
linesize 250
timing on
autotrace on

ALTER SESSION SET star_transformation_enabled=TRUE;


SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

5) How do you eliminate one table access on the CUSTOMERS table from the previous
execution plan for the same SELECT statement seen in step 3?
a) Create a bitmap join index between the SALES and CUSTOMERS tables.
6) Try to apply your finding. What happens and why?
a) Because the CUSTOMERS_PK primary key constraint is not enforced, it is not
possible to create a bitmap join index between the SALES and CUSTOMERS
tables.
SQL> @create_bji.sql
SQL>
SQL> CREATE BITMAP INDEX sales_c_state_bjix ON
sales(customers.cust_state_province) FROM sales, customers WHERE
sales.cust_id=customers.cust_id
2 LOCAL NOLOGGING COMPUTE STATISTICS;
CREATE BITMAP INDEX sales_c_state_bjix ON
sales(customers.cust_state_province) FROM sales, customers WHERE
sales.cust_id=customers.cust_id

Oracle Database 11g: SQL Tuning Workshop A - 189

Practice 6-1: Star Schema Tuning (continued)


*
ERROR at line 1:
ORA-25954: missing primary key or unique constraint on dimension

SQL>
-------------------------------------------------------------set echo on
CREATE BITMAP INDEX sales_c_state_bjix ON
sales(customers.cust_state_province) FROM sales, customers WHERE
sales.cust_id=customers.cust_id
LOCAL NOLOGGING COMPUTE STATISTICS;

7) Fix the issue you found and apply your solution from step 5 again.
a) You need to ENABLE VALIDATE the CUSTOMERS_PK constraint before you
can create the bitmap join index.
SQL> @recreate_bji
SQL>
SQL> alter table customers enable constraint customers_pk;
Table altered.
SQL>
SQL> CREATE BITMAP INDEX sales_c_state_bjix ON
sales(customers.cust_state_province) FROM sales, customers WHERE
sales.cust_id=customers.cust_id
2 LOCAL NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL>
-------------------------------------------------------------set echo on
alter table customers enable constraint customers_pk;
CREATE BITMAP INDEX sales_c_state_bjix ON
sales(customers.cust_state_province) FROM sales, customers WHERE
sales.cust_id=customers.cust_id
LOCAL NOLOGGING COMPUTE STATISTICS;

8) Verify that you solved the problem from step 5.


SQL> @fourth_run

Oracle Database 11g: SQL Tuning Workshop A - 190

Practice 6-1: Star Schema Tuning (continued)


SQL> set echo on
SQL>
SQL> ALTER SESSION SET star_transformation_enabled=TRUE;
Session altered.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> set pagesize 200
SQL> set linesize 250
SQL> set timing on
SQL> set autotrace on
SQL>
SQL> SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
2
SUM(s.amount_sold) sales_amount
3 FROM sales s, times t, customers c, channels ch
4 WHERE s.time_id = t.time_id
5 AND
s.cust_id = c.cust_id
6 AND
s.channel_id = ch.channel_id
7 AND
c.cust_state_province = 'CA'
8 AND
ch.channel_desc in ('Internet','Catalog')
9 AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
10 GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc;
CHANNEL_CLASS
SALES_AMOUNT
----------------------Indirect
987.3
Indirect
241.2
Indirect

Indirect
1618.01
Indirect
412.83

CUST_CITY

CALENDA

------------------------------ ------- -------Quartzhill

1999-01

Arbuckle

1999-02

Pala

2000-03

Montara

1999-02

Quartzhill

1999-02

41 rows selected.
Elapsed: 00:00:00.23
Execution Plan
---------------------------------------------------------Plan hash value: 632695221
----------------------------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 191

Practice 6-1: Star Schema Tuning (continued)


| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)| Time
|
Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
498 | 41832 |
557
(2)| 00:00:07 |
|
|
|
1 | HASH GROUP BY
|
|
498 | 41832 |
557
(2)| 00:00:07 |
|
|
|* 2 |
HASH JOIN
|
|
498 | 41832 |
556
(2)| 00:00:07 |
|
|
|* 3 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01 |
|
|
|* 4 |
HASH JOIN
|
|
996 | 62748 |
552
(1)| 00:00:07 |
|
|
|* 5 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 6 |
HASH JOIN
|
| 3984 |
182K|
533
(1)| 00:00:07 |
|
|
|* 7 |
TABLE ACCESS FULL
| CUSTOMERS
|
383 | 9958 |
406
(1)| 00:00:05 |
|
|
|
8 |
PARTITION RANGE SUBQUERY
|
| 73467 | 1506K|
126
(1)| 00:00:02
|KEY(SQ)|KEY(SQ)|
|
9 |
TABLE ACCESS BY LOCAL INDEX ROWID| SALES
| 73467 | 1506K|
126
(1)| 00:00:02
|KEY(SQ)|KEY(SQ)|
| 10 |
BITMAP CONVERSION TO ROWIDS
|
|
|
|
|
|
|
|
| 11 |
BITMAP AND
|
|
|
|
|
|
|
|
| 12 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 13 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 14 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 15 |
TABLE ACCESS FULL
| CHANNELS
|
2 |
42 |
3
(0)| 00:00:01 |
|
|
|* 16 |
BITMAP INDEX RANGE SCAN
| SALES_CHANNEL_BIX |
|
|
|
|KEY(SQ)|KEY(SQ)|
| 17 |
BITMAP MERGE
|
|
|
|
|
|
|
|
| 18 |
BITMAP KEY ITERATION
|
|
|
|
|
|
|
|
| 19 |
BUFFER SORT
|
|
|
|
|
|
|
|
|* 20 |
TABLE ACCESS FULL
| TIMES
|
365 | 5840 |
18
(0)| 00:00:01 |
|
|
|* 21 |
BITMAP INDEX RANGE SCAN
| SALES_TIME_BIX
|
|
|
|
|KEY(SQ)|KEY(SQ)|
|* 22 |
BITMAP INDEX SINGLE VALUE
| SALES_C_STATE_BJIX |
|
|
|
|KEY(SQ)|KEY(SQ)|
---------------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2
3
4
5

6
7
15
16
20

21 22 -

access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')
access("S"."TIME_ID"="T"."TIME_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
access("S"."CUST_ID"="C"."CUST_ID")
filter("C"."CUST_STATE_PROVINCE"='CA')
filter("CH"."CHANNEL_DESC"='Catalog' OR "CH"."CHANNEL_DESC"='Internet')
access("S"."CHANNEL_ID"="CH"."CHANNEL_ID")
filter("T"."CALENDAR_QUARTER_DESC"='1999-01' OR "T"."CALENDAR_QUARTER_DESC"='1999-02' OR
"T"."CALENDAR_QUARTER_DESC"='2000-03' OR "T"."CALENDAR_QUARTER_DESC"='2000-04')
access("S"."TIME_ID"="T"."TIME_ID")
access("S"."SYS_NC00008$"='CA')

Note
----- star transformation used for this statement

Statistics
---------------------------------------------------------18151 recursive calls
0 db block gets
8759 consistent gets
2002 physical reads
0 redo size
1888 bytes sent via SQL*Net to client
442 bytes received via SQL*Net from client
4 SQL*Net roundtrips to/from client
154 sorts (memory)
0 sorts (disk)

Oracle Database 11g: SQL Tuning Workshop A - 192

Practice 6-1: Star Schema Tuning (continued)


41

rows processed

SQL>
-------------------------------------------------------------set echo on
ALTER SESSION SET star_transformation_enabled=TRUE;
alter system flush shared_pool;
alter system flush buffer_cache;
set
set
set
set

pagesize 200
linesize 250
timing on
autotrace on

SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,


SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;

9) Determine how the system could dynamically determine which SALES partitions to
access for the previous query.
a) View the OTHER column of PLAN_TABLE for the PARTITION RANGE
operation.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@dynamic_partition_pruning
set autotrace off
set timing off
set long 4000
alter session set star_transformation_enabled=true;

Session altered.
SQL>
SQL>
2
3
4
5
6
7
8

explain plan for


SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'

Oracle Database 11g: SQL Tuning Workshop A - 193

Practice 6-1: Star Schema Tuning (continued)


9 AND
ch.channel_desc in ('Internet','Catalog')
10 AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
11 GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc;
Explained.
SQL>
SQL> SELECT other
2 FROM plan_table
3 WHERE operation='PARTITION RANGE';
OTHER
------------------------------------------------------------------------------SELECT distinct TBL$OR$IDX$PART$NUM("SALES", 0, d#, p#,
PAP_ALIAS_0."PAP_COL_ALI
AS_0") FROM (SELECT /*+ SEMIJOIN_DRIVER */ "T"."TIME_ID"
"PAP_COL_ALIAS_0" FROM
"TIMES" "T" WHERE "T"."CALENDAR_QUARTER_DESC"='1999-01' OR
"T"."CALENDAR_QUARTER
_DESC"='1999-02' OR "T"."CALENDAR_QUARTER_DESC"='2000-03' OR
"T"."CALENDAR_QUART
ER_DESC"='2000-04') PAP_ALIAS_0 ORDER BY 1

SQL>

-------------------------------------------------------------set echo on
set autotrace off
set timing off
set long 4000
alter session set star_transformation_enabled=true;
explain plan for
SELECT ch.channel_class, c.cust_city, t.calendar_quarter_desc,
SUM(s.amount_sold) sales_amount
FROM sales s, times t, customers c, channels ch
WHERE s.time_id = t.time_id
AND
s.cust_id = c.cust_id
AND
s.channel_id = ch.channel_id
AND
c.cust_state_province = 'CA'
AND
ch.channel_desc in ('Internet','Catalog')
AND
t.calendar_quarter_desc IN ('1999-01','1999-02','200003','2000-04')
GROUP BY ch.channel_class, c.cust_city, t.calendar_quarter_desc;
SELECT other
FROM plan_table
WHERE operation='PARTITION RANGE';

Oracle Database 11g: SQL Tuning Workshop A - 194

Practice 6-1: Star Schema Tuning (continued)


10) Clean up your environment by removing the index you created and returning the
constraint to its original state.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@cleanup_star_schema_lab
set echo on
set timing off
set autotrace off
drop index sales_c_state_bjix;

Index dropped.
SQL>
SQL> alter table customers enable novalidate constraint
customers_pk;
Table altered.
SQL>
SQL> connect / as sysdba
Connected.
SQL>
SQL> revoke dba from sh;
Revoke succeeded.
SQL>
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Star_Schema_Tuning]$
-------------------------------------------------------------set echo on
set timing off
set autotrace off
drop index sales_c_state_bjix;
alter table customers enable novalidate constraint customers_pk;
connect / as sysdba
revoke dba from sh;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 195

Practices for Lesson 7

Oracle Database 11g: SQL Tuning Workshop A - 196

Practice 7-1: Using System Statistics


In this practice, you manipulate system statistics and show that they are important for the
optimizer to select the correct execution plans.
1. Connected as the oracle user in a terminal session, execute the
sysstats_setup.sh script located in your
$HOME/solutions/System_Stats directory. This script creates a user
called JFV and some tables used throughout this lab. The script also makes sure
that object statistics are correctly gathered.
[oracle@edrsr33p1-orcl ~]$ cd $HOME/solutions/System_Stats
[oracle@edrsr33p1-orcl System_Stats]$
[oracle@edrsr33p1-orcl System_Stats]$ ./sysstats_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 19:11:42
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect / as sysdba;
Connected.
SQL>
SQL> drop user jfv cascade;
User dropped.
SQL>
SQL> create user jfv identified by jfv default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant connect, resource, dba to jfv;
Grant succeeded.
SQL>
SQL>
SQL> connect jfv/jfv
Connected.
SQL>
SQL> drop table t purge;
drop table t purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

Oracle Database 11g: SQL Tuning Workshop A - 197

Practice 7-1: Using System Statistics (continued)


SQL>
SQL> drop table z purge;
drop table z purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table t(c number);
Table created.
SQL>
SQL> insert into t values (1);
1 row created.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> insert into t select * from t;
1 row created.
SQL>
SQL> /
2 rows created.
SQL> /
4 rows created.
SQL> /
8 rows created.
SQL> /
16 rows created.
SQL> /
32 rows created.
SQL> /
64 rows created.
SQL> /

Oracle Database 11g: SQL Tuning Workshop A - 198

Practice 7-1: Using System Statistics (continued)


128 rows created.
SQL> /
256 rows created.
SQL> /
512 rows created.
SQL> /
1024 rows created.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> insert into t select * from t;
2048 rows created.
SQL>
SQL> /
4096 rows created.
SQL> /
8192 rows created.
SQL> /
16384 rows created.
SQL> /
32768 rows created.
SQL> /
65536 rows created.
SQL> /
131072 rows created.
SQL> /
262144 rows created.
SQL>
SQL> commit;
Commit complete.

Oracle Database 11g: SQL Tuning Workshop A - 199

Practice 7-1: Using System Statistics (continued)


SQL>
SQL> create table z(d number);
Table created.
SQL>
SQL> begin
2
for i in 1..100 loop
3
insert into z values (i);
4
end loop;
5
commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> create unique index iz on z(d);
Index created.
SQL>
SQL> execute dbms_stats.gather_table_stats('JFV','T',cascade=>true);
PL/SQL procedure successfully completed.
SQL>
SQL> execute dbms_stats.gather_table_stats('JFV','Z',cascade=>true);
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL> connect / as sysdba;
Connected.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> execute dbms_stats.delete_system_stats;
PL/SQL procedure successfully completed.
SQL>
SQL> execute DBMS_STATS.SET_SYSTEM_STATS (pname => 'cpuspeednw',
pvalue => 0);
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 200

Practice 7-1: Using System Statistics (continued)


SQL>
SQL> select sname,pname,pval1 from aux_stats$;
SNAME
PVAL1
--------------------------------SYSSTATS_INFO
SYSSTATS_INFO
SYSSTATS_INFO
SYSSTATS_INFO
1
SYSSTATS_MAIN
0
SYSSTATS_MAIN
10
SYSSTATS_MAIN
4096
SYSSTATS_MAIN
SYSSTATS_MAIN
SYSSTATS_MAIN
SYSSTATS_MAIN

PNAME

SNAME
PVAL1
--------------------------------SYSSTATS_MAIN
SYSSTATS_MAIN

PNAME

------------------------------ -----STATUS
DSTART
DSTOP
FLAGS
CPUSPEEDNW
IOSEEKTIM
IOTFRSPEED
SREADTIM
MREADTIM
CPUSPEED
MBRC

------------------------------ -----MAXTHR
SLAVETHR

13 rows selected.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl System_Stats]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/System_Stats
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @sysstats_setup.sql

Oracle Database 11g: SQL Tuning Workshop A - 201

Practice 7-1: Using System Statistics (continued)


[oracle@edrsr33p1-orcl System_Stats]$
-------------------------------------------------------------set echo on
connect / as sysdba;
drop user jfv cascade;
create user jfv identified by jfv default tablespace users temporary
tablespace temp;
grant connect, resource, dba to jfv;

connect jfv/jfv
drop table t purge;
drop table z purge;
create table t(c number);
insert into t values (1);
commit;
insert into t select * from t;
/
/
/
/
/
/
/
/
/
/
commit;
insert into t select * from t;
/
/
/
/
/
/
/
commit;
create table z(d number);

Oracle Database 11g: SQL Tuning Workshop A - 202

Practice 7-1: Using System Statistics (continued)


begin
for i in 1..100 loop
insert into z values (i);
end loop;
commit;
end;
/
create unique index iz on z(d);
execute dbms_stats.gather_table_stats('JFV','T',cascade=>true);
execute dbms_stats.gather_table_stats('JFV','Z',cascade=>true);

connect / as sysdba;
alter system flush shared_pool;
alter system flush buffer_cache;
execute dbms_stats.delete_system_stats;
execute DBMS_STATS.SET_SYSTEM_STATS (pname => 'cpuspeednw', pvalue
=> 0);
select sname,pname,pval1 from aux_stats$;
exit;

2. From your terminal session, connect as the JFV user in the SQL*Plus session.
After this, execute the following statement and determine how long it takes to
execute:
select /* Without system stats */ count(*)
from t,z
where t.c=z.d;
[oracle@edrsr33p1-orcl System_Stats]$ sqlplus jfv/jfv
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 19:11:57
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @select_without_sysstats
SQL>
SQL> set timing on
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 203

Practice 7-1: Using System Statistics (continued)


SQL> select /* Without system stats */ count(*)
2 from t,z
3 where t.c=z.d;
COUNT(*)
---------524288
Elapsed: 00:00:00.24
SQL>
SQL> set timing off
SQL>
SQL>
-------------------------------------------------------------set echo on
set timing on
select /* Without system stats */ count(*)
from t,z
where t.c=z.d;
set timing off

3. Determine the execution plan used to execute the previous statement. In addition,
determine the optimizers cost, CPU cost, and I/O cost for the previous execution.
What do you observe?
a) The optimizer does not use CPU costing. This is because system statistics were
deleted during the first step of this lab. The plan chosen by the optimizer might
not be the best one.
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------SQL_ID 6avdu58tamzju, child number 0
------------------------------------select /* Without system stats */ count(*) from t,z where t.c=z.d
Plan hash value: 3698032250
-----------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost |
-----------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
134 |
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 204

Practice 7-1: Using System Statistics (continued)


|
2 |
NESTED LOOPS
|
|
524K| 3072K|
134 |
|
3 |
TABLE ACCESS FULL| T
|
524K| 1536K|
134 |
|* 4 |
INDEX UNIQUE SCAN| IZ
|
1 |
3 |
|
-----------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------4 - access("T"."C"="Z"."D")
Note
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------- cpu costing is off (consider enabling it)

25 rows selected.
SQL>
SQL> col operations
format a20
SQL> col object_name format a11
SQL> col options
format a15
SQL> col cost_cpu_io format a30
SQL>
SQL>
SQL> select operation operations, object_name, options,
2
cost||' -- '||cpu_cost||' -- '||io_cost cost_cpu_io
3 from (select * from v$sql_plan where address in (select address
4
from v$sql
5
where sql_text
like '%system stats%' and
6
sql_text
not like '%connect%'));
OPERATIONS
-----------------------------SELECT STATEMENT
SORT
NESTED LOOPS
TABLE ACCESS
INDEX

OBJECT_NAME OPTIONS
COST_CPU_IO
----------- --------------- -------------------

AGGREGATE
T
IZ

FULL
UNIQUE SCAN

134 -- --- -134 -- -- 134


134 -- -- 134
-- --

SQL>
-------------------------------------------------------------set echo on
select * from table(dbms_xplan.display_cursor);
col operations format a20
col object_name format a11
col options
format a15

Oracle Database 11g: SQL Tuning Workshop A - 205

Practice 7-1: Using System Statistics (continued)


col cost_cpu_io format a30
select operation operations, object_name, options,
cost||' -- '||cpu_cost||' -- '||io_cost cost_cpu_io
from (select * from v$sql_plan where address in (select address
from v$sql
where sql_text like
'%system stats%' and
sql_text not
like '%connect%'));

4. How can you ensure that the optimizer finds a better plan during future executions
of the same statement? Implement your solution.
b) Gather system statistics again. Because you do not have yet a real workload, you
can gather system statistics in NOWORKLOAD mode.
SQL> connect / as sysdba;
Connected.
SQL> @gather_system_stats
SQL> set echo on
SQL>
SQL> execute DBMS_STATS.GATHER_SYSTEM_STATS(gathering_mode =>
'NOWORKLOAD');
PL/SQL procedure successfully completed.
SQL>
SQL> select sname,pname,pval1 from aux_stats$;
SNAME
PVAL1
--------------------------------SYSSTATS_INFO
SYSSTATS_INFO
SYSSTATS_INFO
SYSSTATS_INFO
1
SYSSTATS_MAIN
1893.021
SYSSTATS_MAIN
8.043
SYSSTATS_MAIN
4096
SYSSTATS_MAIN
SYSSTATS_MAIN
SYSSTATS_MAIN
SYSSTATS_MAIN

PNAME
------------------------------ -----STATUS
DSTART
DSTOP
FLAGS
CPUSPEEDNW
IOSEEKTIM
IOTFRSPEED
SREADTIM
MREADTIM
CPUSPEED
MBRC

SNAME
PNAME
PVAL1
------------------------------ ------------------------------ --------SYSSTATS_MAIN
MAXTHR

Oracle Database 11g: SQL Tuning Workshop A - 206

Practice 7-1: Using System Statistics (continued)


SYSSTATS_MAIN

SLAVETHR

13 rows selected.
SQL>
-------------------------------------------------------------set echo on
execute DBMS_STATS.GATHER_SYSTEM_STATS(gathering_mode =>
'NOWORKLOAD');
select sname,pname,pval1 from aux_stats$;

5. Before verifying your solution, you should flush the System Global Area (SGA)
pools, such as the shared pool and the buffer cache. This is done to prevent
interferences from the previous steps.
SQL> @flush_sga
SQL>
SQL> set echo on
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL>
-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;

6. Connected as the JFV user again, check your solution against the following
query:
select /* With system stats */ count(*)
from t,z
where t.c=z.d;

What do you observe?


c) The optimizer can make a better decision because it was able to use meaningful
system statistics. You can see that the execution time is now half the value it was
previously, and the execution plan now includes CPU costing information.

Oracle Database 11g: SQL Tuning Workshop A - 207

Practice 7-1: Using System Statistics (continued)


SQL> connect jfv/jfv
Connected.
SQL> @select_with_sysstats
SQL> set timing on
SQL>
SQL> select /* With system stats */ count(*)
2 from t,z
3 where t.c=z.d;
COUNT(*)
---------524288
Elapsed: 00:00:00.11
SQL>
SQL> set timing off
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------SQL_ID 2x55txn3742by, child number 0
------------------------------------select /* With system stats */ count(*) from t,z where t.c=z.d
Plan hash value: 2407521827
--------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
--------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
272 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
6 |
|
|
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------|* 2 |
HASH JOIN
|
|
524K| 3072K|
272
(3)|
00:00:03 |
|
3 |
INDEX FULL SCAN | IZ
|
100 |
300 |
1
(0)|
00:00:01 |
|
4 |
TABLE ACCESS FULL| T
|
524K| 1536K|
267
(2)|
00:00:03 |
--------------------------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 208

Practice 7-1: Using System Statistics (continued)


2 - access("T"."C"="Z"."D")
21 rows selected.
SQL>
SQL> col operations
format a20
SQL> col object_name format a11
SQL> col options
format a15
SQL> col cost_cpu_io format a30
SQL>
SQL>
SQL> select operation operations, object_name, options,
2
cost||' -- '||cpu_cost||' -- '||io_cost cost_cpu_io
3 from (select * from v$sql_plan where address in (select address
4
from v$sql
5
where sql_text
like '%system stats%' and
6
sql_text
not like '%connect%'));
OPERATIONS
-----------------------------SELECT STATEMENT
SORT
HASH JOIN
264
INDEX
TABLE ACCESS
263

OBJECT_NAME OPTIONS
COST_CPU_IO
----------- --------------- -------------------

IZ
T

AGGREGATE

272 -- --- -272 -- 146844065 --

FULL SCAN
FULL

1 -- 27121 -- 1
267 -- 84867339 --

SQL>
-------------------------------------------------------------set timing on
select /* With system stats */ count(*)
from t,z
where t.c=z.d;
set timing off
-------------------------------------------------------------set echo on
select * from table(dbms_xplan.display_cursor);
col
col
col
col

operations
object_name
options
cost_cpu_io

format
format
format
format

a20
a11
a15
a30

select operation operations, object_name, options,

Oracle Database 11g: SQL Tuning Workshop A - 209

Practice 7-1: Using System Statistics (continued)


cost||' -- '||cpu_cost||' -- '||io_cost cost_cpu_io
from (select * from v$sql_plan where address in (select address
from v$sql
where sql_text like
'%system stats%' and
sql_text not
like '%connect%'));

7. Exit from your SQL*Plus session and clean up your environment for this lab by
executing the systats_cleanup.sh script.
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl System_Stats]$ ./sysstats_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 19:13:54
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user jfv cascade;
User dropped.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl System_Stats]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/System_Stats
export ORACLE_SID=orcl

Oracle Database 11g: SQL Tuning Workshop A - 210

Practice 7-1: Using System Statistics (continued)


export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @sysstats_cleanup.sql
-------------------------------------------------------------set echo on
drop user jfv cascade;
alter system flush shared_pool;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 211

Practice 7-2: Automatic Statistics Gathering


In this practice, you manipulate object statistics to see their incidences on the execution
plans of your SQL statements. Note: All scripts needed for this lab can be found in your
$HOME/solutions/Automatic_Gather_Stats directory.
1) From a terminal window connected as the oracle user, set up your environment for
this lab by executing the ags_setup.sh script. This script creates a user called
AGS that you use throughout this lab. The script also creates a table called EMP and
an index.
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$ ./ags_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Apr 7 15:55:28 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user ags cascade;
drop user ags cascade
*
ERROR at line 1:
ORA-01918: user 'AGS' does not exist

SQL>
SQL> create user ags identified by ags;
User created.
SQL>
SQL> grant dba to ags;
Grant succeeded.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

--- Turn off AUTOTASK


-alter system set "_enable_automatic_maintenance"=0;

System altered.

Oracle Database 11g: SQL Tuning Workshop A - 212

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
SQL> connect ags/ags
Connected.
SQL>
SQL> drop table emp purge;
drop table emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table emp
2 (
3
empno
number,
4
ename
varchar2(20),
5
phone
varchar2(20),
6
deptno number
7 );
Table created.
SQL>
SQL>
SQL> insert into emp
2
with tdata as
3
(select rownum empno
4
from all_objects
5
where rownum <= 1000)
6
select rownum,
7
dbms_random.string ('u', 20),
8
dbms_random.string ('u', 20),
9
case
10
when rownum/100000 <= 0.001 then mod(rownum,
10)
11
else 10
12
end
13
from tdata a, tdata b
14
where rownum <= 100000;
100000 rows created.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> create index emp_i1 on emp(deptno);
Index created.
SQL>
SQL> exec dbms_stats.delete_schema_stats('AGS');
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 213

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_Gather_Stats
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba @ags_setup.sql
-------------------------------------------------------------set echo on
drop user ags cascade;
create user ags identified by ags;
grant dba to ags;
alter system flush shared_pool;
--- Turn off AUTOTASK
-alter system set "_enable_automatic_maintenance"=0;
connect ags/ags
drop table emp purge;
create table emp
(
empno
number,
ename
varchar2(20),
phone
varchar2(20),
deptno number
);

insert into emp


with tdata as

Oracle Database 11g: SQL Tuning Workshop A - 214

Practice 7-2: Automatic Statistics Gathering (continued)


(select rownum empno
from all_objects
where rownum <= 1000)
select rownum,
dbms_random.string ('u', 20),
dbms_random.string ('u', 20),
case
when rownum/100000 <= 0.001 then mod(rownum, 10)
else 10
end
from tdata a, tdata b
where rownum <= 100000;
commit;
create index emp_i1 on emp(deptno);
exec dbms_stats.delete_schema_stats('AGS');
exit;

2) Connect as the AGS user under a SQL*Plus session.


[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$ sqlplus ags/ags
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Apr 7 15:55:47 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

3) Determine the distribution of the deptno values found in the EMP table. What do
you observe?
a) You can see that there are 11 different department values, each repeating 0.01%
of the time except value 10 that repeats 99.9% of the time.
SQL> @show_deptno_distribution
SQL>
SQL> select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent
2 from emp, (select max(empno) nr
3
from emp)
4 group by deptno, nr
5 order by deptno;
DEPTNO CNT_PER_DEPTNO DEPTNO_PERCENT
---------- -------------- -------------0
10
.01

Oracle Database 11g: SQL Tuning Workshop A - 215

Practice 7-2: Automatic Statistics Gathering (continued)


1
2
3
4
5
6
7
8
9
10

10
10
10
10
10
10
10
10
10
99900

.01
.01
.01
.01
.01
.01
.01
.01
.01
99.9

11 rows selected.
SQL>
-------------------------------------------------------------set echo on
select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent
from emp, (select max(empno) nr
from emp)
group by deptno, nr
order by deptno;

4) Determine if there are histograms available on any column of the EMP table. What do
you observe?
a) Currently, there are no histograms defined on any column of the EMP table.
SQL>
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
NONE
NONE
NONE

SQL>
-------------------------------------------------------------set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

Oracle Database 11g: SQL Tuning Workshop A - 216

Practice 7-2: Automatic Statistics Gathering (continued)


5) Determine if you currently have statistics gathered on your EMP table. What do you
observe?
a) Currently, there are no statistics gathered on your EMP table.
SQL> @check_table_last_analyzed
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
2 FROM user_tables
3 WHERE table_name = 'EMP';
LAST_ANAL SAMPLE_SIZE MON
NUM_ROWS
BLOCKS TABLE_NAME
--------- ----------- --- ---------- ---------- ----------------------------YES
EMP
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
FROM user_tables
WHERE table_name = 'EMP';

6) Determine if you currently have statistics gathered on the index created on top of the
EMP table. What do you observe?
a) Currently, EMP_I1 has no statistics gathered.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@check_index_last_analyzed
set echo on
set linesize 200 pagesize 1000
SELECT index_name name,num_rows,
last_analyzed,distinct_keys,leaf_blocks,
avg_leaf_blocks_per_key,join_index
FROM user_indexes
WHERE table_name = 'EMP';

NAME
NUM_ROWS LAST_ANAL DISTINCT_KEYS
LEAF_BLOCKS AVG_LEAF_BLOCKS_PER_KEY JOI
------------------------------ ---------- --------- ------------- ---------- ----------------------- --EMP_I1
NO

Oracle Database 11g: SQL Tuning Workshop A - 217

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
SELECT index_name name,num_rows,
last_analyzed,distinct_keys,leaf_blocks,
avg_leaf_blocks_per_key,join_index
FROM user_indexes
WHERE table_name = 'EMP';

7) Execute the following two statements and determine their execution plans:
select count(*), max(empno) from emp where deptno = 9;
select count(*), max(empno) from emp where deptno = 10;

What do you observe and why?


a) You see that for literal 9, the optimizer decided to use the index whereas for literal
10, the optimizer decided to do TABLE ACCESS FULL. The optimizer
determined the correct plans in both cases. This is because dynamic sampling was
used, as there were no statistics gathered on both objects.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@check_exec_plans
set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;

COUNT(*) MAX(EMPNO)
---------- ---------10
99

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
2
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
10 |
260 |
2
(0)| 00:00:01 |

Oracle Database 11g: SQL Tuning Workshop A - 218

Practice 7-2: Automatic Statistics Gathering (continued)


|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
10 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)
Note
----- dynamic sampling used for this statement

Statistics
---------------------------------------------------------9 recursive calls
0 db block gets
64 consistent gets
1 physical reads
0 redo size
481 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> select count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Execution Plan
---------------------------------------------------------Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
265
(1)|
00:00:03 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 86262 | 2190K|
265
(1)|
00:00:03 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):

Oracle Database 11g: SQL Tuning Workshop A - 219

Practice 7-2: Automatic Statistics Gathering (continued)


--------------------------------------------------2 - filter("DEPTNO"=10)
Note
----- dynamic sampling used for this statement

Statistics
---------------------------------------------------------7 recursive calls
0 db block gets
871 consistent gets
5 physical reads
0 redo size
482 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set autotrace off
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;
select count(*), max(empno) from emp where deptno = 10;
set autotrace off

8) Confirm your assumption from the previous step.


SQL> show parameter optimizer_dynamic_sampling
NAME
TYPE
VALUE
------------------------------------ ----------- ----------------------------optimizer_dynamic_sampling
integer
2
SQL>

9) Make sure you disable the mechanism found in the previous step.
SQL> alter session set optimizer_dynamic_sampling=0;
Session altered.
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 220

Practice 7-2: Automatic Statistics Gathering (continued)


10) Perform step 7 again. What do you observe and why?
a) Because dynamic sampling is not used, the optimizer cannot use any statistics. It
uses default statistics that are not a good choice for the second statement.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@check_exec_plans
set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;

COUNT(*) MAX(EMPNO)
---------- ---------10
99

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
5
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
714 | 18564 |
5
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
286 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
481 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)

Oracle Database 11g: SQL Tuning Workshop A - 221

Practice 7-2: Automatic Statistics Gathering (continued)


0
1

sorts (disk)
rows processed

SQL>
SQL> select count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
5
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
714 | 18564 |
5
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
286 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=10)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
954 consistent gets
190 physical reads
0 redo size
482 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set autotrace off
SQL>
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 222

Practice 7-2: Automatic Statistics Gathering (continued)


set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;
select count(*), max(empno) from emp where deptno = 10;
set autotrace off

11) Reset dynamic sampling as it was at the beginning of this lab.


SQL> alter session set optimizer_dynamic_sampling=2;
Session altered.
SQL>

12) Set the following wrong statistic values for your EMP table:
- Set its number of rows to 10.
- Set its number of blocks to 5.
SQL> @set_wrong_stats
SQL> set echo on
SQL>
SQL> exec
dbms_stats.set_table_stats('AGS','EMP',null,null,null,10,5);
PL/SQL procedure successfully completed.
SQL>
-------------------------------------------------------------set echo on
exec dbms_stats.set_table_stats('AGS','EMP',null,null,null,10,5);

13) Check that you modified correctly the statistics of the EMP table.
SQL> @check_table_last_analyzed
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
2 FROM user_tables
3 WHERE table_name = 'EMP';
LAST_ANAL SAMPLE_SIZE MON
NUM_ROWS
BLOCKS TABLE_NAME
--------- ----------- --- ---------- ---------- -----------------------------

Oracle Database 11g: SQL Tuning Workshop A - 223

Practice 7-2: Automatic Statistics Gathering (continued)


07-APR-08

2000 YES

10

5 EMP

SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
FROM user_tables
WHERE table_name = 'EMP';

14) Perform step 7 again. What do you observe and why?


a) Because there are statistics defined on the EMP table, the optimizer uses them, and
not dynamic sampling. However, because the statistics are incorrect, the
generated plans are also incorrect, at least for the second statement.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@check_exec_plans
set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;

COUNT(*) MAX(EMPNO)
---------- ---------10
99

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
2
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
1 |
26 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
1 |
|
1
(0)| 00:00:01 |
--------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 224

Practice 7-2: Automatic Statistics Gathering (continued)


Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
481 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> select count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
26 |
2
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
1 |
26 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
1 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=10)

Statistics
----------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 225

Practice 7-2: Automatic Statistics Gathering (continued)


0
0
805
0
0
482
420
2
0
0
1

recursive calls
db block gets
consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL> set autotrace off
SQL>
-------------------------------------------------------------set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;
select count(*), max(empno) from emp where deptno = 10;
set autotrace off

15) Make sure you manually gather statistics on your EMP table and its corresponding
index.
SQL> @gather_stats_manually
SQL> set echo on
SQL>
SQL> exec dbms_stats.gather_table_stats('AGS', 'EMP', cascade =>
true);
PL/SQL procedure successfully completed.
SQL>
-------------------------------------------------------------set echo on
exec dbms_stats.gather_table_stats('AGS', 'EMP', cascade => true);

16) Make sure statistics were gathered on your objects.


SQL> @check_table_last_analyzed
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000

Oracle Database 11g: SQL Tuning Workshop A - 226

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
SQL> SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
2 FROM user_tables
3 WHERE table_name = 'EMP';
LAST_ANAL SAMPLE_SIZE MON
NUM_ROWS
BLOCKS TABLE_NAME
--------- ----------- --- ---------- ---------- ----------------------------07-APR-08
100000 YES
100000
874 EMP
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@check_index_last_analyzed
set echo on
set linesize 200 pagesize 1000
SELECT index_name name,num_rows,
last_analyzed,distinct_keys,leaf_blocks,
avg_leaf_blocks_per_key,join_index
FROM user_indexes
WHERE table_name = 'EMP';

NAME
NUM_ROWS LAST_ANAL DISTINCT_KEYS
LEAF_BLOCKS AVG_LEAF_BLOCKS_PER_KEY JOI
------------------------------ ---------- --------- ------------- ---------- ----------------------- --EMP_I1
100000 07-APR-08
11
196
17 NO
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
1
NONE
1
NONE
1
FREQUENCY
7

SQL>

17) Perform step 7 again. What do you observe and why?


a) Because statistics were correctly gathered on both objects, the optimizer is able to
use correct execution plans for both statements.
SQL> @check_exec_plans
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000

Oracle Database 11g: SQL Tuning Workshop A - 227

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
SQL> set autotrace on
SQL>
SQL> select count(*), max(empno) from emp where deptno = 9;
COUNT(*) MAX(EMPNO)
---------- ---------10
99

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
8 |
2
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
8 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
18 |
144 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
18 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
481 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> select count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Oracle Database 11g: SQL Tuning Workshop A - 228

Practice 7-2: Automatic Statistics Gathering (continued)


Execution Plan
---------------------------------------------------------Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
8 |
265
(1)|
00:00:03 |
|
1 | SORT AGGREGATE
|
|
1 |
8 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 99864 |
780K|
265
(1)|
00:00:03 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=10)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
805 consistent gets
0 physical reads
0 redo size
482 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> set autotrace off
SQL>

18) Ensure you delete all statistics previously generated on your objects.
SQL> @delete_stats
SQL> set echo on
SQL>
SQL> exec dbms_stats.delete_schema_stats('AGS');
PL/SQL procedure successfully completed.
SQL>
-------------------------------------------------------------set echo on

Oracle Database 11g: SQL Tuning Workshop A - 229

Practice 7-2: Automatic Statistics Gathering (continued)


exec dbms_stats.delete_schema_stats('AGS');

19) Verify that you no longer have statistics gathered on both objects.
SQL> @check_table_last_analyzed
SQL> set echo on
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
2 FROM user_tables
3 WHERE table_name = 'EMP';
LAST_ANAL SAMPLE_SIZE MON
NUM_ROWS
BLOCKS TABLE_NAME
--------- ----------- --- ---------- ---------- ----------------------------YES
EMP
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@check_index_last_analyzed
set echo on
set linesize 200 pagesize 1000
SELECT index_name name,num_rows,
last_analyzed,distinct_keys,leaf_blocks,
avg_leaf_blocks_per_key,join_index
FROM user_indexes
WHERE table_name = 'EMP';

NAME
NUM_ROWS LAST_ANAL DISTINCT_KEYS
LEAF_BLOCKS AVG_LEAF_BLOCKS_PER_KEY JOI
------------------------------ ---------- --------- ------------- ---------- ----------------------- --EMP_I1
NO
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
NONE
NONE
NONE

SQL>

Oracle Database 11g: SQL Tuning Workshop A - 230

Practice 7-2: Automatic Statistics Gathering (continued)


20) How would you determine the list of automated tasks that exist on your database?
a) You can use Enterprise Manager Database Control by navigating to the
Automated Maintenance Tasks page (Home > Server> Automated Maintenance
Tasks). On the Automated Maintenance Tasks page, you can see the three
automated tasks implemented by default on your database.
b) Another possibility is to use the DBA_AUTOTASK_TASK table as shown by the
following statement:
SQL> select task_name from dba_autotask_task;
TASK_NAME
---------------------------------------------------------------gather_stats_prog
auto_space_advisor_prog
AUTO_SQL_TUNING_PROG
SQL>

21) Exit from your SQL*Plus session.


SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$

22) You now want to observe the effects of the automatic statistics-gathering feature of
your database. However, you do not want to wait until the database automatically
opens the next maintenance window. So from your terminal session, execute the
run_ags.sh script. This script forces the execution of the automatic statisticsgathering task.
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$ ./run_ags.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Apr 7 16:00:03 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> exec dbms_workload_repository.create_snapshot;
PL/SQL procedure successfully completed.
SQL>
SQL> variable window varchar2(20);
SQL>
SQL> begin

Oracle Database 11g: SQL Tuning Workshop A - 231

Practice 7-2: Automatic Statistics Gathering (continued)


2
select upper(to_char(sysdate,'fmday'))||'_WINDOW' into :window
from dual;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL>
SQL> print window;
WINDOW
-------------------------------MONDAY_WINDOW
SQL>
SQL> -SQL> -- Open the corresponding maintenance window, but with other
clients disabled
SQL> -SQL>
SQL> alter system set "_enable_automatic_maintenance"=1
2 /
System altered.
SQL>
SQL> exec dbms_auto_task_admin.disable( >
'auto space advisor', null, :window);
PL/SQL procedure successfully completed.
SQL>
SQL> exec dbms_auto_task_admin.disable( >
'sql tuning advisor', null, :window);
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> exec dbms_scheduler.open_window(:window, null, true);
PL/SQL procedure successfully completed.
SQL>
SQL> -SQL> -- Close the maintenance window when auto optimizer stats
collection is done
SQL> -SQL>
SQL>
SQL> exec dbms_lock.sleep(120);
PL/SQL procedure successfully completed.
SQL>
SQL> exec dbms_scheduler.close_window(:window);

Oracle Database 11g: SQL Tuning Workshop A - 232

Practice 7-2: Automatic Statistics Gathering (continued)


PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> alter system set "_enable_automatic_maintenance"=0
2 /
System altered.
SQL>
SQL> -SQL> -- Re-enable the other guys so they look like they are enabled
in EM.
SQL> -- Still they will be disabled because we have set the
underscore.
SQL> -SQL>
SQL> exec dbms_auto_task_admin.enable( >
'auto space advisor', null, :window);
PL/SQL procedure successfully completed.
SQL>
SQL> exec dbms_auto_task_admin.enable( >
'sql tuning advisor', null, :window);
PL/SQL procedure successfully completed.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_Gather_Stats
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_ags.sql
-------------------------------------------------------------set echo on
exec dbms_workload_repository.create_snapshot;

Oracle Database 11g: SQL Tuning Workshop A - 233

Practice 7-2: Automatic Statistics Gathering (continued)


variable window varchar2(20);
begin
select upper(to_char(sysdate,'fmday'))||'_WINDOW' into :window from
dual;
end;
/
print window;
--- Open the corresponding maintenance window, but with other clients
disabled
-alter system set "_enable_automatic_maintenance"=1
/
exec dbms_auto_task_admin.disable( 'auto space advisor', null, :window);
exec dbms_auto_task_admin.disable( 'sql tuning advisor', null, :window);

exec dbms_scheduler.open_window(:window, null, true);


--- Close the maintenance window when auto optimizer stats collection
is done
--

exec dbms_lock.sleep(120);
exec dbms_scheduler.close_window(:window);

alter system set "_enable_automatic_maintenance"=0


/
--- Re-enable the other guys so they look like they are enabled in
EM.
-- Still they will be disabled because we have set the underscore.
-exec dbms_auto_task_admin.enable( 'auto space advisor', null, :window);
exec dbms_auto_task_admin.enable( 'sql tuning advisor', null, :window);
exit;

Oracle Database 11g: SQL Tuning Workshop A - 234

Practice 7-2: Automatic Statistics Gathering (continued)


23) Connect again as the AGS user from a SQL*Plus session.
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$ sqlplus ags/ags
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Apr 7 16:02:44 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

24) View the statistics of your objects. What do you observe and why?
a) The statistics were automatically gathered by the database during the maintenance
window. You can also see this directly from the Automated Maintenance Tasks
page in Enterprise Manager. The important thing is that the database
automatically gathered the right statistics and histograms. Depending on your
environment, you may see different sample sizes.
SQL> @check_table_last_analyzed
SQL>
SQL> set linesize 200 pagesize 1000
SQL>
SQL> SELECT
last_analyzed,sample_size,monitoring,num_rows,blocks,table_name
2 FROM user_tables
3 WHERE table_name = 'EMP';
LAST_ANAL SAMPLE_SIZE MON
NUM_ROWS
BLOCKS TABLE_NAME
--------- ----------- --- ---------- ---------- ----------------------------07-APR-08
100000 YES
100000
874 EMP
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5

@check_index_last_analyzed
set echo on
set linesize 200 pagesize 1000
SELECT index_name name,num_rows,
last_analyzed,distinct_keys,leaf_blocks,
avg_leaf_blocks_per_key,join_index
FROM user_indexes
WHERE table_name = 'EMP';

NAME
NUM_ROWS LAST_ANAL DISTINCT_KEYS
LEAF_BLOCKS AVG_LEAF_BLOCKS_PER_KEY JOI
------------------------------ ---------- --------- ------------- ---------- ----------------------- --EMP_I1
100000 07-APR-08
11
196
17 NO

Oracle Database 11g: SQL Tuning Workshop A - 235

Practice 7-2: Automatic Statistics Gathering (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
1
NONE
1
NONE
1
FREQUENCY
5

SQL>

25) Perform step 7 again. What do you observe and why?


a) The optimizer can make the right decisions for both statements. This is because of
the statistics that were automatically gathered by the database previously.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@check_exec_plans
set echo on
set linesize 200 pagesize 1000
set autotrace on
select count(*), max(empno) from emp where deptno = 9;

COUNT(*) MAX(EMPNO)
---------- ---------10
99

Execution Plan
---------------------------------------------------------Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
8 |
2
(0)| 00:00:01 |
|
1 | SORT AGGREGATE
|
|
1 |
8 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
9 |
72 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
9 |
|
1
(0)| 00:00:01 |
--------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 236

Practice 7-2: Automatic Statistics Gathering (continued)


Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
481 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
SQL> select count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Execution Plan
---------------------------------------------------------Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
8 |
265
(1)|
00:00:03 |
|
1 | SORT AGGREGATE
|
|
1 |
8 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 99863 |
780K|
265
(1)|
00:00:03 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=10)

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets

Oracle Database 11g: SQL Tuning Workshop A - 237

Practice 7-2: Automatic Statistics Gathering (continued)


805
0
0
482
420
2
0
0
1

consistent gets
physical reads
redo size
bytes sent via SQL*Net to client
bytes received via SQL*Net from client
SQL*Net roundtrips to/from client
sorts (memory)
sorts (disk)
rows processed

SQL>
SQL> set autotrace off
SQL>

26) Exit from your SQL*Plus session.


SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$

27) From your terminal window, clean up your environment by executing the
ags_cleanup.sh script.
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$ ./ags_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Apr 7 16:03:37 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user ags cascade;
User dropped.
SQL>
SQL> alter system set "_enable_automatic_maintenance"=1;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_Gather_Stats]$
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 238

Practice 7-2: Automatic Statistics Gathering (continued)


#!/bin/bash
cd /home/oracle/solutions/Automatic_Gather_Stats
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba @ags_cleanup.sql
-------------------------------------------------------------set echo on
drop user ags cascade;
alter system set "_enable_automatic_maintenance"=1;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 239

Practices for Lesson 8

Oracle Database 11g: SQL Tuning Workshop A - 240

Practice 8-1: Understanding Adaptive Cusrsor Sharing


In this practice, you experiment with bind variable peeking and adaptive cursor sharing.
1) Connected to a terminal session as the oracle user, execute the acs_setup.sh
script to set up the environment used for this lab. You can locate this script in your
$HOME/solutions/Adaptive_Cursor_Sharing directory.
[oracle@edrsr33p1-orcl ~]$ cd solutions/Adaptive_Cursor_Sharing
[oracle@edrsr33p1-orcl Adaptive_Cursor_Sharing]$ ./acs_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Mar 28 16:54:36
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user acs cascade;
drop user acs cascade
*
ERROR at line 1:
ORA-01918: user 'ACS' does not exist

SQL>
SQL> create user acs identified by acs default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant dba, connect to acs;
Grant succeeded.
SQL>
SQL> connect acs/acs
Connected.
SQL>
SQL> drop table emp purge;
drop table emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table emp
2 (
3
empno
number,

Oracle Database 11g: SQL Tuning Workshop A - 241

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
4
5
6
7

ename
phone
deptno
);

varchar2(20),
varchar2(20),
number

Table created.
SQL>
SQL>
SQL> insert into emp
2
with tdata as
3
(select rownum empno
4
from all_objects
5
where rownum <= 1000)
6
select rownum,
7
dbms_random.string ('u', 20),
8
dbms_random.string ('u', 20),
9
case
10
when rownum/100000 <= 0.001 then mod(rownum,
10)
11
else 10
12
end
13
from tdata a, tdata b
14
where rownum <= 100000;
100000 rows created.
SQL>
SQL> create index emp_i1 on emp(deptno);
Index created.
SQL>
SQL> exec dbms_stats.gather_table_stats(null, 'EMP', METHOD_OPT =>
'FOR COLUMNS DEPTNO SIZE 10', CASCADE => TRUE);
PL/SQL procedure successfully completed.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Adaptive_Cursor_Sharing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Adaptive_Cursor_Sharing

Oracle Database 11g: SQL Tuning Workshop A - 242

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @acs_setup.sql
-------------------------------------------------------------set echo on
drop user acs cascade;
create user acs identified by acs default tablespace users temporary
tablespace temp;
grant dba, connect to acs;
connect acs/acs
drop table emp purge;
create table emp
(
empno
number,
ename
varchar2(20),
phone
varchar2(20),
deptno number
);
insert into emp
with tdata as
(select rownum empno
from all_objects
where rownum <= 1000)
select rownum,
dbms_random.string ('u', 20),
dbms_random.string ('u', 20),
case
when rownum/100000 <= 0.001 then mod(rownum, 10)
else 10
end
from tdata a, tdata b
where rownum <= 100000;
create index emp_i1 on emp(deptno);
exec dbms_stats.gather_table_stats(null, 'EMP', METHOD_OPT => 'FOR
COLUMNS DEPTNO SIZE 10', CASCADE => TRUE);
alter system flush shared_pool;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 243

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
2) In your terminal session, connect to the SQL*Plus session as the ACS user. Ensure
that you stay connected to the same SQL*Plus session until the end of this lab. After
you get connected, identify the columns of the EMP table that have histograms.
a) Only the DEPTNO column has a 10 buckets histogram.
[oracle@edrsr33p1-orcl Adaptive_Cursor_Sharing]$ sqlplus acs/acs
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Mar 28 16:54:49
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
NONE
NONE
HEIGHT BALANCED
10

SQL>
-------------------------------------------------------------set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

3) Determine the distribution of all the distinct values found in the DEPTNO column of
the EMP table. What do you find?
a) Values distribution is uniform for all of them (0.01%) except for value 10
(99.9%). This is typical of what is called data skew.
SQL> @show_deptno_distribution
SQL> set echo on
SQL>
SQL> select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent

Oracle Database 11g: SQL Tuning Workshop A - 244

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
2
3
4
5

from emp, (select max(empno) nr


from emp)
group by deptno, nr
order by deptno;

DEPTNO CNT_PER_DEPTNO DEPTNO_PERCENT


---------- -------------- -------------0
10
.01
1
10
.01
2
10
.01
3
10
.01
4
10
.01
5
10
.01
6
10
.01
7
10
.01
8
10
.01
9
10
.01
10
99900
99.9
11 rows selected.
SQL>
-------------------------------------------------------------set echo on
select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent
from emp, (select max(empno) nr
from emp)
group by deptno, nr
order by deptno;

4) Before you study the adaptive cursor sharing feature, disable its functionality by
setting the OPTIMIZER_FEATURES_ENABLE session parameter back to
10.2.0.1. After this is done, ensure that you execute the following command in
your SQL*Plus session: set lines 200 pages 10000. This is used in the lab
to print execution plans correctly.
SQL> alter session set optimizer_features_enable="10.2.0.1";
Session altered.
SQL> set lines 200 pages 10000

5) Determine the execution plan for the following statement:


select /*ACS_L9*/ count(*), max(empno)
from emp
where deptno = 9;
What do you notice and why?
a) The optimizer uses an index range scan because value 9 is very selective.
Oracle Database 11g: SQL Tuning Workshop A - 245

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_literal_9
set echo on
select /*ACS_L9*/ count(*), max(empno)
from emp
where deptno = 9;

COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 64ngy4j55d1z5, child number 0
------------------------------------select /*ACS_L9*/ count(*), max(empno) from emp where deptno = 9
Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
12 |
192 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
12 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=9)

20 rows selected.
SQL>
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 246

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
set echo on
select /*ACS_L9*/ count(*), max(empno)
from emp
where deptno = 9;

6) Determine the execution plan for the following statement:


select /*ACS_L10*/ count(*), max(empno)
from emp
where deptno = 10;
What do you notice and why?
a) The optimizer uses a full table scan because value 10 is not a selective value.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_literal_10
set echo on
select /*ACS_L10*/ count(*), max(empno)
from emp
where deptno = 10;

COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 3232j5gkp2u5h, child number 0
------------------------------------select /*ACS_L10*/ count(*), max(empno) from emp where deptno = 10
Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
240 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 95000 | 1484K|
240
(1)|
00:00:03 |

Oracle Database 11g: SQL Tuning Workshop A - 247

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=10)

19 rows selected.
SQL>
-------------------------------------------------------------set echo on
select /*ACS_L10*/ count(*), max(empno)
from emp
where deptno = 10;

7) Define a bind variable called DEPTNO in your SQL*Plus session, set it to value 9,
and execute the following query, and determine its execution plan:
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;
What do you notice and why?
a) Because the optimizer uses bind peeking the first time you execute a statement
with a bind variable, and because for this first execution, value 9 is used, the
execution plan with index access is used.
SQL> variable deptno number;
SQL> exec :deptno := 9
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);

Oracle Database 11g: SQL Tuning Workshop A - 248

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
12 |
192 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
12 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=:DEPTNO)

20 rows selected.
SQL>
-------------------------------------------------------------set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;
-------------------------------------------------------------set echo on
select * from table(dbms_xplan.display_cursor);

Oracle Database 11g: SQL Tuning Workshop A - 249

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
8) Determine the execution statistics in terms of child cursors, executions, and buffer
gets for the previously executed statement. What do you observe?
a) In V$SQL, only one child cursor exists, and it has been executed only once (first
time ever in this case). Also, the number of buffer gets is small due to the efficient
access path that was used.
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
1
3
SQL>
-------------------------------------------------------------set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

9) Perform steps 7 and 8 again, but this time using 10 as the bind value for DEPTNO.
What do you observe and why?
a) The execution plan is identical. The index path is used although value 10 is not
selective. This is because bind peeking only operates the first time you execute
your statement. Looking at V$SQL, you can clearly see that there is still only one
child cursor associated with your statement. However, this time, the number of
buffer gets was raised significantly due to the full table scan.
SQL> exec :deptno := 10
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL> @show_latest_exec_plan

Oracle Database 11g: SQL Tuning Workshop A - 250

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
12 |
192 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
12 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=:DEPTNO)

20 rows selected.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
2
957
SQL>

10) Before the next step, flush your shared pool to make sure you wipe out all cursors
information.
Oracle Database 11g: SQL Tuning Workshop A - 251

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
SQL> alter system flush shared_pool;
System altered.
SQL>

11) Perform step 9 again. What do you observe and why?


a) The execution plan is a full table scan because you used value 10 as your first
bind value. There is only one child cursor that is created so far to handle your
statement.
SQL> exec :deptno := 10
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
240 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 95000 | 1484K|
240
(1)|
00:00:03 |

Oracle Database 11g: SQL Tuning Workshop A - 252

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=:DEPTNO)

19 rows selected.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
1
872
SQL>

12) Perform step 9 again, but this time use 9 as your bind value. What do you observe and
why?
a) Although value 9 is very selective, a full table scan is still used. This is because
the second time you execute your statement, bind peeking is not done. So you
continue to use the same child cursor.
SQL> exec :deptno := 9
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT

Oracle Database 11g: SQL Tuning Workshop A - 253

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
240 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 95000 | 1484K|
240
(1)|
00:00:03 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=:DEPTNO)

19 rows selected.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
2
1693
SQL>

13) Before the next step, reset your session to use adaptive cursor sharing, and ensure that
you flush your shared pool again.
SQL> alter session set optimizer_features_enable="11.1.0.6";
Session altered.
SQL> alter system flush shared_pool;

Oracle Database 11g: SQL Tuning Workshop A - 254

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
System altered.
SQL>

14) Perform step 12 again. What do you observe, and why?


a) Because this is the first time you execute the statement, bind peeking is used, and
because value 9 is very selective, the index path is used. Only one child cursor is
used to handle this statement.
SQL> exec :deptno := 9
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
1 |
16 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
1 |
|
1
(0)| 00:00:01 |

Oracle Database 11g: SQL Tuning Workshop A - 255

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=:DEPTNO)

20 rows selected.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
1
54
SQL>

15) Perform step 14 again, but this time using value 10 as your bind value. What do you
observe and why?
a) Although value 10 is not selective, the same index path as in the previous step is
used. Only one child cursor is currently needed to represent your statement.
SQL> exec :deptno := 10
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 256

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
SQL_ID 272gr4hapc9w1, child number 0
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 3184478295
-------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
|
-------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
2 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|
2 |
TABLE ACCESS BY INDEX ROWID| EMP
|
1 |
16 |
2
(0)| 00:00:01 |
|* 3 |
INDEX RANGE SCAN
| EMP_I1 |
1 |
|
1
(0)| 00:00:01 |
-------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - access("DEPTNO"=:DEPTNO)

20 rows selected.
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_exec_stats
set echo on
select child_number, executions, buffer_gets
from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
2
1008
SQL>

16) Perform step 15 again. What do you observe and why?


a) Because you now use adaptive cursor sharing, the system realizes that you benefit
from another child cursor for handling your statement. This time, a full table
access path is used to better handle your statement.
SQL> exec :deptno := 10
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 257

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
SQL>
SQL>
SQL>
SQL>
2
3

@select_deptno_bind
set echo on
select /*ACS_1*/ count(*), max(empno)
from emp
where deptno = :deptno;

COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from table(dbms_xplan.display_cursor);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 272gr4hapc9w1, child number 1
------------------------------------select /*ACS_1*/ count(*), max(empno) from emp where deptno =
:deptno
Plan hash value: 2083865914
-------------------------------------------------------------------------| Id | Operation
| Name | Rows | Bytes | Cost (%CPU)|
Time
|
-------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
240 (100)|
|
|
1 | SORT AGGREGATE
|
|
1 |
16 |
|
|
|* 2 |
TABLE ACCESS FULL| EMP | 95000 | 1484K|
240
(1)|
00:00:03 |
-------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - filter("DEPTNO"=:DEPTNO)

19 rows selected.
SQL>
SQL> @show_latest_exec_stats
SQL> set echo on
SQL>
SQL> select child_number, executions, buffer_gets

Oracle Database 11g: SQL Tuning Workshop A - 258

Practice 8-1: Understanding Adaptive Cusrsor Sharing


(continued)
2
3

from v$sql
where sql_text like 'select /*ACS_1%';

CHILD_NUMBER EXECUTIONS BUFFER_GETS


------------ ---------- ----------0
2
1008
1
1
821
SQL>

17) Exit your SQL*Plus session, and execute the acs_cleanup.sh script to clean up
your environment.
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Adaptive_Cursor_Sharing]$ ./acs_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Mar 28 16:59:18
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user acs cascade;
User dropped.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Adaptive_Cursor_Sharing]$

Oracle Database 11g: SQL Tuning Workshop A - 259

Practice 8-2: Understanding CURSOR_SHARING


In this practice, you investigate the use of the CURSOR_SHARING initialization
parameter.
1) You can find all the necessary scripts for this lab in your
$HOME/solutions/Cursor_Sharing directory. First, you must set up the
environment for this lab by executing the cs_setup.sh script from a terminal
session connected as the oracle user. This script creates a new user called CS and
the EMP table used throughout this lab.
[oracle@edrsr33p1-orcl ~]$ cd solutions/Cursor_Sharing
[oracle@edrsr33p1-orcl Cursor_Sharing]$
[oracle@edrsr33p1-orcl Cursor_Sharing]$ ./cs_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 14:10:59
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user cs cascade;
User dropped.
SQL>
SQL> create user cs identified by cs default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant dba, connect to cs;
Grant succeeded.
SQL>
SQL> connect cs/cs
Connected.
SQL>
SQL> drop table emp purge;
drop table emp purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table emp

Oracle Database 11g: SQL Tuning Workshop A - 260

Practice 8-2: Understanding CURSOR_SHARING (continued)


2
3
4
5
6
7

(
empno
ename
phone
deptno
);

number,
varchar2(20),
varchar2(20),
number

Table created.
SQL>
SQL>
SQL> insert into emp
2
with tdata as
3
(select rownum empno
4
from all_objects
5
where rownum <= 1000)
6
select rownum,
7
dbms_random.string ('u', 20),
8
dbms_random.string ('u', 20),
9
case
10
when rownum/100000 <= 0.001 then mod(rownum,
10)
11
else 10
12
end
13
from tdata a, tdata b
14
where rownum <= 100000;
100000 rows created.
SQL>
SQL> create index emp_i1 on emp(deptno);
Index created.
SQL>
SQL> execute dbms_stats.gather_table_stats(null, 'EMP', cascade =>
true);
PL/SQL procedure successfully completed.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> connect / as sysdba
Connected.
SQL>
SQL> shutdown immediate;
Database closed.
Database dismounted.
ORACLE instance shut down.
SQL>
SQL> startup;
ORACLE instance started.

Oracle Database 11g: SQL Tuning Workshop A - 261

Practice 8-2: Understanding CURSOR_SHARING (continued)


Total System Global Area 845348864 bytes
Fixed Size
1303188 bytes
Variable Size
541068652 bytes
Database Buffers
297795584 bytes
Redo Buffers
5181440 bytes
Database mounted.
Database opened.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Cursor_Sharing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Cursor_Sharing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @cs_setup.sql
-------------------------------------------------------------set echo on
drop user cs cascade;
create user cs identified by cs default tablespace users temporary
tablespace temp;
grant dba, connect to cs;
connect cs/cs
drop table emp purge;
create table emp
(
empno
number,
ename
varchar2(20),
phone
varchar2(20),
deptno number
);

insert into emp


with tdata as

Oracle Database 11g: SQL Tuning Workshop A - 262

Practice 8-2: Understanding CURSOR_SHARING (continued)


(select rownum empno
from all_objects
where rownum <= 1000)
select rownum,
dbms_random.string ('u', 20),
dbms_random.string ('u', 20),
case
when rownum/100000 <= 0.001 then mod(rownum, 10)
else 10
end
from tdata a, tdata b
where rownum <= 100000;
create index emp_i1 on emp(deptno);
execute dbms_stats.gather_table_stats(null, 'EMP', cascade => true);
alter system flush shared_pool;
connect / as sysdba
shutdown immediate;
startup;
exit;

2) From the same terminal session, connect as the CS user in the SQL*Plus session, and
stay connected to that session until the end of this lab. For formatting reasons, after
you have connected in the SQL*Plus session, execute the following command:
set linesize 200 pagesize 1000
[oracle@edrsr33p1-orcl Cursor_Sharing]$ sqlplus cs/cs
SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 14:11:38
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

SQL> set linesize 200 pagesize 1000


SQL>

3) Check the existence of histograms on the columns of the EMP table, and then
determine the data distribution in the DEPTNO column of the EMP table. What do you
observe?

Oracle Database 11g: SQL Tuning Workshop A - 263

Practice 8-2: Understanding CURSOR_SHARING (continued)


a) Currently, there are no histograms created on the columns of the EMP table. Also,
it is clear that you have data skew in the DEPTNO column. Value 10 repeats most
of the time (99.9%) whereas all other values only repeat 0.01%.
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
1
NONE
1
NONE
1
NONE
1

SQL>
SQL> @show_deptno_distribution
SQL> set echo on
SQL>
SQL> select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent
2 from emp, (select max(empno) nr
3
from emp)
4 group by deptno, nr
5 order by deptno;
DEPTNO CNT_PER_DEPTNO DEPTNO_PERCENT
---------- -------------- -------------0
10
.01
1
10
.01
2
10
.01
3
10
.01
4
10
.01
5
10
.01
6
10
.01
7
10
.01
8
10
.01
9
10
.01
10
99900
99.9
11 rows selected.
SQL>
-------------------------------------------------------------set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';
-------------------------------------------------------------set echo on

Oracle Database 11g: SQL Tuning Workshop A - 264

Practice 8-2: Understanding CURSOR_SHARING (continued)


select deptno, count(*) cnt_per_deptno, (count(*)*100)/nr
deptno_percent
from emp, (select max(empno) nr
from emp)
group by deptno, nr
order by deptno;

4) Before you continue, ensure that you flush your shared pool.
SQL> alter system flush shared_pool;
System altered.
SQL>

5) How would you force your SQL*Plus session to automatically replace statement
literals with bind variables to make sure the same cursor is used independently of the
literal values?
SQL> alter session set cursor_sharing = force;
Session altered.
SQL>

6) From the same SQL*Plus session, execute the following two queries, and then
determine how many cursors are generated to handle these two statements, and what
execution plans were used. What do you observe and why?
select /*CS*/ count(*), max(empno) from emp where deptno = 9;
select /*CS*/ count(*), max(empno) from emp where deptno = 10;

a) Because of the previous step, literal values are replaced with bind variables. The
FORCE option forces the system to share only one child cursor in this case and
use the exact same execution plan (index range scan).
SQL> @select_deptno_literal_9
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 9;
COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @select_deptno_literal_10
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000

Oracle Database 11g: SQL Tuning Workshop A - 265

Practice 8-2: Understanding CURSOR_SHARING (continued)


SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_cursors
set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';

SQL_TEXT
HASH_VALUE
--------------------------------------------------------------------- ---------select /*CS*/ count(*), max(empno) from emp where deptno =
:"SYS_B_0" 3434097775
SQL> @show_latest_exec_plans
SQL> set echo on
SQL>
SQL> col object_name format a5
SQL> col operation format a16
SQL> col options format a15
SQL>
SQL> select address,hash_value,child_number,
operation,options,object_name
2 from v$sql_plan
3 where (address,hash_value) in
4
(select address,hash_value
5
from v$sql
6
where sql_text like '%select /*CS%');
ADDRESS
OBJEC
---------4C9C53D4
4C9C53D4
4C9C53D4
EMP
4C9C53D4
EMP_I

HASH_VALUE CHILD_NUMBER OPERATION

OPTIONS

---------- ------------ ---------------- --------------- -3434097775


3434097775
3434097775

0 SELECT STATEMENT
0 SORT
AGGREGATE
0 TABLE ACCESS
BY INDEX ROWID

3434097775

0 INDEX

RANGE SCAN
1

SQL>
-------------------------------------------------------------set echo on
select /*CS*/ count(*), max(empno) from emp where deptno = 9;
-------------------------------------------------------------set echo on

Oracle Database 11g: SQL Tuning Workshop A - 266

Practice 8-2: Understanding CURSOR_SHARING (continued)


select /*CS*/ count(*), max(empno) from emp where deptno = 10;
-------------------------------------------------------------set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';
-------------------------------------------------------------set echo on
col object_name format a5
col operation format a16
col options format a15
select address,hash_value,child_number,
operation,options,object_name
from v$sql_plan
where (address,hash_value) in
(select address,hash_value
from v$sql
where sql_text like '%select /*CS%');

7) Ensure that you create a 10 buckets histogram on the DEPTNO column of the EMP
table.
SQL> exec dbms_stats.gather_table_stats(null, 'EMP', METHOD_OPT =>
'FOR COLUMNS DEPTNO SIZE 10', CASCADE => TRUE);
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL>
2
3

@check_emp_histogram
set echo on
select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

COLUMN_NAME
-----------------------------EMPNO
ENAME
PHONE
DEPTNO

HISTOGRAM
NUM_BUCKETS
--------------- ----------NONE
1
NONE
1
NONE
1
HEIGHT BALANCED
10

SQL>
-------------------------------------------------------------set echo on

Oracle Database 11g: SQL Tuning Workshop A - 267

Practice 8-2: Understanding CURSOR_SHARING (continued)


select column_name, histogram, num_buckets
from user_tab_columns
where table_name='EMP';

8) Before you continue, ensure that you flush your shared pool.
SQL> alter system flush shared_pool;
System altered.
SQL>

9) Perform step 6 again. What do you observe and why?


a) Although you captured histogram for the DEPTNO column that shows data skew,
the system continues to share only one child cursor to handle both statements.
This behavior is due to the FORCE option for the CURSOR_SHARING
initialization parameter.
SQL> @select_deptno_literal_9
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 9;
COUNT(*) MAX(EMPNO)
---------- ---------10
99
SQL>
SQL> @select_deptno_literal_10
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_cursors
set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';

SQL_TEXT
HASH_VALUE
--------------------------------------------------------------------- ----------

Oracle Database 11g: SQL Tuning Workshop A - 268

Practice 8-2: Understanding CURSOR_SHARING (continued)


select /*CS*/ count(*), max(empno) from emp where deptno =
:"SYS_B_0" 3434097775
SQL>
-------------------------------------------------------------set echo on
select /*CS*/ count(*), max(empno) from emp where deptno = 9;
-------------------------------------------------------------set echo on
select /*CS*/ count(*), max(empno) from emp where deptno = 10;
-------------------------------------------------------------set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';

10) Before you continue, ensure that you flush your shared pool.
SQL> alter system flush shared_pool;
System altered.
SQL>

11) How would you ensure that you now use more than one child cursor to handle both
statements? Implement your solution, and check it.
a) By setting CURSOR_SHARING to SIMILAR for your session, the system is able
to see that you benefit from using two different child cursors to handle both
statements because they lend themselves to very different execution plans.
SQL> alter session set cursor_sharing = similar;
Session altered.
SQL> @select_deptno_literal_9
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 9;
COUNT(*) MAX(EMPNO)
---------- ---------10
99

Oracle Database 11g: SQL Tuning Workshop A - 269

Practice 8-2: Understanding CURSOR_SHARING (continued)


SQL>
SQL> @select_deptno_literal_10
SQL> set echo on
SQL>
SQL> select /*CS*/ count(*), max(empno) from emp where deptno = 10;
COUNT(*) MAX(EMPNO)
---------- ---------99900
100000
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@show_latest_cursors
set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';

SQL_TEXT
HASH_VALUE
--------------------------------------------------------------------- ---------select /*CS*/ count(*), max(empno) from emp where deptno =
:"SYS_B_0" 3434097775
select /*CS*/ count(*), max(empno) from emp where deptno =
:"SYS_B_0" 3434097775
SQL> @show_latest_exec_plans
SQL> set echo on
SQL>
SQL> col object_name format a5
SQL> col operation format a16
SQL> col options format a15
SQL>
SQL> select address,hash_value,child_number,
operation,options,object_name
2 from v$sql_plan
3 where (address,hash_value) in
4
(select address,hash_value
5
from v$sql
6
where sql_text like '%select /*CS%');
ADDRESS
OBJEC
---------4C9C53D4
4C9C53D4
4C9C53D4
EMP
4C9C53D4
4C9C53D4
4C9C53D4
EMP

HASH_VALUE CHILD_NUMBER OPERATION

OPTIONS

---------- ------------ ---------------- --------------- -3434097775


3434097775
3434097775

1 SELECT STATEMENT
1 SORT
AGGREGATE
1 TABLE ACCESS
FULL

3434097775
3434097775
3434097775

0 SELECT STATEMENT
0 SORT
AGGREGATE
0 TABLE ACCESS
BY INDEX ROWID

Oracle Database 11g: SQL Tuning Workshop A - 270

Practice 8-2: Understanding CURSOR_SHARING (continued)


4C9C53D4 3434097775
EMP_I

0 INDEX

RANGE SCAN
1

7 rows selected.
SQL>
-------------------------------------------------------------set echo on
select /*CS*/ count(*), max(empno) from emp where deptno = 9;
-------------------------------------------------------------set echo on
select /*CS*/ count(*), max(empno) from emp where deptno = 10;
-------------------------------------------------------------set echo on
col sql_text format a70
select sql_text,hash_value
from v$sql
where sql_text like '%select /*CS%';
-------------------------------------------------------------set echo on
col object_name format a5
col operation format a16
col options format a15
select address,hash_value,child_number,
operation,options,object_name
from v$sql_plan
where (address,hash_value) in
(select address,hash_value
from v$sql
where sql_text like '%select /*CS%');

12) Exit your SQL*Plus session and execute the cs_cleanup.sh script to clean up
your environment for this lab.
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Cursor_Sharing]$
[oracle@edrsr33p1-orcl Cursor_Sharing]$ ./cs_cleanup.sh

Oracle Database 11g: SQL Tuning Workshop A - 271

Practice 8-2: Understanding CURSOR_SHARING (continued)


SQL*Plus: Release 11.1.0.6.0 - Production on Mon Mar 31 14:15:49
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user cs cascade;
User dropped.
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Cursor_Sharing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Cursor_Sharing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @cs_cleanup.sql
-------------------------------------------------------------set echo on
drop user cs cascade;
alter system flush shared_pool;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 272

Practices for Lesson 9

Oracle Database 11g: SQL Tuning Workshop A - 273

Practice 9-1: Using Hints


In this practice, you study five different hint cases. They are all independent from each
other. Note: You can find all the necessary scripts used for this lab in your
$HOME/solutions/Hints directory. You should be using a terminal session
connected as the oracle user.
1) From your terminal session, execute the iot_setup.sh script. This script creates
an index-organized table.
[oracle@edrsr33p1-orcl Hints]$ ./iot_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 15:17:24 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user iot cascade;
drop user iot cascade
*
ERROR at line 1:
ORA-01918: user 'IOT' does not exist

SQL>
SQL> create user iot identified by iot default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant connect, resource, dba to iot;
Grant succeeded.
SQL>
SQL> connect iot/iot
Connected.
SQL>
SQL> drop table iottab purge;
drop table iottab purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> CREATE TABLE IOTTAB (
2
OBJECT_ID
NUMBER(14, 0) NOT NULL ENABLE
3
, OBJECT_ID_ATT NUMBER(14, 0) NOT NULL ENABLE

Oracle Database 11g: SQL Tuning Workshop A - 274

Practice 9-1: Using Hints (continued)


4
5
6
7
8
9
10
11
12
13
14
15

,
,
,
,
,
,

OBJECT_ID_CAT NUMBER(14, 0) NOT NULL ENABLE


BEGIN
DATE NOT NULL ENABLE
END
DATE NOT NULL ENABLE
STATUS
NUMBER
COMM
VARCHAR2(32) NOT NULL ENABLE
CONSTRAINT IOTTAB_PK
PRIMARY KEY (OBJECT_ID
, OBJECT_ID_ATT
, OBJECT_ID_CAT
, BEGIN
, END) ENABLE )
ORGANIZATION INDEX PCTTHRESHOLD 50 ;

Table created.
SQL>
SQL> CREATE INDEX OBJECT_ID_ATT_INDX ON IOTTAB (OBJECT_ID_ATT);
Index created.
SQL>
SQL> -- load data
SQL>
SQL> begin
2 for i in 400001..500000 loop
3
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> begin
2 for i in 100001..200000 loop
3
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> begin
2 for i in 300001..400000 loop
3
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
4 end loop;

Oracle Database 11g: SQL Tuning Workshop A - 275

Practice 9-1: Using Hints (continued)


5
6
7

commit;
end;
/

PL/SQL procedure successfully completed.


SQL>
SQL>
SQL> begin
2 for i in 500001..600000 loop
3
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL> begin
2 for i in 1..100000 loop
3
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
4 end loop;
5 commit;
6 end;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL>
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash

Oracle Database 11g: SQL Tuning Workshop A - 276

Practice 9-1: Using Hints (continued)


cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @iot_setup.sql
-------------------------------------------------------------set echo on
drop user iot cascade;
create user iot identified by iot default tablespace users temporary
tablespace temp;
grant connect, resource, dba to iot;
connect iot/iot
drop table iottab purge;
CREATE TABLE IOTTAB (
OBJECT_ID
NUMBER(14, 0) NOT NULL ENABLE
, OBJECT_ID_ATT NUMBER(14, 0) NOT NULL ENABLE
, OBJECT_ID_CAT NUMBER(14, 0) NOT NULL ENABLE
, BEGIN
DATE NOT NULL ENABLE
, END
DATE NOT NULL ENABLE
, STATUS
NUMBER
, COMM
VARCHAR2(32) NOT NULL ENABLE
, CONSTRAINT IOTTAB_PK
PRIMARY KEY (OBJECT_ID
, OBJECT_ID_ATT
, OBJECT_ID_CAT
, BEGIN
, END) ENABLE )
ORGANIZATION INDEX PCTTHRESHOLD 50 ;
CREATE INDEX OBJECT_ID_ATT_INDX ON IOTTAB (OBJECT_ID_ATT);
-- load data
begin
for i in 400001..500000 loop
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
end loop;
commit;
end;
/

Oracle Database 11g: SQL Tuning Workshop A - 277

Practice 9-1: Using Hints (continued)


begin
for i in 100001..200000 loop
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
end loop;
commit;
end;
/

begin
for i in 300001..400000 loop
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
end loop;
commit;
end;
/

begin
for i in 500001..600000 loop
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
end loop;
commit;
end;
/

begin
for i in 1..100000 loop
insert into iottab values(i,mod(i,428),mod(i,20),sysdatemod(i,100),sysdate+mod(i,100),mod(i,3),'aaaaaaaaaaaaaaaaaaaaaaaaaa'|
|i);
end loop;
commit;
end;
/

alter system flush shared_pool;


alter system flush buffer_cache;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 278

Practice 9-1: Using Hints (continued)


2) From your terminal session, connect as the IOT user in the SQL*Plus session. From
your SQL*Plus session, execute the set_session.sql script. This script sets a
number of parameters for the duration of this case. Ensure that you do not exit from
your SQL*Plus session until asked to do so.
[oracle@edrsr33p1-orcl Hints]$ sqlplus iot/iot
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 15:18:22 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @set_session
Session altered.
SQL>
-------------------------------------------------------------ALTER SESSION SET OPTIMIZER_INDEX_CACHING=2;
set timing on
set linesize 200 pagesize 1000

3) From your SQL*Plus session, execute the following query and note down the time it
takes to execute:
SELECT
FROM
WHERE
AND
AND

comm.
iottab
object_id = 1
object_id_cat = 0
object_id_att = 426 ;

SQL> @select_iot
SQL>
SQL> SELECT comm
2
FROM iottab
3
WHERE object_id = 1
4
AND object_id_cat = 0
5
AND object_id_att = 426 ;
no rows selected
Elapsed: 00:00:00.07
SQL>
-------------------------------------------------------------set echo on

Oracle Database 11g: SQL Tuning Workshop A - 279

Practice 9-1: Using Hints (continued)


SELECT
FROM
WHERE
AND
AND

comm
iottab
object_id = 1
object_id_cat = 0
object_id_att = 426 ;

4) Use the DBMS_XPLAN package to display the execution plan associated with the
statement you executed in the previous step. What do you observe?
a) It is strange to see that the optimizer chooses a plan that accesses the secondary
index while the query references all columns of the primary key.
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from
table(dbms_xplan.display_cursor(null,null,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 6u4tsfpprgvzn, child number 0
------------------------------------SELECT comm
FROM iottab WHERE object_id = 1
AND object_id_cat
= 0
AND object_id_att = 426
Plan hash value: 2544181447
--------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes |
Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
|
|
1 (100)|
|
|* 1 | INDEX UNIQUE SCAN| IOTTAB_PK
|
1 |
57 |
1
(0)| 00:00:01 |
|* 2 |
INDEX RANGE SCAN| OBJECT_ID_ATT_INDX |
50 |
|
1
(0)| 00:00:01 |
--------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("OBJECT_ID_ATT"=426)
filter(("OBJECT_ID"=1 AND "OBJECT_ID_CAT"=0))
2 - access("OBJECT_ID_ATT"=426)
Note
----- dynamic sampling used for this statement

Oracle Database 11g: SQL Tuning Workshop A - 280

Practice 9-1: Using Hints (continued)


26 rows selected.
Elapsed: 00:00:00.13
SQL>
-------------------------------------------------------------set echo on
select * from table(dbms_xplan.display_cursor(null,null,'TYPICAL'));

5) Before trying to fix this issue, make sure you flush the important content of your SGA
by executing the flush_sga.sql script.
SQL> @flush_sga
SQL> set echo on
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.11
SQL>
SQL> alter system flush buffer_cache;
System altered.
Elapsed: 00:00:00.43
SQL>
-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;

6) Using hints only, how would you fix the issue raised at step 4? Implement your
solution and check if it works.
a) The idea is to use a hint to prevent the optimizer from using the secondary index.
You will not use the NO_INDEX hint.
SQL> @select_iot_hint
SQL> set echo on
SQL>
SQL> SELECT /*+ NO_INDEX(t OBJECT_ID_ATT_INDX) */ comm
2
FROM iottab t
3
WHERE object_id = 1
4
AND object_id_cat = 0
5
AND object_id_att = 426 ;

Oracle Database 11g: SQL Tuning Workshop A - 281

Practice 9-1: Using Hints (continued)


no rows selected
Elapsed: 00:00:00.03
SQL>
SQL> @show_latest_exec_plan
SQL> set echo on
SQL>
SQL> select * from
table(dbms_xplan.display_cursor(null,null,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 4zcxy7z1cg8da, child number 0
------------------------------------SELECT /*+ NO_INDEX(t OBJECT_ID_ATT_INDX) */ comm
FROM iottab t
WHERE object_id = 1
AND object_id_cat = 0
AND object_id_att =
426
Plan hash value: 181430399
----------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
----------------------------------------------------------------------------|
0 | SELECT STATEMENT |
|
|
|
1 (100)|
|
|* 1 | INDEX RANGE SCAN| IOTTAB_PK |
1 |
57 |
1
(0)|
00:00:01 |
----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - access("OBJECT_ID"=1 AND "OBJECT_ID_ATT"=426 AND
"OBJECT_ID_CAT"=0)
Note
----- dynamic sampling used for this statement

24 rows selected.
Elapsed: 00:00:00.13
SQL>
-------------------------------------------------------------set echo on
SELECT /*+ NO_INDEX(t OBJECT_ID_ATT_INDX) */ comm
FROM iottab t

Oracle Database 11g: SQL Tuning Workshop A - 282

Practice 9-1: Using Hints (continued)


WHERE object_id = 1
AND object_id_cat = 0
AND object_id_att = 426 ;
-------------------------------------------------------------set echo on
select * from table(dbms_xplan.display_cursor(null,null,'TYPICAL'));

7) Exit from your SQL*Plus session, and clean up your environment by executing the
iot_cleanup.sh script.
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
[oracle@edrsr33p1-orcl Hints]$ ./iot_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 15:21:09 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user iot cascade;
User dropped.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1

Oracle Database 11g: SQL Tuning Workshop A - 283

Practice 9-1: Using Hints (continued)


export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @iot_cleanup.sql
-------------------------------------------------------------set echo on
drop user iot cascade;
exit;

8) You study a second case of hint utilization to specify hints in lower query blocks.
From your terminal session connected as the oracle user, execute the
hr_hint_setup.sh script.
[oracle@edrsr33p1-orcl Hints]$ ./hr_hint_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 16:08:51 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> alter user hr identified by hr account unlock;
User altered.
SQL>
SQL> grant dba to hr
2
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1

Oracle Database 11g: SQL Tuning Workshop A - 284

Practice 9-1: Using Hints (continued)


export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @hr_hint_setup.sql
-------------------------------------------------------------set echo on
alter user hr identified by hr account unlock;
grant dba to hr
exit;

9) From your terminal session, connect as the HR user in the SQL*Plus session. After
you are connected, execute the create_hr_view1.sql script that creates a view
called V1 on top of the EMPLOYEES table. After this is done, execute the
create_hr_view2.sql script that creates a view V2 on top of V1.
[oracle@edrsr33p1-orcl Hints]$ sqlplus hr/hr
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 16:09:02 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @create_hr_view1
SQL>
SQL> CREATE OR REPLACE VIEW v1 AS
2
SELECT *
3
FROM
employees
4
WHERE employee_id < 150;
View created.
SQL>
SQL> @create_hr_view2
SQL> set echo on
SQL>
SQL> CREATE OR REPLACE VIEW v2 AS
2
SELECT v1.employee_id employee_id, departments.department_id
department_id
3
FROM
v1, departments
4
WHERE v1.department_id = departments.department_id;
View created.

Oracle Database 11g: SQL Tuning Workshop A - 285

Practice 9-1: Using Hints (continued)


SQL>
-------------------------------------------------------------set echo on
CREATE OR REPLACE VIEW v1 AS
SELECT *
FROM
employees
WHERE employee_id < 150;
-------------------------------------------------------------set echo on
CREATE OR REPLACE VIEW v2 AS
SELECT v1.employee_id employee_id, departments.department_id
department_id
FROM
v1, departments
WHERE v1.department_id = departments.department_id;

10) Determine the execution plan used to process the following query:
SELECT * FROM v2 WHERE department_id = 30;
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4

set linesize 200 pagesize 1000


@show_exec_plan_view2
set echo on
explain plan for
SELECT *
FROM
v2
WHERE department_id = 30;

Explained.
SQL>
SQL> select * from table(DBMS_XPLAN.DISPLAY(null,null,'ALL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Plan hash value: 389887213
------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3 |
33 |
1
(0)| 00:00:01 |
|
1 | NESTED LOOPS
|
|
3 |
33 |
1
(0)| 00:00:01 |
|* 2 |
INDEX UNIQUE SCAN
| DEPT_ID_PK
|
1 |
4 |
0
(0)| 00:00:01 |

Oracle Database 11g: SQL Tuning Workshop A - 286

Practice 9-1: Using Hints (continued)


|* 3 |
TABLE ACCESS BY INDEX ROWID| EMPLOYEES
|
3 |
21 |
1
(0)| 00:00:01 |
|* 4 |
INDEX RANGE SCAN
| EMP_DEPARTMENT_IX |
6 |
|
0
(0)| 00:00:01 |
------------------------------------------------------------------------------------------------Query Block Name / Object Alias (identified by operation id):
------------------------------------------------------------1
2
3
4

SEL$5C160134
SEL$5C160134 / DEPARTMENTS@SEL$2
SEL$5C160134 / EMPLOYEES@SEL$3
SEL$5C160134 / EMPLOYEES@SEL$3

Predicate Information (identified by operation id):


--------------------------------------------------2 - access("DEPARTMENTS"."DEPARTMENT_ID"=30)
3 - filter("EMPLOYEE_ID"<150)
4 - access("DEPARTMENT_ID"=30)
Column Projection Information (identified by operation id):
----------------------------------------------------------1 - (#keys=0) "DEPARTMENTS"."DEPARTMENT_ID"[NUMBER,22],
"EMPLOYEE_ID"[NUMBER,22]
2 - "DEPARTMENTS"."DEPARTMENT_ID"[NUMBER,22]
3 - "EMPLOYEE_ID"[NUMBER,22]
4 - "EMPLOYEES".ROWID[ROWID,10]
34 rows selected.
SQL>
-------------------------------------------------------------set echo on
explain plan for
SELECT *
FROM
v2
WHERE department_id = 30;
select * from table(DBMS_XPLAN.DISPLAY(null,null,'ALL'));

11) How do you force the query from step 10 to do a full table scan of the
departments table and a range scan of the emp_emp_id_pk index?
a) You have to use extended hint syntax to be able to specify hints that apply to
tables and indexes that appear in views. The NO_MERGE hint is used to make sure
that the view is not merged into the surrounding query blocks.
SQL> @show_exec_plan_view2_hints
SQL> set echo on
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 287

Practice 9-1: Using Hints (continued)


SQL> explain plan for
2 SELECT /*+ NO_MERGE(v2) INDEX(v2.v1.employees emp_emp_id_pk)
FULL(v2.departments) */ *
3 FROM
v2
4 WHERE department_id = 30;
Explained.
SQL>
SQL> select * from table(DBMS_XPLAN.DISPLAY(null,null,'ALL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Plan hash value: 1511767168
---------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
---------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
3 |
78 |
5
(0)| 00:00:01 |
|
1 | VIEW
| V2
|
3 |
78 |
5
(0)| 00:00:01 |
|
2 |
NESTED LOOPS
|
|
|
|
|
|
|
3 |
NESTED LOOPS
|
|
3 |
57 |
5
(0)| 00:00:01 |
|* 4 |
TABLE ACCESS FULL
| DEPARTMENTS
|
1 |
8 |
3
(0)| 00:00:01 |
|* 5 |
INDEX RANGE SCAN
| EMP_EMP_ID_PK |
50 |
|
1
(0)| 00:00:01 |
|* 6 |
TABLE ACCESS BY INDEX ROWID| EMPLOYEES
|
3 |
33 |
2
(0)| 00:00:01 |
---------------------------------------------------------------------------------------------Query Block Name / Object Alias (identified by operation id):
------------------------------------------------------------1
2
4
5
6

SEL$335DD26A
SEL$335DD26A
SEL$335DD26A
SEL$335DD26A
SEL$335DD26A

/ V2@SEL$1
/ DEPARTMENTS@SEL$2
/ EMPLOYEES@SEL$3
/ EMPLOYEES@SEL$3

Predicate Information (identified by operation id):


--------------------------------------------------4 - filter("DEPARTMENTS"."DEPARTMENT_ID"=30)
5 - access("EMPLOYEE_ID"<150)
6 - filter("DEPARTMENT_ID"=30)
Column Projection Information (identified by operation id):

Oracle Database 11g: SQL Tuning Workshop A - 288

Practice 9-1: Using Hints (continued)


----------------------------------------------------------1 - "V2"."EMPLOYEE_ID"[NUMBER,22],
"V2"."DEPARTMENT_ID"[NUMBER,22]
2 - (#keys=0) "DEPARTMENTS"."DEPARTMENT_ID"[NUMBER,22],
"EMPLOYEE_ID"[NUMBER,22]
3 - (#keys=0) "DEPARTMENTS"."DEPARTMENT_ID"[NUMBER,22],
"EMPLOYEES".ROWID[ROWID,10], "EMPLOYEE_ID"[NUMBER,22]
4 - "DEPARTMENTS"."DEPARTMENT_ID"[NUMBER,22]
5 - "EMPLOYEES".ROWID[ROWID,10], "EMPLOYEE_ID"[NUMBER,22]
39 rows selected.
SQL>
-------------------------------------------------------------set echo on
explain plan for
SELECT /*+ NO_MERGE(v2) INDEX(v2.v1.employees emp_emp_id_pk)
FULL(v2.departments) */ *
FROM
v2
WHERE department_id = 30;
select * from table(DBMS_XPLAN.DISPLAY(null,null,'ALL'));

12) Exit from your SQL*Plus session and clean up your environment by executing the
hr_hint_cleanup.sh script.
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$ ./hr_hint_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 16:10:09 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> revoke dba from hr;
revoke dba from hr
*
ERROR at line 1:
ORA-01951: ROLE 'DBA' not granted to 'HR'

Oracle Database 11g: SQL Tuning Workshop A - 289

Practice 9-1: Using Hints (continued)


SQL>
SQL> drop view hr.v1;
View dropped.
SQL>
SQL> drop view hr.v2;
View dropped.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @hr_hint_cleanup.sql
-------------------------------------------------------------set echo on
revoke dba from hr;
drop view hr.v1;
drop view hr.v2;
exit;

13) In this third case, you investigate how to influence optimizers joins. From your
terminal session, execute the sh_hint_setup.sh script.
[oracle@edrsr33p1-orcl Hints]$ ./sh_hint_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 19:38:18 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Oracle Database 11g: SQL Tuning Workshop A - 290

Practice 9-1: Using Hints (continued)


Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> alter user sh identified by sh acccount unlock;
alter user sh identified by sh acccount unlock
*
ERROR at line 1:
ORA-00922: missing or invalid option

SQL>
SQL> grant dba to sh;
Grant succeeded.
SQL>
SQL> connect sh/sh
Connected.
SQL>
SQL> exec dbms_stats.delete_schema_stats('SH');
PL/SQL procedure successfully completed.
SQL>
SQL> exec
dbms_stats.set_table_stats('SH','SALES',null,null,null,10,5);
PL/SQL procedure successfully completed.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @sh_hint_setup.sql

Oracle Database 11g: SQL Tuning Workshop A - 291

Practice 9-1: Using Hints (continued)


-------------------------------------------------------------set echo on
alter user sh identified by sh acccount unlock;
grant dba to sh;
connect sh/sh
exec dbms_stats.delete_schema_stats('SH');
exec dbms_stats.set_table_stats('SH','SALES',null,null,null,10,5);
exit;

14) From your terminal session, connect as the SH user in the SQL*Plus session. After
you are connected, ensure that you flush your SGA content using the
flush_sga.sql script, and set some important session parameters using the
set_sh_session.sql script. Ensure that you do not disconnect from your
established SQL*Plus session.
[oracle@edrsr33p1-orcl Hints]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 19:38:35 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @flush_sga
SQL>
SQL> alter system flush shared_pool;
System altered.
SQL>
SQL> alter system flush buffer_cache;
System altered.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>

@set_sh_session
set linesize 200
set pagesize 200
set timing on

Oracle Database 11g: SQL Tuning Workshop A - 292

Practice 9-1: Using Hints (continued)


SQL>
SQL> alter session set optimizer_dynamic_sampling=0;
Session altered.
Elapsed: 00:00:00.00
SQL>
-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;
-------------------------------------------------------------set linesize 200
set pagesize 200
set timing on
alter session set optimizer_dynamic_sampling=0;

15) From your SQL*Plus session, execute the following query and determine its
execution plan:
select count(*) from sales s, customers c where s.cust_id=c.cust_id;
SQL> @exec_and_show_sh_exec_plan
SQL> set echo on
SQL>
SQL> select count(*) from sales s, customers c where
s.cust_id=c.cust_id;
COUNT(*)
---------918843
Elapsed: 00:00:03.59
SQL>
SQL> select * from
table(dbms_xplan.display_cursor(null,null,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 88r79fy8nphrc, child number 0
------------------------------------select count(*) from sales s, customers c where s.cust_id=c.cust_id
Plan hash value: 2841872969

Oracle Database 11g: SQL Tuning Workshop A - 293

Practice 9-1: Using Hints (continued)


----------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
| Pstart| Pstop |
----------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
3
(100)|
|
|
|
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
|
|
2 |
NESTED LOOPS
|
|
121K| 3081K|
3
(0)| 00:00:01 |
|
|
|
3 |
PARTITION RANGE ALL|
|
10 |
130 |
3
(0)| 00:00:01 |
1 |
28 |
|
4 |
TABLE ACCESS FULL | SALES
|
10 |
130 |
3
(0)| 00:00:01 |
1 |
28 |
|* 5 |
INDEX UNIQUE SCAN | CUSTOMERS_PK | 12138 |
154K|
0
(0)|
|
|
|
----------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------5 - access("S"."CUST_ID"="C"."CUST_ID")

22 rows selected.
Elapsed: 00:00:00.15
SQL>
-------------------------------------------------------------set echo on
select count(*) from sales s, customers c where s.cust_id=c.cust_id;
select * from table(dbms_xplan.display_cursor(null,null,'TYPICAL'));

16) Before you continue, ensure that you flush the content of your SGA to avoid caching
issues later. Use the flush_sga.sql script.
SQL> @flush_sga
SQL> set echo on
SQL>
SQL> alter system flush shared_pool;
System altered.
Elapsed: 00:00:00.12
SQL>
SQL> alter system flush buffer_cache;
System altered.

Oracle Database 11g: SQL Tuning Workshop A - 294

Practice 9-1: Using Hints (continued)


Elapsed: 00:00:00.18
SQL>
-------------------------------------------------------------set echo on
alter system flush shared_pool;
alter system flush buffer_cache;

17) Using hints only, how would you enhance the performance of the same query you
executed in step 15? Make sure you verify your implementation.
a) In step 15, the optimizer chose a NESTED LOOPS join. You can try to force a
hash join instead, using the LEADING and USE_HASH hints.
SQL> @exec_and_show_sh_hint_exec_plan
SQL> set echo on
SQL>
SQL> select /*+ LEADING(c s) USE_HASH(s) */ count(*) from sales s,
customers c where s.cust_id=c.cust_id;
COUNT(*)
---------918843
Elapsed: 00:00:00.40
SQL>
SQL> select * from
table(dbms_xplan.display_cursor(null,null,'TYPICAL'));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------SQL_ID 9k8wcn7ckb7c1, child number 0
------------------------------------select /*+ LEADING(c s) USE_HASH(s) */ count(*) from sales s,
customers
c where s.cust_id=c.cust_id
Plan hash value: 3568173901
-------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes
|TempSpc| Cost (%CPU)| Time
| Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
|
|
|
182 (100)|
|
|
|
|
1 | SORT AGGREGATE
|
|
1 |
26 |
|
|
|
|
|
|* 2 |
HASH JOIN
|
|
121K| 3081K|
2968K|
182
(2)| 00:00:02 |
|
|

Oracle Database 11g: SQL Tuning Workshop A - 295

Practice 9-1: Using Hints (continued)


|
3 |
INDEX FAST FULL SCAN| CUSTOMERS_PK |
121K| 1540K|
|
9
(0)| 00:00:01 |
|
|
|
4 |
PARTITION RANGE ALL |
|
10 |
130 |
|
3
(0)| 00:00:01 |
1 |
28 |
|
5 |
TABLE ACCESS FULL | SALES
|
10 |
130 |
|
3
(0)| 00:00:01 |
1 |
28 |
-------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------2 - access("S"."CUST_ID"="C"."CUST_ID")

23 rows selected.
Elapsed: 00:00:00.13
SQL>
-------------------------------------------------------------set echo on
select /*+ LEADING(c s) USE_HASH(s) */ count(*) from sales s,
customers c where s.cust_id=c.cust_id;
select * from table(dbms_xplan.display_cursor(null,null,'TYPICAL'));

18) Exit from your SQL*Plus session and clean up your environment by executing the
sh_hint_cleanup.sh script.
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
[oracle@edrsr33p1-orcl Hints]$ ./sh_hint_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 19:43:04 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> Connected.
SQL> SQL>
User altered.
SQL> SQL> revoke dba from sh

Oracle Database 11g: SQL Tuning Workshop A - 296

Practice 9-1: Using Hints (continued)


*
ERROR at line 1:
ORA-01951: ROLE 'DBA' not granted to 'SH'

SQL>
SQL>
SQL>
SQL>
SQL>
...
SQL>
SQL>
SQL>

SQL> SQL> Rem


Rem $Header: sh_main.sql 06-mar-2008.15:00:45 cbauwens Exp $
Rem
Rem sh_main.sql
Rem
Rem
SET ECHO OFF

specify password for SH as parameter 1:


specify default tablespace for SH as parameter 2:
specify temporary tablespace for SH as parameter 3:
specify password for SYS as parameter 4:
specify directory path for the data files as parameter 5:
writeable directory path for the log files as parameter 6:
specify version as parameter 7:

Session altered.

User dropped.
old
new

1: CREATE USER sh IDENTIFIED BY &pass


1: CREATE USER sh IDENTIFIED BY sh

User created.
old
new
old
new

1: ALTER USER sh DEFAULT TABLESPACE &tbs


1: ALTER USER sh DEFAULT TABLESPACE example
2: QUOTA UNLIMITED ON &tbs
2: QUOTA UNLIMITED ON example

User altered.
old
new

1: ALTER USER sh TEMPORARY TABLESPACE &ttbs


1: ALTER USER sh TEMPORARY TABLESPACE temp

User altered.

Grant succeeded.

...

Oracle Database 11g: SQL Tuning Workshop A - 297

Practice 9-1: Using Hints (continued)


Grant succeeded.

PL/SQL procedure successfully completed.


Connected.
Grant succeeded.
old
1: CREATE OR REPLACE DIRECTORY data_file_dir AS '&data_dir'
new
1: CREATE OR REPLACE DIRECTORY data_file_dir AS
'/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/'
Directory created.
old
new

1: CREATE OR REPLACE DIRECTORY log_file_dir AS '&log_dir'


1: CREATE OR REPLACE DIRECTORY log_file_dir AS '/home/oracle/'

Directory created.

Grant succeeded.

Grant succeeded.

Grant succeeded.
Connected.
Session altered.

Session altered.

Table created.

...

Comment created.

Creating OLAP metadata ...


<<<<< CREATE CWMLite Metadata for the Sales History Schema >>>>>

<<<<< FINAL PROCESSING >>>>>


- Changes have been committed
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 298

Practice 9-1: Using Hints (continued)


Commit complete.

gathering statistics ...


PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.


SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$

19) With this fourth case, you study the influence of the INDEX and AND_EQUAL hints.
From your terminal session, execute the sh_hint_index_setup.sh script to set
up the environment for this lab.
[oracle@edrsr33p1-orcl Hints]$ ./sh_hint_index_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 20:16:22 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> alter user sh identified by sh acccount unlock;
alter user sh identified by sh acccount unlock
*
ERROR at line 1:
ORA-00922: missing or invalid option

SQL>
SQL> grant dba to sh;
Grant succeeded.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 299

Practice 9-1: Using Hints (continued)


#!/bin/bash
cd /home/oracle/solutions/Hints
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @sh_hint_index_setup.sql
-------------------------------------------------------------set echo on
alter user sh identified by sh acccount unlock;
grant dba to sh;
exit;

20) From your terminal session, connect to the SQL*Plus session as the SH user. After
you are connected, execute the following scripts to further set up your environment
for this lab. Ensure that you stay connected to your session throughout this case.
[oracle@edrsr33p1-orcl Hints]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 20:16:28 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> @drop_index_customers
SQL>
SQL> @dait
SQL>
SQL> drop index CUSTOMERS_YOB_BIX;
Index dropped.
SQL> drop index CUSTOMERS_MARITAL_BIX;
Index dropped.
SQL> drop index CUSTOMERS_GENDER_BIX;

Oracle Database 11g: SQL Tuning Workshop A - 300

Practice 9-1: Using Hints (continued)


Index dropped.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3

@create_cust_indexes
set echo on
CREATE INDEX CUST_CUST_GENDER_idx
ON CUSTOMERS(CUST_GENDER)
NOLOGGING COMPUTE STATISTICS;

Index created.
SQL>
SQL> CREATE INDEX CUST_CUST_POSTAL_CODE_idx
2 ON CUSTOMERS(CUST_POSTAL_CODE)
3 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
SQL> CREATE INDEX CUST_CUST_CREDIT_LIMIT_idx
2 ON CUSTOMERS(CUST_CREDIT_LIMIT)
3 NOLOGGING COMPUTE STATISTICS;
Index created.
SQL>
-------------------------------------------------------------REM
REM
REM

drop all indexes on CUSTOMERS table


does not touch indexes associated with constraints
==================================================

set
store
save
set

termout off
set sqlplus_settings replace
buffer.sql replace
heading off verify off autotrace off feedback off

spool

dait.sql

SELECT 'drop index '||i.index_name||';'


FROM
user_indexes i
WHERE i.table_name = 'CUSTOMERS'
AND
NOT EXISTS
(SELECT 'x'
FROM
user_constraints c
WHERE c.index_name = i.index_name
AND
c.table_name = i.table_name
AND
c.status = 'ENABLED');
spool

off

get
buffer.sql nolist
@sqlplus_settings
set
termout on

Oracle Database 11g: SQL Tuning Workshop A - 301

Practice 9-1: Using Hints (continued)


set echo on
@dait
-------------------------------------------------------------set echo on
CREATE INDEX CUST_CUST_GENDER_idx
ON CUSTOMERS(CUST_GENDER)
NOLOGGING COMPUTE STATISTICS;
CREATE INDEX CUST_CUST_POSTAL_CODE_idx
ON CUSTOMERS(CUST_POSTAL_CODE)
NOLOGGING COMPUTE STATISTICS;
CREATE INDEX CUST_CUST_CREDIT_LIMIT_idx
ON CUSTOMERS(CUST_CREDIT_LIMIT)
NOLOGGING COMPUTE STATISTICS;

21) Determine the execution plan for the following query:


SELECT
c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000;
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7

set autotrace traceonly


set linesize 200 pagesize 1000
@index
set echo on
SELECT
c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 2008213504
-----------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost (%CPU)|
Time
|
------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 302

Practice 9-1: Using Hints (continued)


|
0 | SELECT STATEMENT |
|
6 | 1080 |
448
(1)|
00:00:05 |
|* 1 | TABLE ACCESS FULL| CUSTOMERS |
6 | 1080 |
448
(1)|
00:00:05 |
-----------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
1460 consistent gets
0 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
-------------------------------------------------------------set echo on
SELECT
c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

22) Try to get a better execution plan using the INDEX hint for the same query you
investigated in step 21. Which index is best suited?
a) The CUST_CUST_GENDER_IDX index is the best one for this query. Note that
using the INDEX hint without specifying any index leads the optimizer to use the
CUST_CUST_GENDER_IDX index.
SQL>
SQL>
SQL>
SQL>
2
3
4

@index_hint
set echo on
SELECT /*+ INDEX (c &indexname) */
c.*
FROM
customers c
WHERE cust_gender
= 'M'

Oracle Database 11g: SQL Tuning Workshop A - 303

Practice 9-1: Using Hints (continued)


5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for indexname: CUST_CUST_CREDIT_LIMIT_IDX
old
1: SELECT /*+ INDEX (c &indexname) */
new
1: SELECT /*+ INDEX (c CUST_CUST_CREDIT_LIMIT_IDX) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 1407552528
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 | 1074
(1)| 00:00:11 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 | 1074
(1)| 00:00:11 |
|* 2 |
INDEX RANGE SCAN
| CUST_CUST_CREDIT_LIMIT_IDX |
6938 |
|
14
(0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_GENDER"='M')
2 - access("CUST_CREDIT_LIMIT"=10000)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
1053 consistent gets
13 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL>
SQL>
SQL>
SQL>
2

@index_hint
set echo on
SELECT /*+ INDEX (c &indexname) */
c.*

Oracle Database 11g: SQL Tuning Workshop A - 304

Practice 9-1: Using Hints (continued)


3 FROM
customers c
4 WHERE cust_gender
= 'M'
5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for indexname: CUST_CUST_GENDER_IDX
old
1: SELECT /*+ INDEX (c &indexname) */
new
1: SELECT /*+ INDEX (c CUST_CUST_GENDER_IDX) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 3629874189
--------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 |
1080 | 1164
(1)| 00:00:12 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 |
1080 | 1164
(1)| 00:00:12 |
|* 2 |
INDEX RANGE SCAN
| CUST_CUST_GENDER_IDX | 27750 |
|
51
(0)| 00:00:01 |
--------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_CREDIT_LIMIT"=10000)
2 - access("CUST_GENDER"='M')

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
1399 consistent gets
68 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> @index_hint
SQL> set echo on
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 305

Practice 9-1: Using Hints (continued)


SQL> SELECT /*+ INDEX (c &indexname) */
2 c.*
3 FROM
customers c
4 WHERE cust_gender
= 'M'
5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for indexname: CUST_CUST_POSTAL_CODE_IDX
old
1: SELECT /*+ INDEX (c &indexname) */
new
1: SELECT /*+ INDEX (c CUST_CUST_POSTAL_CODE_IDX) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 1928091631
-------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 2 |
INDEX FULL SCAN
| CUST_CUST_POSTAL_CODE_IDX |
89 |
|
134
(1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')
2 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
251 consistent gets
132 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> @index_hint
SQL> set echo on

Oracle Database 11g: SQL Tuning Workshop A - 306

Practice 9-1: Using Hints (continued)


SQL>
SQL> SELECT /*+ INDEX (c &indexname) */
2 c.*
3 FROM
customers c
4 WHERE cust_gender
= 'M'
5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for indexname:
old
1: SELECT /*+ INDEX (c &indexname) */
new
1: SELECT /*+ INDEX (c ) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 1928091631
-------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 2 |
INDEX FULL SCAN
| CUST_CUST_POSTAL_CODE_IDX |
89 |
|
134
(1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')
2 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
251 consistent gets
0 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 307

Practice 9-1: Using Hints (continued)


-------------------------------------------------------------set echo on
SELECT /*+ INDEX (c &indexname) */
c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

23) Investigate the same query, but this time using the AND_EQUAL hint. Can you find a
better execution plan with this hint?
a) Using the AND_EQUAL hint allows the optimizer to explicitly choose an
execution plan that uses an access path that merges the scans on several singlecolumn indexes. However, no combination is better than the
CUST_CUST_GENDER_IDX index.
SQL> @and_equal_hint
SQL> set echo on
SQL>
SQL> SELECT /*+ AND_EQUAL (c &index_name1, &index_name2) */
2 c.*
3 FROM
customers c
4 WHERE cust_gender
= 'M'
5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for index_name1: CUST_CUST_CREDIT_LIMIT_IDX
Enter value for index_name2: CUST_CUST_GENDER_IDX
old
1: SELECT /*+ AND_EQUAL (c &index_name1, &index_name2) */
new
1: SELECT /*+ AND_EQUAL (c CUST_CUST_CREDIT_LIMIT_IDX,
CUST_CUST_GENDER_IDX) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 1121089724
--------------------------------------------------------------------------------------------------------| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
--------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 | 1416
(1)| 00:00:15 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 | 1416
(1)| 00:00:15 |
|
2 |
AND-EQUAL
|
|
|
|
|
|

Oracle Database 11g: SQL Tuning Workshop A - 308

Practice 9-1: Using Hints (continued)


|* 3 |
INDEX RANGE SCAN
| CUST_CUST_CREDIT_LIMIT_IDX |
6938 |
|
14
(0)| 00:00:01 |
|* 4 |
INDEX RANGE SCAN
| CUST_CUST_GENDER_IDX
|
27750 |
|
51
(0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804 AND
"CUST_CREDIT_LIMIT"=10000 AND
"CUST_GENDER"='M')
3 - access("CUST_CREDIT_LIMIT"=10000)
4 - access("CUST_GENDER"='M')

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
9160 consistent gets
0 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
SQL> @and_equal_hint
SQL> set echo on
SQL>
SQL> SELECT /*+ AND_EQUAL (c &index_name1, &index_name2) */
2 c.*
3 FROM
customers c
4 WHERE cust_gender
= 'M'
5 AND
cust_postal_code = 40804
6 AND
cust_credit_limit = 10000
7 /
Enter value for index_name1: CUST_CUST_CREDIT_LIMIT_IDX
Enter value for index_name2: CUST_CUST_POSTAL_CODE_IDX
old
1: SELECT /*+ AND_EQUAL (c &index_name1, &index_name2) */
new
1: SELECT /*+ AND_EQUAL (c CUST_CUST_CREDIT_LIMIT_IDX,
CUST_CUST_POSTAL_CODE_IDX) */
6 rows selected.

Execution Plan
---------------------------------------------------------Plan hash value: 1928091631
--------------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 309

Practice 9-1: Using Hints (continued)


| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 1 | TABLE ACCESS BY INDEX ROWID| CUSTOMERS
|
6 | 1080 |
218
(1)| 00:00:03 |
|* 2 |
INDEX FULL SCAN
| CUST_CUST_POSTAL_CODE_IDX |
89 |
|
134
(1)| 00:00:02 |
-------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------1 - filter("CUST_CREDIT_LIMIT"=10000 AND "CUST_GENDER"='M')
2 - filter(TO_NUMBER("CUST_POSTAL_CODE")=40804)

Statistics
---------------------------------------------------------1 recursive calls
0 db block gets
251 consistent gets
0 physical reads
0 redo size
2570 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
SQL>
-------------------------------------------------------------set echo on
SELECT /*+ AND_EQUAL (c &index_name1, &index_name2) */
c.*
FROM
customers c
WHERE cust_gender
= 'M'
AND
cust_postal_code = 40804
AND
cust_credit_limit = 10000
/

24) Exit from your SQL*Plus session and clean up your environment by executing the
sh_hint_cleanup.sh script.
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

Oracle Database 11g: SQL Tuning Workshop A - 310

Practice 9-1: Using Hints (continued)


[oracle@edrsr33p1-orcl Hints]$
[oracle@edrsr33p1-orcl Hints]$ ./sh_hint_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 1 19:43:04 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> Connected.
SQL> SQL>
User altered.
SQL> SQL> revoke dba from sh
*
ERROR at line 1:
ORA-01951: ROLE 'DBA' not granted to 'SH'

SQL>
SQL>
SQL>
SQL>
SQL>
...
SQL>
SQL>
SQL>

SQL> SQL> Rem


Rem $Header: sh_main.sql 06-mar-2008.15:00:45 cbauwens Exp $
Rem
Rem sh_main.sql
Rem
Rem
SET ECHO OFF

specify password for SH as parameter 1:


specify default tablespace for SH as parameter 2:
specify temporary tablespace for SH as parameter 3:
specify password for SYS as parameter 4:
specify directory path for the data files as parameter 5:
writeable directory path for the log files as parameter 6:
specify version as parameter 7:

Session altered.

User dropped.
old
new

1: CREATE USER sh IDENTIFIED BY &pass


1: CREATE USER sh IDENTIFIED BY sh

Oracle Database 11g: SQL Tuning Workshop A - 311

Practice 9-1: Using Hints (continued)


User created.
old
new
old
new

1: ALTER USER sh DEFAULT TABLESPACE &tbs


1: ALTER USER sh DEFAULT TABLESPACE example
2: QUOTA UNLIMITED ON &tbs
2: QUOTA UNLIMITED ON example

User altered.
old
new

1: ALTER USER sh TEMPORARY TABLESPACE &ttbs


1: ALTER USER sh TEMPORARY TABLESPACE temp

User altered.

Grant succeeded.

...

Grant succeeded.

PL/SQL procedure successfully completed.


Connected.
Grant succeeded.
old
1: CREATE OR REPLACE DIRECTORY data_file_dir AS '&data_dir'
new
1: CREATE OR REPLACE DIRECTORY data_file_dir AS
'/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/'
Directory created.
old
new

1: CREATE OR REPLACE DIRECTORY log_file_dir AS '&log_dir'


1: CREATE OR REPLACE DIRECTORY log_file_dir AS '/home/oracle/'

Directory created.

Grant succeeded.

Grant succeeded.

Grant succeeded.
Connected.
Session altered.

Session altered.

Oracle Database 11g: SQL Tuning Workshop A - 312

Practice 9-1: Using Hints (continued)


Table created.

...

Comment created.

Creating OLAP metadata ...


<<<<< CREATE CWMLite Metadata for the Sales History Schema >>>>>

<<<<< FINAL PROCESSING >>>>>


- Changes have been committed
PL/SQL procedure successfully completed.

Commit complete.

gathering statistics ...


PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.


SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Hints]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Access_Advisor/sh
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
cp * $ORACLE_HOME/demo/schema/sales_history
sqlplus / as sysdba <<FIN!
SET
SET
SET
SET

ECHO ON
FEEDBACK 1
NUMWIDTH 10
LINESIZE 8000

Oracle Database 11g: SQL Tuning Workshop A - 313

Practice 9-1: Using Hints (continued)


SET
SET
SET
SET

TRIMSPOOL ON
TAB OFF
PAGESIZE 100
LONG 1000

CONNECT / AS SYSDBA
alter user sh identified by sh account unlock;
revoke dba from sh;
@sh_main sh example temp oracle
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/
/home/oracle/ v3
exit;
FIN!

25) In the fifth case, you retrieve the first rows of your query as fast as possible. Connect
to the SQL*Plus session as the SYS user and ensure that you set the following
SQL*Plus environment variables:
timing on
autotrace on
pagesize 200
linesize 200
[oracle@edrsr33p1-orcl Hints]$ sqlplus / as sysdba
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Apr 29
21:02:19 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data
Mining
and Real Application Testing options
SQL> set timing on autotrace on pagesize 200 linesize 200
SQL>

26) From the same SQL*Plus session, execute the following query. Based on the fact that
you want to retrieve the first 10 rows as fast as possible, what do you observe?
SELECT employee_id, department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id;
a) It takes a bit of time before the result starts to be printed on the screen. This is
because a merge join is used. It needs to sort all data before producing rows.

Oracle Database 11g: SQL Tuning Workshop A - 314

Practice 9-1: Using Hints (continued)


SQL> SELECT employee_id, department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id;
2
3
EMPLOYEE_ID DEPARTMENT_NAME
----------- -----------------------------200 Administration
201 Marketing
202 Marketing
114 Purchasing
115 Purchasing
116 Purchasing
117 Purchasing

113 Finance
205 Accounting
206 Accounting
106 rows selected.
Elapsed: 00:00:00.09
Execution Plan
---------------------------------------------------------Plan hash value: 1343509718
------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows |
Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
106 |
2438 |
6 (17)| 00:00:01 |
|
1 | MERGE JOIN
|
|
106 |
2438 |
6 (17)| 00:00:01 |
|
2 |
TABLE ACCESS BY INDEX ROWID| DEPARTMENTS |
27 |
432 |
2
(0)| 00:00:01 |
|
3 |
INDEX FULL SCAN
| DEPT_ID_PK |
27 |
|
1
(0)| 00:00:01 |
|* 4 |
SORT JOIN
|
|
107 |
749 |
4 (25)| 00:00:01 |
|
5 |
TABLE ACCESS FULL
| EMPLOYEES
|
107 |
749 |
3
(0)| 00:00:01 |
------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")
filter("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

Oracle Database 11g: SQL Tuning Workshop A - 315

Practice 9-1: Using Hints (continued)


Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
19 consistent gets
0 physical reads
0 redo size
2435 bytes sent via SQL*Net to client
497 bytes received via SQL*Net from client
9 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
106 rows processed
SQL>

27) Using a hint, how can you ensure that the previous query starts fetching rows faster?
Test your solution.
a) Using the FIRST_ROWS(10) hint, a nested loop is chosen by the optimizer. It is
faster to retrieve the first rows.
SQL> SELECT /*+ FIRST_ROWS(10) */ employee_id, department_name
FROM hr.employees e, hr.departments d
WHERE e.department_id = d.department_id;
2
3
EMPLOYEE_ID DEPARTMENT_NAME
----------- -----------------------------200 Administration
201 Marketing
202 Marketing
114 Purchasing
115 Purchasing
116 Purchasing

112 Finance
113 Finance
205 Accounting
206 Accounting
106 rows selected.
Elapsed: 00:00:00.02
Execution Plan
---------------------------------------------------------Plan hash value: 1021246405
-------------------------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 316

Practice 9-1: Using Hints (continued)


| Id | Operation
| Name
|
Rows | Bytes | Cost (%CPU)| Time
|
------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
10 |
230 |
3
(0)| 00:00:01 |
|
1 | NESTED LOOPS
|
|
|
|
|
|
|
2 |
NESTED LOOPS
|
|
10 |
230 |
3
(0)| 00:00:01 |
|
3 |
TABLE ACCESS FULL
| DEPARTMENTS
|
25 |
400 |
2
(0)| 00:00:01 |
|* 4 |
INDEX RANGE SCAN
| EMP_DEPARTMENT_IX |
8 |
|
0
(0)| 00:00:01 |
|
5 |
TABLE ACCESS BY INDEX ROWID| EMPLOYEES
|
3 |
21 |
1
(0)| 00:00:01 |
------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------4 - access("E"."DEPARTMENT_ID"="D"."DEPARTMENT_ID")

Statistics
---------------------------------------------------------0 recursive calls
0 db block gets
40 consistent gets
0 physical reads
0 redo size
2435 bytes sent via SQL*Net to client
497 bytes received via SQL*Net from client
9 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
106 rows processed
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 317

Practices for Lesson 10

Oracle Database 11g: SQL Tuning Workshop A - 318

Practice 10-1: Tracing Applications


In this practice, you define a service and use it to generate traces. You then interpret
generated trace files. You can find all needed script files for this lab in your
$HOME/solutions/Application_Tracing directory.
1) Initialize your environment be executing the at_setup.sh script from a terminal
session connected as the oracle user.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./at_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:25:55 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user trace cascade;
drop user trace cascade
*
ERROR at line 1:
ORA-01918: user 'TRACE' does not exist

SQL>
SQL> create user trace identified by trace default tablespace users
temporary tablespace temp;
User created.
SQL>
SQL> grant connect, resource, dba to trace;
Grant succeeded.
SQL>
SQL>
SQL> drop tablespace tracetbs including contents and datafiles;
drop tablespace tracetbs including contents and datafiles
*
ERROR at line 1:
ORA-00959: tablespace 'TRACETBS' does not exist

SQL>
SQL> drop tablespace tracetbs3 including contents and datafiles;
drop tablespace tracetbs3 including contents and datafiles
*
ERROR at line 1:
ORA-00959: tablespace 'TRACETBS3' does not exist

Oracle Database 11g: SQL Tuning Workshop A - 319

Practice 10-1: Tracing Applications (continued)


SQL>
SQL> create tablespace tracetbs
2 datafile '/u01/app/oracle/oradata/orcl/tracetbs.dbf' size 100m
3 extent management local uniform size 40k;
Tablespace created.
SQL>
SQL> create tablespace tracetbs3
2 datafile '/u01/app/oracle/oradata/orcl/tracetbs3.dbf' size 100m
3 extent management local uniform size 10m;
Tablespace created.
SQL>
SQL>
SQL> connect trace/trace
Connected.
SQL>
SQL> drop table sales purge;
drop table sales purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table sales as select * from sh.sales;
Table created.
SQL>
SQL>
SQL> drop table sales2 purge;
drop table sales2 purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
SQL> create table sales2 tablespace tracetbs as select * from
sh.sales where 1=2;
Table created.
SQL>
SQL> drop table sales3 purge;
drop table sales3 purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>

Oracle Database 11g: SQL Tuning Workshop A - 320

Practice 10-1: Tracing Applications (continued)


SQL> create table sales3 tablespace tracetbs3 as select * from
sh.sales where 1=2;
Table created.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @at_setup.sql
-------------------------------------------------------------set echo on
drop user trace cascade;
create user trace identified by trace default tablespace users
temporary tablespace temp;
grant connect, resource, dba to trace;

drop tablespace tracetbs including contents and datafiles;


drop tablespace tracetbs3 including contents and datafiles;
create tablespace tracetbs
datafile '/u01/app/oracle/oradata/orcl/tracetbs.dbf' size 100m
extent management local uniform size 40k;
create tablespace tracetbs3
datafile '/u01/app/oracle/oradata/orcl/tracetbs3.dbf' size 100m
extent management local uniform size 10m;

connect trace/trace
drop table sales purge;

Oracle Database 11g: SQL Tuning Workshop A - 321

Practice 10-1: Tracing Applications (continued)


create table sales as select * from sh.sales;

drop table sales2 purge;


create table sales2 tablespace tracetbs as select * from sh.sales
where 1=2;
drop table sales3 purge;
create table sales3 tablespace tracetbs3 as select * from sh.sales
where 1=2;
exit;

2) Before you can use a service in your applications, you have to make sure this service
is available from the tnsnames.ora file you use to connect to your database.
Modify this file to make sure it references a service called TRACESERV. You can use
the add_traceserv_tns.sh script to help you in this task.
TRACESERV =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = node)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = TRACESERV)
)
)
[oracle@edrsr33p1-orcl Application_Tracing]$ ./add_traceserv_tns.sh
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/ksh
y=`hostname`
DBNAME=orcl
sed 's/NODE/'$y'/'
/home/oracle/solutions/Application_Tracing/wrong_tnstraceserv.ora >
/home/oracle/solutions/Application_Tracing/tnstraceserv.ora
cp /u01/app/oracle/product/11.1.0/db_1/network/admin/tnsnames.ora
/u01/app/oracle/product/11.1.0/db_1/network/admin/tnsnames.ora.bak1
cat /home/oracle/solutions/Application_Tracing/tnstraceserv.ora >>
/u01/app/oracle/product/11.1.0/db_1/network/admin/tnsnames.ora
-------------------------------------------------------------TRACESERV =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = NODE)(PORT = 1521))
(CONNECT_DATA =

Oracle Database 11g: SQL Tuning Workshop A - 322

Practice 10-1: Tracing Applications (continued)


(SERVER = DEDICATED)
(SERVICE_NAME = TRACESERV)
)
)

3) You now need to declare the TRACESERV service in your database. So connect to
your database instance as the SYS user using a SQL*Plus session.
[oracle@edrsr33p1-orcl Application_Tracing]$ sqlplus / as sysdba
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:26:15 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

4) In your SQL*Plus session, create a new service called TRACESERV with the same
network name.
SQL> @add_traceserv_db
SQL>
SQL> select name from dba_services;
NAME
---------------------------------------------------------------SYS$BACKGROUND
SYS$USERS
seeddataXDB
seeddata
orclXDB
orcl
6 rows selected.
SQL>
SQL> exec DBMS_SERVICE.CREATE_SERVICE('TRACESERV','TRACESERV');
PL/SQL procedure successfully completed.
SQL>
SQL> select name from dba_services;
NAME
---------------------------------------------------------------SYS$BACKGROUND
SYS$USERS
seeddataXDB
seeddata
orclXDB

Oracle Database 11g: SQL Tuning Workshop A - 323

Practice 10-1: Tracing Applications (continued)


orcl
TRACESERV
7 rows selected.
SQL>
SQL>
-------------------------------------------------------------set echo on
select name from dba_services;
exec DBMS_SERVICE.CREATE_SERVICE('TRACESERV','TRACESERV');
select name from dba_services;

5) From the same SQL*Plus session, start the TRACESERV service.


SQL> @start_traceserv
SQL> set echo on
SQL>
SQL> show parameter service_names
NAME
TYPE
VALUE
------------------------------------ ----------- ----------------------------service_names
string
SQL>
SQL> exec DBMS_SERVICE.START_SERVICE('TRACESERV');
PL/SQL procedure successfully completed.
SQL>
SQL> show parameter service_names
NAME
TYPE
VALUE
------------------------------------ ----------- ----------------------------service_names
string
TRACESERV
SQL>
SQL>
-------------------------------------------------------------set echo on
show parameter service_names
exec DBMS_SERVICE.START_SERVICE('TRACESERV');
show parameter service_names

Oracle Database 11g: SQL Tuning Workshop A - 324

Practice 10-1: Tracing Applications (continued)


6) Exit from your SQL*Plus session.
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$

7) At this point, open a browser window and connect to Enterprise Manager Database
Control as the SYS user. Ensure that you navigate to the Top Services page.
a) Log in to Enterprise Manager as the SYS user.
b) On the Database Home page, click the Performance tab.
c) On the Performance page, click the Top Consumers link in the Additional
Monitoring links section of the page.
d) On the Top Consumers page, click the Top Services tab. This takes you to the
Top Services page.
8) You now execute seven workload scripts that are traced. All workload scripts run
under the TRACESERV service. Your goal is to analyze the generated trace files to
interpret what happens in the seven cases. From your terminal session, execute the
run_tracep0.sh script. This script is used to trigger the generation of statistics
for your TRACESERV service so it can be viewed from within Enterprise Manager.
As soon as you start the execution of the run_tracep0.sh script, move to the
next step of this lab.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep0.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:28:40 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP0';
Session altered.
SQL>
SQL> set termout off
SQL>
SQL> exit;

Oracle Database 11g: SQL Tuning Workshop A - 325

Practice 10-1: Tracing Applications (continued)


Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep0.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP0';
set termout off
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select
select

count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)
count(*)

from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from
from

dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;
dba_objects;

exec dbms_lock.sleep(60);
set termout on
exit;

Oracle Database 11g: SQL Tuning Workshop A - 326

Practice 10-1: Tracing Applications (continued)


9) Go back to the Top Consumers page in your Enterprise Manager session. Wait until
you see the TRACESERV service in the Active Services table, and enable tracing for
that service.
a) When you see TRACESERV in the Active Services table on the Top Services
page, select it, and click the Enable SQL Trace button.
b) On the Enable SQL Trace page, make sure Waits is set to TRUE, and Binds is set
to FALSE. Click OK.
c) Back to the Top Services page, you should see a confirmation message near the
top of the Top Consumers page.
10) When tracing for TRACESERV is enabled, execute the run_tracep1.sh script
from your terminal session. Observe your screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep1.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:29:48 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set workarea_size_policy=manual;
Session altered.
SQL>
SQL> alter session set sort_area_size=50000;
Session altered.
SQL>
SQL> alter session set hash_area_size=5000;
Session altered.
SQL>
SQL>
SQL> alter session set tracefile_identifier='mytraceP1';
Session altered.
SQL>
SQL>
SQL> set timing on

Oracle Database 11g: SQL Tuning Workshop A - 327

Practice 10-1: Tracing Applications (continued)


SQL>
SQL> select /*+ ORDERED USE_HASH(s2) */ count(*) from sales s1,
sales s2 where s1.cust_id=s2.cust_id;
COUNT(*)
---------172878975
Elapsed: 00:01:19.25
SQL>
SQL>
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceS1';
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> set timing on
SQL>
SQL> select /*+ ORDERED USE_HASH(s2) S1 */ count(*) from sales s1,
sales s2 where s1.cust_id=s2.cust_id;
COUNT(*)
---------172878975
Elapsed: 00:00:40.19
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep1.sql
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 328

Practice 10-1: Tracing Applications (continued)


[oracle@edrsr33p1-orcl Application_Tracing]$ cat run_tracep1.sql
set echo on
connect trace/trace@TRACESERV
alter session set workarea_size_policy=manual;
alter session set sort_area_size=50000;
alter session set hash_area_size=5000;

alter session set tracefile_identifier='mytraceP1';

set timing on
select /*+ ORDERED USE_HASH(s2) */ count(*) from sales s1, sales s2
where s1.cust_id=s2.cust_id;

connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceS1';
set timing on
select /*+ ORDERED USE_HASH(s2) S1 */ count(*) from sales s1, sales
s2 where s1.cust_id=s2.cust_id;
exit;

11) Execute the run_tracep2.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep2.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:31:57 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP2';
Session altered.

Oracle Database 11g: SQL Tuning Workshop A - 329

Practice 10-1: Tracing Applications (continued)


SQL>
SQL> set timing on
SQL>
SQL> declare
2
c
number := dbms_sql.open_cursor;
3
oname varchar2(50);
4
ignore integer;
5 begin
6
for i in 1 .. 5000 loop
7
dbms_sql.parse(c,'select object_name from dba_objects where
object_id = '||i , dbms_sql.native); -- use literal
8
dbms_sql.define_column(c, 1, oname, 50);
9
ignore := dbms_sql.execute(c);
10
if dbms_sql.fetch_rows(c)>0 then
11
dbms_sql.column_value(c, 1, oname);
12
end if;
13
end loop;
14
dbms_sql.close_cursor(c);
15 end;
16 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:49.36
SQL>
SQL>
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceS2';
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> declare
2
c number := dbms_sql.open_cursor;
3
oname varchar2(50);
4
ignore integer;
5 begin
6
dbms_sql.parse(c,'select object_name from dba_objects where
object_id = :y' , dbms_sql.native); -- use bind var
7
for i in 1 .. 5000 loop
8
dbms_sql.bind_variable(c,':y',i);
9
dbms_sql.define_column(c, 1, oname, 50);
10
ignore := dbms_sql.execute(c);
11
if dbms_sql.fetch_rows(c)>0 then
12
dbms_sql.column_value(c, 1, oname);
13
end if;
14
end loop;
15
dbms_sql.close_cursor(c);
16 end;
17 /
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 330

Practice 10-1: Tracing Applications (continued)


Elapsed: 00:00:00.86
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep2.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP2';
set timing on
declare
c
number := dbms_sql.open_cursor;
oname varchar2(50);
ignore integer;
begin
for i in 1 .. 5000 loop
dbms_sql.parse(c,'select object_name from dba_objects where
object_id = '||i , dbms_sql.native); -- use literal
dbms_sql.define_column(c, 1, oname, 50);
ignore := dbms_sql.execute(c);
if dbms_sql.fetch_rows(c)>0 then
dbms_sql.column_value(c, 1, oname);
end if;
end loop;
dbms_sql.close_cursor(c);
end;
/

connect trace/trace@TRACESERV

Oracle Database 11g: SQL Tuning Workshop A - 331

Practice 10-1: Tracing Applications (continued)


alter session set tracefile_identifier='mytraceS2';
declare
c number := dbms_sql.open_cursor;
oname varchar2(50);
ignore integer;
begin
dbms_sql.parse(c,'select object_name from dba_objects where
object_id = :y' , dbms_sql.native); -- use bind var
for i in 1 .. 5000 loop
dbms_sql.bind_variable(c,':y',i);
dbms_sql.define_column(c, 1, oname, 50);
ignore := dbms_sql.execute(c);
if dbms_sql.fetch_rows(c)>0 then
dbms_sql.column_value(c, 1, oname);
end if;
end loop;
dbms_sql.close_cursor(c);
end;
/
exit;

12) Execute the run_tracep3.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep3.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:32:53 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP3';
Session altered.
SQL>
SQL> update sales set amount_sold=20000 where prod_id=13 and
cust_id=987;
2 rows updated.
SQL>
SQL> commit;

Oracle Database 11g: SQL Tuning Workshop A - 332

Practice 10-1: Tracing Applications (continued)


Commit complete.
SQL>
SQL>
SQL> connect trace/trace
Connected.
SQL>
SQL> create index sales_prod_cust_indx on sales(prod_id,cust_id);
Index created.
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceS3';
Session altered.
SQL>
SQL> update sales set amount_sold=30000 where prod_id=13 and
cust_id=987;
2 rows updated.
SQL>
SQL> commit;
Commit complete.
SQL>
SQL> connect trace/trace
Connected.
SQL>
SQL> drop index sales_prod_cust_indx;
Index dropped.
SQL>
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1

Oracle Database 11g: SQL Tuning Workshop A - 333

Practice 10-1: Tracing Applications (continued)


export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep3.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP3';
update sales set amount_sold=20000 where prod_id=13 and cust_id=987;
commit;

connect trace/trace
create index sales_prod_cust_indx on sales(prod_id,cust_id);
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceS3';
update sales set amount_sold=30000 where prod_id=13 and cust_id=987;
commit;
connect trace/trace
drop index sales_prod_cust_indx;

exit;

13) Execute the run_tracep4.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep4.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:33:08 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 334

Practice 10-1: Tracing Applications (continued)


SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP4';
Session altered.
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

set timing on
DECLARE
TYPE SalesCurTyp IS REF CURSOR;
v_sales_cursor
SalesCurTyp;
sales_record
sh.sales%ROWTYPE;
v_stmt_str
VARCHAR2(200);
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'select * from sh.sales where amount_sold>0';
-- Open cursor and specify bind argument in USING clause:
OPEN v_sales_cursor FOR v_stmt_str;
-- Fetch rows from result set one at a time:
LOOP
FETCH v_sales_cursor INTO sales_record;
EXIT WHEN v_sales_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_sales_cursor;
END;
/

PL/SQL procedure successfully completed.


Elapsed: 00:00:26.84
SQL>
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceS4';
Session altered.
Elapsed: 00:00:00.00
SQL>
SQL> set timing on
SQL>
SQL> DECLARE
2
TYPE SalesCurTyp IS REF CURSOR;
3
TYPE SalesList IS TABLE OF sh.sales%ROWTYPE;
4
v_sales_cursor
SalesCurTyp;
5
sales_List
SalesList;
6
v_stmt_str
VARCHAR2(200);
7 BEGIN
8
-- Dynamic SQL statement with placeholder:

Oracle Database 11g: SQL Tuning Workshop A - 335

Practice 10-1: Tracing Applications (continued)


9
v_stmt_str := 'select /* S4 */ * from sh.sales where
amount_sold>0';
10
11
-- Open cursor:
12
OPEN v_sales_cursor FOR v_stmt_str;
13
14
-- Fetch rows from result set one at a time:
15
LOOP
16
FETCH v_sales_cursor BULK COLLECT INTO Sales_List LIMIT
10000;
17
EXIT WHEN v_sales_cursor%NOTFOUND;
18
END LOOP;
19
20
-- Close cursor:
21
CLOSE v_sales_cursor;
22 END;
23 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.09
SQL>
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep4.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP4';
set timing on
DECLARE
TYPE SalesCurTyp

IS REF CURSOR;

Oracle Database 11g: SQL Tuning Workshop A - 336

Practice 10-1: Tracing Applications (continued)


v_sales_cursor
SalesCurTyp;
sales_record
sh.sales%ROWTYPE;
v_stmt_str
VARCHAR2(200);
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'select * from sh.sales where amount_sold>0';
-- Open cursor and specify bind argument in USING clause:
OPEN v_sales_cursor FOR v_stmt_str;
-- Fetch rows from result set one at a time:
LOOP
FETCH v_sales_cursor INTO sales_record;
EXIT WHEN v_sales_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_sales_cursor;
END;
/

connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceS4';
set timing on
DECLARE
TYPE SalesCurTyp IS REF CURSOR;
TYPE SalesList IS TABLE OF sh.sales%ROWTYPE;
v_sales_cursor
SalesCurTyp;
sales_List
SalesList;
v_stmt_str
VARCHAR2(200);
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'select /* S4 */ * from sh.sales where
amount_sold>0';
-- Open cursor:
OPEN v_sales_cursor FOR v_stmt_str;
-- Fetch rows from result set one at a time:
LOOP
FETCH v_sales_cursor BULK COLLECT INTO Sales_List LIMIT 10000;
EXIT WHEN v_sales_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_sales_cursor;
END;
/

exit;

Oracle Database 11g: SQL Tuning Workshop A - 337

Practice 10-1: Tracing Applications (continued)


14) Execute the run_tracep5.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep5.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:33:59 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP5';
Session altered.
SQL>
SQL> insert into sales2 select * from sh.sales union all select *
from sales;
1837686 rows created.
SQL> commit;
Commit complete.
SQL>
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceS5';
Session altered.
SQL>
SQL> insert into sales3 select * from sh.sales union all select *
from sales;
1837686 rows created.
SQL> commit;
Commit complete.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production

Oracle Database 11g: SQL Tuning Workshop A - 338

Practice 10-1: Tracing Applications (continued)


With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep5.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP5';
insert into sales2 select * from sh.sales union all select * from
sales;
commit;

connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceS5';
insert into sales3 select * from sh.sales union all select * from
sales;
commit;
exit;

15) Execute the run_tracep6.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep6.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:34:26 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:34:26 2008


Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Oracle Database 11g: SQL Tuning Workshop A - 339

Practice 10-1: Tracing Applications (continued)


Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> update sales set amount_sold=amount_sold+1;
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP6';
Session altered.
SQL>
SQL> exec dbms_lock.sleep(30);
918843 rows updated.
SQL>
SQL> exec dbms_lock.sleep(60);
PL/SQL procedure successfully completed.
SQL>
SQL> set termout off
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
PL/SQL procedure successfully completed.
SQL>
SQL> rollback;
Rollback complete.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining

Oracle Database 11g: SQL Tuning Workshop A - 340

Practice 10-1: Tracing Applications (continued)


and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep6a.sql &
sqlplus / as sysdba @run_tracep6b.sql
--------------------------------------------------------------

set echo on
connect trace/trace
update sales set amount_sold=amount_sold+1;
exec dbms_lock.sleep(60);
rollback;
exit;
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP6';
exec dbms_lock.sleep(30);
set termout off
select cust_id, sum(amount_sold) from sales group by cust_id order
by cust_id;
set tournout on
exit;

Oracle Database 11g: SQL Tuning Workshop A - 341

Practice 10-1: Tracing Applications (continued)


16) Execute the run_tracep7.sh script from your terminal session. Observe your
screen.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./run_tracep7.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:36:10 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> connect trace/trace@TRACESERV
Connected.
SQL>
SQL> alter session set tracefile_identifier='mytraceP7';
Session altered.
SQL>
SQL> declare
2
c number := dbms_sql.open_cursor;
3
custid number;
4
amount number;
5
ignore integer;
6 begin
7
dbms_sql.parse(c,'select cust_id, sum(amount_sold) from
sales where cust_id=2 group by cust_id order by cust_id' ,
dbms_sql.native); -- use bind var
8
dbms_sql.define_column(c, 1, custid);
9
dbms_sql.define_column(c, 2, amount);
10
ignore := dbms_sql.execute(c);
11
if dbms_sql.fetch_rows(c)>0 then
12
dbms_sql.column_value(c, 1, custid);
13
dbms_sql.column_value(c, 2, amount);
14
end if;
15 end;
16 /
PL/SQL procedure successfully completed.
SQL>
SQL> connect trace/trace
Connected.
SQL>
SQL> create index sales_cust_indx on sales(cust_id);
Index created.
SQL>
SQL> exit;

Oracle Database 11g: SQL Tuning Workshop A - 342

Practice 10-1: Tracing Applications (continued)


Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba @run_tracep7.sql
-------------------------------------------------------------set echo on
connect trace/trace@TRACESERV
alter session set tracefile_identifier='mytraceP7';
declare
c number := dbms_sql.open_cursor;
custid number;
amount number;
ignore integer;
begin
dbms_sql.parse(c,'select cust_id, sum(amount_sold) from sales
where cust_id=2 group by cust_id order by cust_id' ,
dbms_sql.native); -- use bind var
dbms_sql.define_column(c, 1, custid);
dbms_sql.define_column(c, 2, amount);
ignore := dbms_sql.execute(c);
if dbms_sql.fetch_rows(c)>0 then
dbms_sql.column_value(c, 1, custid);
dbms_sql.column_value(c, 2, amount);
end if;
end;
/
connect trace/trace
create index sales_cust_indx on sales(cust_id);
exit;

17) Disable tracing for your database.

Oracle Database 11g: SQL Tuning Workshop A - 343

Practice 10-1: Tracing Applications (continued)


a) Go back to the Top Services page in your Enterprise Manager session.
b) Ensure that TRACESERV is selected, and click the Disable SQL trace button.
c) Back to the Top Services page, you should see a confirmation message near the
top of the Top Consumers page.
18) Try to find out all the trace files that were generated to handle the previous seven
cases.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./show_mytraces.sh
orcl_ora_19237_mytraceP0.trc
orcl_ora_19237_mytraceP0.trm
orcl_ora_19313_mytraceP1.trc
orcl_ora_19313_mytraceP1.trm
orcl_ora_19355_mytraceS1.trc
orcl_ora_19355_mytraceS1.trm
orcl_ora_19382_mytraceP2.trc
orcl_ora_19382_mytraceP2.trm
orcl_ora_19467_mytraceS2.trc
orcl_ora_19467_mytraceS2.trm
orcl_ora_19474_mytraceP3.trc
orcl_ora_19474_mytraceP3.trm
orcl_ora_19503_mytraceS3.trc
orcl_ora_19503_mytraceS3.trm
orcl_ora_19534_mytraceP4.trc
orcl_ora_19534_mytraceP4.trm
orcl_ora_19549_mytraceS4.trc
orcl_ora_19549_mytraceS4.trm
orcl_ora_19558_mytraceP5.trc
orcl_ora_19558_mytraceP5.trm
orcl_ora_19568_mytraceS5.trc
orcl_ora_19568_mytraceS5.trm
orcl_ora_19583_mytraceP6.trc
orcl_ora_19583_mytraceP6.trm
orcl_ora_19634_mytraceP7.trc
orcl_ora_19634_mytraceP7.trm
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
ls /u01/app/oracle/diag/rdbms/orcl/orcl/trace | grep mytrace

19) After you identify the location of those trace files, merge their content into one file
called mytrace.trc located in your
$HOME/solutions/Application_Tracing directory.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./merge_traces.sh
[oracle@edrsr33p1-orcl Application_Tracing]$
--------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 344

Practice 10-1: Tracing Applications (continued)


#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
trcsess output=mytrace.trc service=TRACESERV
/u01/app/oracle/diag/rdbms/orcl/orcl/trace/*.trc

20) Use tkprof over the mytrace.trc file to generate a compiled trace output called
myreport.txt located into your
$HOME/solutions/Application_Tracing directory.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./tkprof_traces.sh
TKPROF: Release 11.1.0.6.0 - Production on Fri Apr 4 20:37:27 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

[oracle@edrsr33p1-orcl Application_Tracing]$
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
tkprof mytrace.trc myreport.txt

21) In addition, run TKPROF over the trace file that was generated for case 7 (step 16)
with the EXPLAIN option set to your TRACE account.
[oracle@edrsr33p1-orcl Application_Tracing]$ tkprof
/u01/app/oracle/diag/rdbms/orcl/orcl/trace/*mytraceP7.trc
myreport2.txt explain=trace/trace
TKPROF: Release 11.1.0.6.0 - Production on Fri Apr 4 20:40:22 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Oracle Database 11g: SQL Tuning Workshop A - 345

Practice 10-1: Tracing Applications (continued)


[oracle@edrsr33p1-orcl Application_Tracing]$
[oracle@edrsr33p1-orcl Application_Tracing]$

22) After this is done, interpret the trace generated for case 1 (step 10). What do you
observe, and what are your conclusions?
a) This case tries to illustrate the consequences of using manually sized SQL area
parameters, such as HASH_AREA_SIZE. The first statement was executed using
a very small HASH_AREA_SIZE value. The immediate consequence was the
number of temporary segments needed to execute the statement. Later, the same
statement was executed, but this time using the default SQL area parameters,
which were sized automatically by the system to handle the needs. You can see a
high disk value as well as a high number of waits for temporary segments for the
first execution, compared to the second one.
select /*+ ORDERED USE_HASH(s2) */ count(*)
from
sales s1, sales s2 where s1.cust_id=s2.cust_id

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
2
1
------- --------------total
4
1

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

78.23

79.16

806500

8874

-------- ---------- ---------- ---------- ---------78.23

79.16

806500

8876

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows
Row Source Operation
------- --------------------------------------------------1 SORT AGGREGATE (cr=8874 pr=806500 pw=806500 time=0 us)
172878975
HASH JOIN (cr=8874 pr=806500 pw=806500 time=1723284 us
cost=1392520 size=5737160286 card=220660011)
918843
TABLE ACCESS FULL SALES (cr=4437 pr=4433 pw=4433
time=7413 us cost=1363 size=14132716 card=1087132)
918843
TABLE ACCESS FULL SALES (cr=4437 pr=4433 pw=4433
time=7485 us cost=1363 size=14132716 card=1087132)

Elapsed times include waiting on following events:


Event waited on
Times
Total Waited

Max. Wait

Oracle Database 11g: SQL Tuning Workshop A - 346

Practice 10-1: Tracing Applications (continued)


---------------------------------------Waited ---------- ----------SQL*Net message to client
2
0.00
0.00
reliable message
1
0.00
0.00
enq: KO - fast object checkpoint
1
0.00
0.00
direct path read
86
0.00
0.00
direct path write temp
2524
0.00
0.00
direct path read temp
797634
0.00
1.70
SQL*Net message from client
2
0.00
0.00
********************************************************************
************

select /*+ ORDERED USE_HASH(s2) S1 */ count(*)


from
sales s1, sales s2 where s1.cust_id=s2.cust_id

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
2
1
------- --------------total
4
1

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

40.02

40.14

10028

8874

-------- ---------- ---------- ---------- ---------40.03

40.14

10028

8876

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows
Row Source Operation
------- --------------------------------------------------1 SORT AGGREGATE (cr=8874 pr=10028 pw=10028 time=0 us)
172878975
HASH JOIN (cr=8874 pr=10028 pw=10028 time=1430806 us
cost=6891 size=5737160286 card=220660011)
918843
TABLE ACCESS FULL SALES (cr=4437 pr=4433 pw=4433
time=6837 us cost=1363 size=14132716 card=1087132)
918843
TABLE ACCESS FULL SALES (cr=4437 pr=4433 pw=4433
time=7084 us cost=1363 size=14132716 card=1087132)

Oracle Database 11g: SQL Tuning Workshop A - 347

Practice 10-1: Tracing Applications (continued)


Elapsed times include waiting on following events:
Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------SQL*Net message to client
2
0.00
0.00
direct path read
86
0.00
0.00
direct path write temp
166
0.00
0.00
direct path read temp
166
0.00
0.00
SQL*Net message from client
2
0.00
0.00
********************************************************************
************

23) Interpret the trace generated for case 2 (step 11). What do you observe, and what are
your conclusions?
a) For this case, the almost same statement was run 5000 times, but each time a
different literal was used to execute the query. This caused the system to parse
almost the same statement 5000 times, which is extremely inefficient although the
time precision is too low to give accurate information about the parse time of each
statement. However, another statement was also executed 5000 times, but this
time using a bind variable. The statement was parsed only once, and executed
5000 times. This behavior is much more efficient than the previous one.
select object_name
from
dba_objects where object_id = 1

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
1
0
------- --------------total
3
0

cpu

elapsed

disk

query

-------- ---------- ---------- ---------- ---------0.01

0.01

0.00

0.00

0.00

0.00

-------- ---------- ---------- ---------- ---------0.01

0.01

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows

current

Row Source Operation

Oracle Database 11g: SQL Tuning Workshop A - 348

Practice 10-1: Tracing Applications (continued)


------- --------------------------------------------------0 VIEW DBA_OBJECTS (cr=3 pr=0 pw=0 time=0 us cost=5 size=158
card=2)
0
UNION-ALL (cr=3 pr=0 pw=0 time=0 us)
0
FILTER (cr=3 pr=0 pw=0 time=0 us)
0
NESTED LOOPS (cr=3 pr=0 pw=0 time=0 us cost=5 size=109
card=1)
0
NESTED LOOPS (cr=3 pr=0 pw=0 time=0 us cost=4 size=105
card=1)
0
TABLE ACCESS BY INDEX ROWID OBJ$ (cr=3 pr=0 pw=0
time=0 us cost=3 size=82 card=1)
1
INDEX RANGE SCAN I_OBJ1 (cr=2 pr=0 pw=0 time=0 us
cost=2 size=0 card=1)(object id 36)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=23 card=1)(object id 47)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
0
TABLE ACCESS BY INDEX ROWID IND$ (cr=0 pr=0 pw=0 time=0
us cost=2 size=8 card=1)
0
INDEX UNIQUE SCAN I_IND1 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=0 card=1)(object id 41)
0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=2 size=28
card=1)
0
INDEX FULL SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=20 card=1)(object id 47)
0
INDEX RANGE SCAN I_OBJ4 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=8 card=1)(object id 39)
0
FILTER (cr=0 pr=0 pw=0 time=0 us)
0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=1 size=83
card=1)
0
INDEX FULL SCAN I_LINK1 (cr=0 pr=0 pw=0 time=0 us
cost=0 size=79 card=1)(object id 134)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
********************************************************************
************

select object_name
from
dba_objects where object_id = 2

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
1
1
------- ---------------

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

0.00

0.00

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

Oracle Database 11g: SQL Tuning Workshop A - 349

Practice 10-1: Tracing Applications (continued)


total
1

0.00

0.00

query

current

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)

...
select object_name
from
dba_objects where object_id = 5000

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
1
1
------- --------------total
3
1

cpu

elapsed

disk

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

0.00

0.00

-------- ---------- ---------- ---------- ---------0.00

0.00

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows
Row Source Operation
------- --------------------------------------------------1 VIEW DBA_OBJECTS (cr=5 pr=0 pw=0 time=0 us cost=5 size=158
card=2)
1
UNION-ALL (cr=5 pr=0 pw=0 time=0 us)
1
FILTER (cr=5 pr=0 pw=0 time=0 us)
1
NESTED LOOPS (cr=5 pr=0 pw=0 time=0 us cost=5 size=109
card=1)
1
NESTED LOOPS (cr=4 pr=0 pw=0 time=0 us cost=4 size=105
card=1)
1
TABLE ACCESS BY INDEX ROWID OBJ$ (cr=3 pr=0 pw=0
time=0 us cost=3 size=82 card=1)
1
INDEX RANGE SCAN I_OBJ1 (cr=2 pr=0 pw=0 time=0 us
cost=2 size=0 card=1)(object id 36)
1
INDEX RANGE SCAN I_USER2 (cr=1 pr=0 pw=0 time=0 us
cost=1 size=23 card=1)(object id 47)
1
INDEX RANGE SCAN I_USER2 (cr=1 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
0
TABLE ACCESS BY INDEX ROWID IND$ (cr=0 pr=0 pw=0 time=0
us cost=2 size=8 card=1)
0
INDEX UNIQUE SCAN I_IND1 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=0 card=1)(object id 41)

Oracle Database 11g: SQL Tuning Workshop A - 350

Practice 10-1: Tracing Applications (continued)


0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=2 size=28
card=1)
0
INDEX FULL SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=20 card=1)(object id 47)
0
INDEX RANGE SCAN I_OBJ4 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=8 card=1)(object id 39)
0
FILTER (cr=0 pr=0 pw=0 time=0 us)
0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=1 size=83
card=1)
0
INDEX FULL SCAN I_LINK1 (cr=0 pr=0 pw=0 time=0 us
cost=0 size=79 card=1)(object id 134)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
********************************************************************
************

select object_name
from
dba_objects where object_id = :y

call
count
rows
------- --------------Parse
1
0
Execute
5000
0
Fetch
5000
4928
------- --------------total
10001
4928

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.20

0.18

0.24

0.24

26837

-------- ---------- ---------- ---------- ---------0.45

0.43

26837

Misses in library cache during parse: 1


Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows
------0
card=2)
0
0
0
card=1)

Row Source Operation


--------------------------------------------------VIEW DBA_OBJECTS (cr=3 pr=0 pw=0 time=0 us cost=5 size=158
UNION-ALL (cr=3 pr=0 pw=0 time=0 us)
FILTER (cr=3 pr=0 pw=0 time=0 us)
NESTED LOOPS (cr=3 pr=0 pw=0 time=0 us cost=5 size=109

Oracle Database 11g: SQL Tuning Workshop A - 351

Practice 10-1: Tracing Applications (continued)


0
NESTED LOOPS (cr=3 pr=0 pw=0 time=0 us cost=4 size=105
card=1)
0
TABLE ACCESS BY INDEX ROWID OBJ$ (cr=3 pr=0 pw=0
time=0 us cost=3 size=82 card=1)
1
INDEX RANGE SCAN I_OBJ1 (cr=2 pr=0 pw=0 time=0 us
cost=2 size=0 card=1)(object id 36)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=23 card=1)(object id 47)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
0
TABLE ACCESS BY INDEX ROWID IND$ (cr=0 pr=0 pw=0 time=0
us cost=2 size=8 card=1)
0
INDEX UNIQUE SCAN I_IND1 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=0 card=1)(object id 41)
0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=2 size=28
card=1)
0
INDEX FULL SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=20 card=1)(object id 47)
0
INDEX RANGE SCAN I_OBJ4 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=8 card=1)(object id 39)
0
FILTER (cr=0 pr=0 pw=0 time=0 us)
0
NESTED LOOPS (cr=0 pr=0 pw=0 time=0 us cost=1 size=83
card=1)
0
INDEX FULL SCAN I_LINK1 (cr=0 pr=0 pw=0 time=0 us
cost=0 size=79 card=1)(object id 134)
0
INDEX RANGE SCAN I_USER2 (cr=0 pr=0 pw=0 time=0 us
cost=1 size=4 card=1)(object id 47)
********************************************************************
************

24) Interpret the trace generated for case 3 (step 12). What do you observe, and what are
your conclusions?
a) If you look closely at this case, you see that you access the complete table to
update only two rows. This is very inefficient and the alternate case is much better
as it uses an index to speed up the retrieval of the rows that need to be updated.
update sales set amount_sold=20000
where
prod_id=13 and cust_id=987

call
count
rows
------- --------------Parse
1
0
Execute
1
2
Fetch
0
0
------- --------------total
2
2

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.09

0.09

2442

4441

0.00

0.00

-------- ---------- ---------- ---------- ---------0.09

0.10

2442

4442

Oracle Database 11g: SQL Tuning Workshop A - 352

Practice 10-1: Tracing Applications (continued)


Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows
Row Source Operation
------- --------------------------------------------------0 UPDATE SALES (cr=4441 pr=2442 pw=2442 time=0 us)
2
TABLE ACCESS FULL SALES (cr=4441 pr=2442 pw=2442 time=17
us cost=1366 size=4251 card=109)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------db file scattered read
63
0.00
0.01
db file sequential read
3
0.00
0.00
SQL*Net message to client
1
0.00
0.00
SQL*Net message from client
1
0.00
0.00
********************************************************************
************

update sales set amount_sold=30000


where
prod_id=13 and cust_id=987

call
count
rows
------- --------------Parse
1
0
Execute
1
2
Fetch
0
0
------- --------------total
2
2

cpu

elapsed

disk

query

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

0.00

0.00

-------- ---------- ---------- ---------- ---------0.00

0.00

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows

current

Row Source Operation

Oracle Database 11g: SQL Tuning Workshop A - 353

Practice 10-1: Tracing Applications (continued)


------- --------------------------------------------------0 UPDATE SALES (cr=3 pr=0 pw=0 time=0 us)
2
INDEX RANGE SCAN SALES_PROD_CUST_INDX (cr=3 pr=0 pw=0
time=2 us cost=3 size=78 card=2)(object id 75372)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------SQL*Net message to client
1
0.00
0.00
SQL*Net message from client
1
0.00
0.00
********************************************************************
************

25) Interpret the trace generated for case 4 (step 13). What do you observe, and what are
your conclusions?
a) In this case, the first statement does not use the fetching mechanism appropriately.
One fetch operation is done for every single row retrieved. This is also very
inefficient. The alternate case is doing 92 fetches to retrieve the same amount of
rows. This technique is called array fetch.
select *
from
sh.sales where amount_sold>0

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
918844
918843
------- --------------total
918846
918843

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.02

0.02

0.00

0.00

5.89

5.93

918944

-------- ---------- ---------- ---------- ---------5.92

5.95

918944

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows
Row Source Operation
------- --------------------------------------------------918843 PARTITION RANGE ALL PARTITION: 1 28 (cr=918944 pr=0 pw=0
time=41095 us cost=547 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES PARTITION: 1 28 (cr=918944 pr=0
pw=0 time=27743 us cost=547 size=26646447 card=918843)

Oracle Database 11g: SQL Tuning Workshop A - 354

Practice 10-1: Tracing Applications (continued)


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

select /* S4 */ *
from
sh.sales where amount_sold>0

call
count
rows
------- --------------Parse
1
0
Execute
1
0
Fetch
92
918843
------- --------------total
94
918843

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

2.06

2.06

1811

-------- ---------- ---------- ---------- ---------2.06

2.06

1811

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows
Row Source Operation
------- --------------------------------------------------918843 PARTITION RANGE ALL PARTITION: 1 28 (cr=1811 pr=0 pw=0
time=21132 us cost=547 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES PARTITION: 1 28 (cr=1811 pr=0 pw=0
time=8617 us cost=547 size=26646447 card=918843)
********************************************************************
************

26) Interpret the trace generated for case 5 (step 14). What do you observe, and what are
your conclusions?
a) Here, the first statement incurs too many recursive calls to allocate extents to the
table. This is because the tablespace in which it is stored is not correctly set up for
extent allocations. The alternate case is much better as you can see the number of
disk values going way down compared to the first case. Also the number of
recursive statements in the second case should be much lower than the first one.
insert into sales2 select * from sh.sales union all select * from
sales

call
rows

count

cpu

elapsed

disk

query

Oracle Database 11g: SQL Tuning Workshop A - 355

current

Practice 10-1: Tracing Applications (continued)


------- --------------Parse
1
0
Execute
1
1837686
Fetch
0
0
------- --------------total
2
1837686

-------- ---------- ---------- ---------- ---------0.00

0.01

5.02

5.41

4432

28134

148866

0.00

0.00

-------- ---------- ---------- ---------- ---------5.02

5.42

4432

28136

148866

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows
Row Source Operation
------- --------------------------------------------------0 LOAD TABLE CONVENTIONAL (cr=44323 pr=4433 pw=4433 time=0
us)
1837686
UNION-ALL (cr=6160 pr=4432 pw=4432 time=58107 us)
918843
PARTITION RANGE ALL PARTITION: 1 28 (cr=1719 pr=0 pw=0
time=21204 us cost=545 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES PARTITION: 1 28 (cr=1719 pr=0
pw=0 time=8395 us cost=545 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES (cr=4441 pr=4432 pw=4432
time=6976 us cost=1369 size=94580484 card=1087132)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------db file scattered read
51
0.00
0.03
log file switch completion
2
0.00
0.00
log buffer space
8
0.09
0.23
SQL*Net message to client
1
0.00
0.00
SQL*Net message from client
1
0.00
0.00
********************************************************************
************
select file#
from
file$ where ts#=:1

call
count
rows
------- ---------------

cpu

elapsed

disk

query

current

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

Oracle Database 11g: SQL Tuning Workshop A - 356

Practice 10-1: Tracing Applications (continued)


Parse
1812
0
Execute
1812
0
Fetch
3624
1812
------- --------------total
7248
1812

0.02

0.02

0.02

0.03

0.05

0.05

7242

-------- ---------- ---------- ---------- ---------0.11

0.10

7242

Misses in library cache during parse: 1


Misses in library cache during execute: 1
Optimizer mode: CHOOSE
Parsing user id: SYS
(recursive depth: 1)
Rows
Row Source Operation
------- --------------------------------------------------1 TABLE ACCESS FULL FILE$ (cr=4 pr=0 pw=0 time=0 us cost=2
size=6 card=1)
********************************************************************
************

insert into sales3 select * from sh.sales union all select * from
sales

call
count
rows
------- --------------Parse
1
0
Execute
1
1837686
Fetch
0
0
------- --------------total
2
1837686

cpu

elapsed

disk

query

-------- ---------- ---------- ---------- ---------0.00

0.00

3.64

5.13

1087

22471

77748

0.00

0.00

-------- ---------- ---------- ---------- ---------3.65

5.14

1087

22472

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows

current

Row Source Operation

Oracle Database 11g: SQL Tuning Workshop A - 357

77748

Practice 10-1: Tracing Applications (continued)


------- --------------------------------------------------0 LOAD TABLE CONVENTIONAL (cr=22544 pr=1087 pw=1087 time=0
us)
1837686
UNION-ALL (cr=6160 pr=1087 pw=1087 time=58277 us)
918843
PARTITION RANGE ALL PARTITION: 1 28 (cr=1719 pr=0 pw=0
time=21371 us cost=545 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES PARTITION: 1 28 (cr=1719 pr=0
pw=0 time=8407 us cost=545 size=26646447 card=918843)
918843
TABLE ACCESS FULL SALES (cr=4441 pr=1087 pw=1087
time=7141 us cost=1369 size=94580484 card=1087132)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------buffer busy waits
3
0.00
0.00
undo segment extension
1
0.00
0.00
log file switch completion
5
0.08
0.12
log buffer space
30
0.15
1.24
db file scattered read
25
0.00
0.00
log file switch (private strand flush incomplete)
313
0.05
0.10
SQL*Net message to client
1
0.00
0.00
SQL*Net message from client
1
0.00
0.00
********************************************************************
************

27) Interpret the trace generated for case 6 (step 15). What do you observe, and what are
your conclusions?
a) This case is more difficult to understand. Here, you select a table that is entirely
locked by another transaction. This forces the query to generate consistent read
blocks for almost the entire table causing undo segments to be accessed. This is
materialized by an important disk value, and almost no current blocks.
select cust_id, sum(amount_sold)
from
sales group by cust_id order by cust_id

call
count
rows
------- --------------Parse
1
0

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

Oracle Database 11g: SQL Tuning Workshop A - 358

Practice 10-1: Tracing Applications (continued)


Execute
1
0
Fetch
472
7059
------- --------------total
474
7059

0.00

0.00

2.38

2.39

5333

978282

-------- ---------- ---------- ---------- ---------2.39

2.39

5333

978283

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
Rows
Row Source Operation
------- --------------------------------------------------7059 SORT GROUP BY (cr=978282 pr=5333 pw=5333 time=64 us
cost=1422 size=28265432 card=1087132)
918843
TABLE ACCESS FULL SALES (cr=978282 pr=5333 pw=5333
time=17979 us cost=1369 size=28265432 card=1087132)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------SQL*Net message to client
472
0.00
0.00
db file sequential read
3827
0.00
0.03
db file scattered read
37
0.00
0.01
latch: cache buffers lru chain
1
0.00
0.00
SQL*Net message from client
472
0.00
0.02
********************************************************************
************

28) Interpret the trace generated for case 7 (step 16). What do you observe, and what are
your conclusions?
a) For case 7, you should compare the content in myreport.txt with the content
in myreport2.txt. You should see that an index was added after the first trace
was generated. This situation can cause confusion especially if the trace does not
contain an execution plan to begin with. This is what you can see from within
myreport.txt:
select cust_id, sum(amount_sold)
from
sales where cust_id=2 group by cust_id order by cust_id

call
rows

count

cpu

elapsed

disk

query

Oracle Database 11g: SQL Tuning Workshop A - 359

current

Practice 10-1: Tracing Applications (continued)


------- --------------Parse
1
0
Execute
1
0
Fetch
1
1
------- --------------total
3
1

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

0.05

0.05

4441

-------- ---------- ---------- ---------- ---------0.06

0.06

4442

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182
(recursive depth: 1)
Rows
Row Source Operation
------- --------------------------------------------------1 SORT GROUP BY NOSORT (cr=4441 pr=0 pw=0 time=0 us cost=1366
size=1872 card=72)
176
TABLE ACCESS FULL SALES (cr=4441 pr=0 pw=0 time=153 us
cost=1366 size=1872 card=72)

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

b) This is what you see from myreport2.txt:


SQL ID : 51xxq509gnbbc
select cust_id, sum(amount_sold)
from
sales where cust_id=2 group by cust_id order by cust_id

call
count
rows
------- --------------Parse
1
0
Execute
2
0
Fetch
1
1
------- --------------total
4
1

cpu

elapsed

disk

query

current

-------- ---------- ---------- ---------- ---------0.00

0.00

0.00

0.00

0.05

0.05

4441

-------- ---------- ---------- ---------- ---------0.06

0.06

4442

Misses in library cache during parse: 1


Optimizer mode: ALL_ROWS
Parsing user id: 182 (TRACE)
(recursive depth: 1)

Oracle Database 11g: SQL Tuning Workshop A - 360

Practice 10-1: Tracing Applications (continued)


Rows
Row Source Operation
------- --------------------------------------------------1 SORT GROUP BY NOSORT (cr=4441 pr=0 pw=0 time=0 us cost=1366
size=1872 card=72)
176
TABLE ACCESS FULL SALES (cr=4441 pr=0 pw=0 time=153 us
cost=1366 size=1872 card=72)

Rows
------0
1
176
0

Execution Plan
--------------------------------------------------SELECT STATEMENT
MODE: ALL_ROWS
SORT (GROUP BY NOSORT)
TABLE ACCESS (BY INDEX ROWID) OF 'SALES' (TABLE)
INDEX
MODE: ANALYZED (RANGE SCAN) OF 'SALES_CUST_INDX'
(INDEX)

Elapsed times include waiting on following events:


Event waited on
Times
Max. Wait
Total Waited
---------------------------------------Waited ---------- ----------SQL*Net message to client
1
0.00
0.00
SQL*Net message from client
1
0.00
0.00
********************************************************************
************

29) You can now clean up your environment by executing the at_cleanup.sh script
from your terminal window.
[oracle@edrsr33p1-orcl Application_Tracing]$ ./at_cleanup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Fri Apr 4 20:45:24 2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL> drop user trace cascade;
User dropped.
SQL>
SQL> drop tablespace tracetbs including contents and datafiles;
Tablespace dropped.
SQL>
SQL> drop tablespace tracetbs3 including contents and datafiles;

Oracle Database 11g: SQL Tuning Workshop A - 361

Practice 10-1: Tracing Applications (continued)


Tablespace dropped.
SQL>
SQL> exit;
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Application_Tracing]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Application_Tracing
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
rm /u01/app/oracle/diag/rdbms/orcl/orcl/trace/*mytrace*.*
rm mytrace.trc
rm myreport.txt
rm myreport2.txt
rm $ORACLE_HOME/network/admin/tnsnames.ora
mv $ORACLE_HOME/network/admin/tnsnames.ora.bak1
$ORACLE_HOME/network/admin/tnsnames.ora
sqlplus / as sysdba @at_cleanup.sql
-------------------------------------------------------------set echo on
drop user trace cascade;
drop tablespace tracetbs including contents and datafiles;
drop tablespace tracetbs3 including contents and datafiles;
exit;

Oracle Database 11g: SQL Tuning Workshop A - 362

Practices for Lesson 11

Oracle Database 11g: SQL Tuning Workshop A - 363

Practice 11-1: Proactively Tuning High-Load SQL Statements


In this practice, you use the SQL Tuning Advisor to help you tune problematic SQL
statements.
1) Connect as SYSDBA through Database Control and navigate to the Performance tab
of the Database Control Home page. On the Performance tabbed page, make sure that
the View Data field is set to Real Time: 15 second Refresh. After this is done, open a
terminal emulator window connected as the oracle user. When this is done, change
your current directory to your lab directory: cd
$HOME/solutions/SQL_Tuning_Advisor. Then enter the following
command from the OS prompt: ./setup_dina.sh
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./setup_dina.sh
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./setup_dina.sh
PL/SQL procedure successfully completed.

Grant succeeded.

Session altered.

PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.

User altered.

User altered.

Index dropped.
drop index sales_time_idx
*
ERROR at line 1:
ORA-01418: specified index does not exist

Index created.
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$

-------------------------------------------------------------#!/bin/bash

Oracle Database 11g: SQL Tuning Workshop A - 364

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
cd /home/oracle/solutions/SQL_Tuning_Advisor
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus -s /NOLOG <<EOF
set echo on
connect / as sysdba
exec DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();
grant dba to SH;
rem -- event to allow setting very short Flushing interval
alter session set events '13508 trace name context forever, level
1';

rem -- change INTERVAL setting to 2 minutes


rem -- change RETENTION setting to 6 hours (total of 180 snapshots)
execute dbms_workload_repository.modify_snapshot_settings(interval
=> 2,retention => 360);

rem -- play with ADDM sensitiveness


exec
dbms_advisor.set_default_task_parameter('ADDM','DB_ACTIVITY_MIN',30)
;

alter user sh account unlock;


alter user sh identified by sh;
connect sh/sh
drop index sales_time_bix;
drop index sales_time_idx;
create index sales_time_idx on sales(time_id) compute statistics;

EOF

2) After this is executed, execute the start_dinas.sh script by using the following
command: ./start_dinas.sh
This script starts the workload used for this lab.

Oracle Database 11g: SQL Tuning Workshop A - 365

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./start_dinas.sh
Started stream with pid=30479
Started stream with pid=30480
Started stream with pid=30481
Started stream with pid=30482
Started stream with pid=30485
Started stream with pid=30486
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Tuning_Advisor
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

STREAM_NUM=0
MAX_STREAM=6
PIDLST=""
while [ $STREAM_NUM -lt $MAX_STREAM ]; do
# one more
let STREAM_NUM="STREAM_NUM+1"
# start one more stream
sqlplus -S sh/sh @dina.sql &
# remember PID
PIDLST="$! $PIDLST"
echo "Started stream with pid=$!"
done
#
# Save PID List
#
echo $PIDLST > /tmp/dina_pids
-------------------------------------------------------------DECLARE
n number;
BEGIN
for i in 1..1000 loop
select /*+ ORDERED USE_NL(c) FULL(c) FULL(s)*/ count(*) into n
from sales s, customers c

Oracle Database 11g: SQL Tuning Workshop A - 366

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
where c.cust_id = s.cust_id and CUST_FIRST_NAME='Dina'
order by time_id;
end loop;
END;
/

3) When the start_dinas.sh script completes, observe the Performance tabbed


page for around 10 minutes (5 snapshot icons). What are your conclusions?
a) You should see that the workload activity goes up very quickly. Because the CPU
used by the workload is very close to the maximum CPU available on your
system, there must be an issue with this workload. Because the most important
area corresponding to a wait class is the Other wait class, the issue must be
associated to that class. Note that the snapshot interval is now around two
minutes.
4) Fix the problem.
a) The fastest way to determine the problem is by looking at an Automatic Database
Diagnostic Monitor (ADDM) report analysis executed during the problematic
period. Then, by following its analysis, ADDM should guide you through the
process of fixing the problem.
b) Using the Database Control Home page, there are two different ways to identify
the correct ADDM analysis task:
i) If the time corresponding to the problematic time period corresponds with the
latest ADDM run detected by Database Control, you should find the link
corresponding to the correct performance analysis directly in the Diagnostic
Summary section of the Database Control Home page. Note that you should
wait around 8 to 10 minutes before the Diagnostic Summary section is
refreshed with the correct ADDM analysis. If you are in this case, click the
link corresponding to the number of findings right next to the ADDM
Findings row. This takes you to the corresponding Automatic Database
Diagnostic Monitor (ADDM) page.
ii) If not, you should open the Advisor Central page and search for the correct
ADDM task. This is how you can retrieve the task from the Advisor Central
page:
(1) On the Database Control Home page, click the Advisor Central link.
(2) On the Advisor Central page, in the search section, select ADDM from
the Advisory Type drop-down list, and Last 24 Hours from the
Advisor Runs drop-down list.
(3) After this is done, click Go.
(4) Then select the ADDM task corresponding to the time of the
problematic period.

Oracle Database 11g: SQL Tuning Workshop A - 367

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
(5) This takes you to the corresponding Automatic Database Diagnostic
Monitor (ADDM) page.
c) On the Automatic Database Diagnostic Monitor (ADDM) page, you should see
two main findings: CPU Usage and Top SQL by DB Time. Both should be close
to 100%. If they are not, ensure that what you see is the latest ADDM analysis.
d) Click the Top SQL by DB Time link.
e) On the Performance Finding Details: Top SQL by DB Time page, click Show
All Details link. You should see something similar to the following: Run SQL
Tuning Advisor on the SQL statement with SQL_ID "5mxdwvuf9j3vp". Next to
this recommendation, click the Run Advisor Now button.
f) Wait on the Processing: SQL Tuning Advisor Task SQL_TUNING_ page for a
while.
g) You are automatically directed to the Recommendations for SQL
ID:5mxdwvuf9j3vp page from where you should see two recommendations-one
for a SQL Profile and one for an index creation.
h) You can investigate the consequence of implementing the recommended profile
by comparing execution plans before and after profile implementation. You can
do so by clicking the Compare explain plan eyeglass icon for the corresponding
SQL Profile row. Because the potential benefit of using the proposed SQL profile
is very high (see both computed Costs), you implement the SQL profile.
i) On the Compare Explain Plans page, click the Recommendations for SQL
ID:5mxdwvuf9j3vp locator link.
j) Back to the Recommendations for SQL ID:5mxdwvuf9j3vp page, ensure that the
SQL Profile row is selected, and click Implement.
k) On Confirmation page, click Yes.
l) On Recommendations for SQL ID:5mxdwvuf9j3vp page, you should see the
following message at its top: Confirmation: The recommended SQL Profile has
been created successfully.
m) Click Return.
n) On SQL Tuning Results:TASK, click the Database Instance: orcl locator link.
o) Back to the Database Home Page, click the Performance tab.
5) After following the SQL Tuning Advisor recommendation to implement a SQL
Profile, how can you quickly verify that the problem is solved?
a) On the Performance tabbed page, you expect to see a dramatic drop for the Other
wait class in the Average Active Sessions graph. However, when you view the
graph you see that not has changed.
6) How do you interpret the result you see, and how would you ensure that the new
profile is taken into account?

Oracle Database 11g: SQL Tuning Workshop A - 368

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
a) A profile is taken into account the next time you execute the corresponding
statement. Because the SQL statement for which you created a profile takes a long
time to execute, you should wait for a long time to see the benefit. To quickly see
the benefit, stop and restart your workload. This executes the same SQL
statements again, and the profile should be used automatically this time.
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./stop_dinas.sh
Killing stream with pid=30486
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Killing stream with pid=30485


DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Killing stream with pid=30482


DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Killing stream with pid=30481


DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Killing stream with pid=30480


DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Killing stream with pid=30479


DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$

[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./start_dinas.sh


Started stream with pid=31731

Oracle Database 11g: SQL Tuning Workshop A - 369

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Started stream with pid=31732
Started stream with pid=31733
Started stream with pid=31734
Started stream with pid=31735
Started stream with pid=31740
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Tuning_Advisor
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

PIDLST=`cat /tmp/dina_pids`
#
# Kill all these processes
#
for PID in $PIDLST; do
echo "Killing stream with pid=$PID"
sqlplus / as sysdba @kill_dina.sql $PID >> /tmp/stop_dina.log 2>&1
sqlplus /nolog @/tmp/drop_dina.sql >> /tmp/stop_dina.log 2>&1
kill -9 $PID >> /tmp/stop_dina.log 2>&1
done
-------------------------------------------------------------set
set
set
set
set

head off
timing off
feedback off;
pagesize 0
verify off

spool /tmp/drop_dina.sql;
select 'connect / as sysdba;' from dual;
select 'alter system kill session ''' || sid || ',' || serial# ||
''';'
from v$session
where process=&1;
select 'exit;' from dual;
spool off
exit;

Oracle Database 11g: SQL Tuning Workshop A - 370

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
b) Go back to the Performance tabbed page.
c) On the Performance page, you should now see the benefit of the SQL Profile.
There is no longer any Other waits in the Average Active Sessions graph.
7) How would you make sure that the SQL Profile was implemented?
a) On the Average Active Sessions graph, click the Other wait category in the
caption.
b) On the Active Sessions Waiting: Other page, you should see that your statement
waits for the latch free event. You can see that from the ACTIVITY(%) column.
c) Click the 5mxdwvuf9j3vp SQL Id link in the Top SQL table.
d) On the SQL Details: 5mxdwvuf9j3vp page, click the Plan Control tab in the
Details section.
e) You should see that this statement is associated to a SQL Profile that was
manually implemented. It is part of the DEFAULT category and is ENABLED.
8) Clean up your environment by executing the following commands from your
command-line window:
./stop_dians.sh
./cleanup_dina.sh
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./stop_dinas.sh
Killing stream with pid=31740
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed
Killing stream with pid=31735
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed
Killing stream with pid=31734
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed
Killing stream with pid=31733
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed
Killing stream with pid=31732
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed

Oracle Database 11g: SQL Tuning Workshop A - 371

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Killing stream with pid=31731
DECLARE
*
ERROR at line 1:
ORA-00028: your session has been killed
[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$

[oracle@edrsr33p1-orcl SQL_Tuning_Advisor]$ ./cleanup_dina.sh


Revoke succeeded.
specify password for SH as parameter 1:
specify default tablespace for SH as parameter 2:
specify temporary tablespace for SH as parameter 3:
specify password for SYS as parameter 4:
specify directory path for the data files as parameter 5:
writeable directory path for the log files as parameter 6:
specify version as parameter 7:

Session altered.

User dropped.
old
new

1: CREATE USER sh IDENTIFIED BY &pass


1: CREATE USER sh IDENTIFIED BY sh

User created.
old
new
old
new

1: ALTER USER sh DEFAULT TABLESPACE &tbs


1: ALTER USER sh DEFAULT TABLESPACE example
2: QUOTA UNLIMITED ON &tbs
2: QUOTA UNLIMITED ON example

User altered.
old
new

1: ALTER USER sh TEMPORARY TABLESPACE &ttbs


1: ALTER USER sh TEMPORARY TABLESPACE temp

User altered.

Grant succeeded.

Oracle Database 11g: SQL Tuning Workshop A - 372

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Grant succeeded.
...

Grant succeeded.

Grant succeeded.

PL/SQL procedure successfully completed.


Connected.
Grant succeeded.
old
1: CREATE OR REPLACE DIRECTORY data_file_dir AS '&data_dir'
new
1: CREATE OR REPLACE DIRECTORY data_file_dir AS
'/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/'
Directory created.
old
new

1: CREATE OR REPLACE DIRECTORY log_file_dir AS '&log_dir'


1: CREATE OR REPLACE DIRECTORY log_file_dir AS '/home/oracle/'

Directory created.

Grant succeeded.

Grant succeeded.

Grant succeeded.
Connected.
Session altered.

Session altered.

Table created.

Table created.

...

Table created.

Oracle Database 11g: SQL Tuning Workshop A - 373

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Creating constraints ...
Table altered.

...

Table altered.

specify password for SH as parameter 1:


specify path for data files as parameter 2:
specify path for log files as parameter 3:
specify version as parameter 4:
Looking for indexes that could slow down load ...
no rows selected

loading TIMES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/time_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/time_v
3.dat
/home/oracle/time_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:23
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 1000.


Load completed - logical record count 1826.

loading COUNTRIES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/coun_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/coun_v
3.dat
/home/oracle/coun_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Oracle Database 11g: SQL Tuning Workshop A - 374

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Load completed - logical record count 23.
loading CUSTOMERS using:
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/cust_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/cust1v
3.dat
/home/oracle/cust1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.
Save
Save
Save
Save
Save

data
data
data
data
data

point
point
point
point
point

reached
reached
reached
reached
reached

logical
logical
logical
logical
logical

All rights reserved.


record
record
record
record
record

count
count
count
count
count

10000.
20000.
30000.
40000.
50000.

Load completed - logical record count 55500.

loading PRODUCTS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prod_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prod1v
3.dat
/home/oracle/prod1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Load completed - logical record count 72.

loading PROMOTIONS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prom_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prom1v
3.dat
/home/oracle/prom1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 10.


Save data point reached - logical record count 20.
...
Save data point reached - logical record count 500.

Oracle Database 11g: SQL Tuning Workshop A - 375

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Load completed - logical record count 503.

loading CHANNELS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/chan_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/chan_v
3.dat
/home/oracle/chan_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Load completed - logical record count 5.

loading SALES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/sale_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/sale1v
3.dat
/home/oracle/sale1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 100000.


...
Save data point reached - logical record count 900000.
Load completed - logical record count 916039.

loading COSTS using external table

Table created.

82112 rows created.

loading additonal SALES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dmsal_
v3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dmsal_
v3.dat
/home/oracle/dmsal_v3.log

Oracle Database 11g: SQL Tuning Workshop A - 376

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:38
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 100.


...
Save data point reached - logical record count 2800.
Load completed - logical record count 2804.

loading SUPPLEMENTARY DEMOGRAPHICS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dem_v3
.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dem1v3
.dat
/home/oracle/dem1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:38
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 10.


...
Save data point reached - logical record count 4500.
Load completed - logical record count 4500.

Commit complete.

Enabling constraints ...


Table altered.

...

Table altered.

Creating additional indexes ...


Index created.

...

Index created.

Oracle Database 11g: SQL Tuning Workshop A - 377

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Create dimensions ...
Dimension created.

Commit complete.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected
Creating MVs as tables ...

View created.

Table created.

Oracle Database 11g: SQL Tuning Workshop A - 378

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Table created.

Index created.

Index created.

Index created.

Index created.
Creating materialized views ...

Materialized view created.

Materialized view created.

Creating comments ...


Comment created.

...

Comment created.

Creating OLAP metadata ...


<<<<< CREATE CWMLite Metadata for the Sales History Schema >>>>>
<<<<< CREATE CATALOG sh_cat for Sales History >>>>>
Catalog Dropped
CWM Collect Garbage
<<<<< CREATE the Sales CUBE >>>>>
Sales amount, Sales quantity
<TIMES CHANNELS PRODUCTS CUSTOMERS PROMOTIONS >
Drop SALES_CUBE prior to recreation
Cube Dropped
Add dimensions to SALES_CUBE and map the foreign keys
Create measures for SALES_CUBE and map to columns in the fact table
Set default aggregation method to SUM for all measures over TIME
Add SALES_CUBE to the catalog
SALES_CUBE successfully added to sh_cat

Oracle Database 11g: SQL Tuning Workshop A - 379

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
<<<<< CREATE the Cost CUBE >>>>>
Unit Cost, Unit Price < TIMES PRODUCTS CHANNELS PROMOTIONS >
Drop COST_CUBE prior to recreation
Cube Dropped
Add dimensions to COST_CUBE and map the foreign keys
Create measures for COST_CUBE and map to columns in the fact table
Set default aggregation method to SUM for all measures over TIME
Add COST_CUBE to the catalog
COST_CUBE successfully added to sh_cat
<<<<< TIME DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
- Period Number of Days created
- Period End Date created
Classify entity descriptor use
- Time dimension
- Long description
- Day name
- Calendar month description
- Calendar quarter description
- Fiscal month description
- Fiscal quarter description
- Short Description
- Day name
- Calendar month description
- Calendar quarter description
- Fiscal month description
- Fiscal quarter description
- Time Span
- Days in calendar month
- Days in calendar quarter
- Days in calendar year
- Days in fiscal month
- Days in fiscal quarter
- Days in fiscal year
- End Date
- End of calendar month
- End of calendar quarter
- End of calendar year
- End of fiscal month
- End of fiscal quarter
- End of fiscal year
-

Oracle Database 11g: SQL Tuning Workshop A - 380

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
<<<<< CUSTOMERS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
- Other Customer Information created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< PRODUCTS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< PROMOTIONS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created

Oracle Database 11g: SQL Tuning Workshop A - 381

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
Classify entity descriptor use
- Long Description
- Short Description
<<<<< CHANNELS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< FINAL PROCESSING >>>>>
- Changes have been committed
PL/SQL procedure successfully completed.

Commit complete.

gathering statistics ...


PL/SQL procedure successfully completed.

PL/SQL procedure successfully completed.


SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$

-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Tuning_Advisor
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin

Oracle Database 11g: SQL Tuning Workshop A - 382

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
#
# Cleanup ADDM snapshot settings
#
sqlplus -s /NOLOG <<EOF >> /tmp/cleanup_dina.log 2>&1
connect / as sysdba
rem -- change INTERVAL setting to 30 minute
execute dbms_workload_repository.modify_snapshot_settings(interval
=> 60);
rem -- change ADDM sensitiveness back to normal
exec
dbms_advisor.set_default_task_parameter('ADDM','DB_ACTIVITY_MIN',300
);
connect sh/sh
drop index sales_time_idx;
create bitmap index sales_time_bix
on sales(time_id)
tablespace example
local nologging compute statistics;

EOF
#
# Cleanup sql profile
#
sqlplus -s /NOLOG <<EOF > /tmp/cleanup_dina.log 2>&1
connect / as sysdba
set
set
set
set

head off
timing off
feedback off;
pagesize 0

spool /tmp/drop_dyn.sql;
select q'#connect / as sysdba;#' from dual;
select q'#execute dbms_sqltune.drop_sql_profile('#' || name || q'#')
;#'
from dba_sql_profiles ;
select q'#execute dbms_advisor.delete_task('#' || task_name || q'#')
;#'
from user_advisor_tasks
where CREATED > SYSDATE-(1/24);
select q'#connect system/oracle;#' from dual;

Oracle Database 11g: SQL Tuning Workshop A - 383

Practice 11-1: Proactively Tuning High-Load SQL Statements


(continued)
select q'#execute dbms_advisor.delete_task('#' || task_name || q'#')
;#'
from user_advisor_tasks
where CREATED > SYSDATE-(1/24);
spool off
@/tmp/drop_dyn.sql
EOF

cp /home/oracle/solutions/SQL_Access_Advisor/sh/*
$ORACLE_HOME/demo/schema/sales_history
cd /home/oracle/solutions/SQL_Access_Advisor/sh
sqlplus -s /NOLOG <<EOF
set echo on
connect / as sysdba
revoke dba from sh;
@sh_main sh example temp oracle
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/
/home/oracle/ v3
EOF

Oracle Database 11g: SQL Tuning Workshop A - 384

Practice 11-2: Using SQL Access Advisor


The following scenario illustrates the types of recommendations that can be made by
SQL Access Advisor. The scenario also uses the SQL Performance Analyzer to prove
that recommendations made by SQL Access Advisor are good.
1) From a terminal session connected as the oracle user, execute the
sqlaccessadv_setup.sh script. This script generates the necessary data that
you use throughout this lab. In particular, it generates the SQL Tuning Set that is used
to represent the workload you want to analyze.
./sqlaccessadv_setup.sh
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$ ./sqlaccessadv_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 21:14:49
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> Connected.
SQL> SQL>
Grant succeeded.
SQL> SQL>
User altered.
SQL> SQL> Connected.
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL> SQL> SQL> SQL> DROP TABLE temp_table purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
Table created.
SQL> SQL> SQL> SQL> SQL> SQL> SQL>
System altered.
SQL> BEGIN dbms_sqltune.drop_sqlset('SQLSET_MY_SQLACCESS_WORKLOAD');
END;
*
ERROR at line 1:

Oracle Database 11g: SQL Tuning Workshop A - 385

Practice 11-2: Using SQL Access Advisor (continued)


ORA-13754: "SQL Tuning Set" "SQLSET_MY_SQLACCESS_WORKLOAD" does not
exist for user "SH".
ORA-06512: at "SYS.DBMS_SQLTUNE_INTERNAL", line 8406
ORA-06512: at "SYS.DBMS_SQLTUNE", line 2949
ORA-06512: at line 1

SQL> SQL> drop table tempjfv purge


*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL> SQL>
Table created.
SQL> SQL> SQL>
2
3
4
5
6
PL/SQL procedure successfully completed.

SQL> SQL> drop table customersjfv purge


*
ERROR at line 1:
ORA-00942: table or view does not exist

SQL>
Table created.
SQL> SQL> SQL>
2
3
4
13
14
15
16
17
18
27
28
29
30
31
32
41
42
43
44
45
46
PL/SQL procedure successfully

5
6
7
19
20
21
33
34
35
47
48
49
completed.

8
22
36

9
23
37

10
24
38

SQL> SQL> SQL>


COUNT(*)
---------5
1 row selected.
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL>
LAST_ANAL
--------18-MAR-08

Oracle Database 11g: SQL Tuning Workshop A - 386

11
25
39

12
26
40

Practice 11-2: Using SQL Access Advisor (continued)


18-MAR-08
18-MAR-08
18-MAR-08
22-AUG-07
18-MAR-08
18-MAR-08
18-MAR-08
18-MAR-08
18-MAR-08
18-MAR-08
18-MAR-08
18-MAR-08
14 rows selected.
SQL> SQL> SQL> SQL> Disconnected from Oracle Database 11g Enterprise
Edition Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Access_Advisor
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
sqlplus / as sysdba <<FIN!
SET
SET
SET
SET
SET
SET
SET
SET

ECHO ON
FEEDBACK 1
NUMWIDTH 10
LINESIZE 8000
TRIMSPOOL ON
TAB OFF
PAGESIZE 100
LONG 1000

CONNECT / AS SYSDBA
grant dba to sh;
alter user sh identified by sh account unlock;
connect sh/sh
set serveroutput on size 32768;
set echo on;

Oracle Database 11g: SQL Tuning Workshop A - 387

Practice 11-2: Using SQL Access Advisor (continued)


variable norecs number;
Rem Clean up

declare
name varchar2(30);
cursor name_cur1 is
select task_name from user_advisor_templates
where task_name like '%SQLACCESS%';
begin
--------------------------------------------------------------------------- Get rid of templates, tasks and workloads.
-------------------------------------------------------------------------open name_cur1;
loop
fetch name_cur1 into name;
exit when name_cur1%NOTFOUND;

dbms_advisor.update_task_attributes(name,null,null,'FALSE','FALSE');
dbms_advisor.delete_task(name);
end loop;
close name_cur1;
end;
/

Rem make a temp table

DROP TABLE temp_table purge;


CREATE TABLE temp_table AS SELECT * FROM SYS.WRI\$_ADV_SQLW_STMTS
WHERE NULL IS NOT NULL;

Rem create a large number of pseudo-random (repeatable) queries in


the temporary table

alter system flush shared_pool;


execute dbms_sqltune.drop_sqlset('SQLSET_MY_SQLACCESS_WORKLOAD');
drop table tempjfv purge;
create table tempjfv (c number, d varchar2(1000));

Oracle Database 11g: SQL Tuning Workshop A - 388

Practice 11-2: Using SQL Access Advisor (continued)


begin
for i in 1..20000 loop
insert into tempjfv values(i,'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
end loop;
commit;
end;
/
drop table customersjfv purge;
create table customersjfv as select * from customers;

DECLARE
sql_stmt
varchar2(2000);
sqlsetname VARCHAR2(30);
sqlsetcur
dbms_sqltune.sqlset_cursor;
refid
NUMBER;
k NUMBER := 0;
num_queries NUMBER := 500;
BEGIN
sql_stmt := 'SELECT /* QueryJFV 2 */ ch.channel_class, c.cust_city,
t.calendar_quarter_desc, SUM(s.amount_sold) sales_amount FROM
sh.sales s, sh.times t, sh.customers c, sh.channels ch WHERE
s.time_id = t.time_id AND s.cust_id = c.cust_id AND s.channel_id =
ch.channel_id AND c.cust_state_province = ''CA'' AND ch.channel_desc
in (''Internet'',''Catalog'') AND t.calendar_quarter_desc IN
(''1999-01'',''1999-02'') GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc';
insert into temp_table values(1,1,NULL,0,'SH','Access
Advisor','Workload',0,0,0,0,1,100,2,to_date('02-FEB2007'),3,0,sql_stmt,1);
sql_stmt := 'SELECT /* QueryJFV 3 */ ch.channel_class, c.cust_city,
t.calendar_quarter_desc, SUM(s.amount_sold) sales_amount FROM
sh.sales s, sh.times t, sh.customers c, sh.channels ch WHERE
s.time_id = t.time_id AND s.cust_id = c.cust_id AND s.channel_id =
ch.channel_id AND c.cust_state_province = ''CA'' AND ch.channel_desc
in (''Internet'',''Catalog'') AND t.calendar_quarter_desc IN
(''1999-03'',''1999-04'') GROUP BY ch.channel_class, c.cust_city,
t.calendar_quarter_desc';
insert into temp_table values(1,1,NULL,0,'SH','Access
Advisor','Workload',0,0,0,0,1,100,2,to_date('02-FEB2007'),3,0,sql_stmt,1);

Oracle Database 11g: SQL Tuning Workshop A - 389

Practice 11-2: Using SQL Access Advisor (continued)


sql_stmt := 'SELECT /* QueryJFV 4 */ c.country_id, c.cust_city,
c.cust_last_name FROM sh.customers c WHERE c.country_id in (52790,
52798) ORDER BY c.country_id, c.cust_city, c.cust_last_name';
insert into temp_table values(1,1,NULL,0,'SH','Access
Advisor','Workload',0,0,0,0,1,100,2,to_date('02-FEB2007'),3,0,sql_stmt,1);

sql_stmt := 'select /* func_indx */ count(*) from tempjfv where


abs(c)=5';
insert into temp_table values(1,1,NULL,0,'SH','Access
Advisor','Workload',0,0,0,0,1,100,2,to_date('02-FEB2007'),3,0,sql_stmt,1);
sql_stmt := 'SELECT /* QueryJFV 5 */ * FROM sh.customersjfv WHERE
cust_state_province = ''CA''';
insert into temp_table values(1,1,NULL,0,'SH','Access
Advisor','Workload',0,0,0,0,1,100,2,to_date('02-FEB2007'),3,0,sql_stmt,1);

sqlsetname := 'SQLSET_MY_SQLACCESS_WORKLOAD';
dbms_sqltune.create_sqlset(sqlsetname, 'Generated STS');
OPEN sqlsetcur FOR
SELECT
SQLSET_ROW(null,null, sql_text, null, null, username,
module,
action, elapsed_time, cpu_time, buffer_gets,
disk_reads,
0,rows_processed, 0, executions, 0,
optimizer_cost, null,
priority, command_type,
to_char(last_execution_date,'yyyy-mmdd/hh24:mi:ss'),
0,0,NULL,0,NULL,NULL
)
FROM temp_table;
dbms_sqltune.load_sqlset(sqlsetname, sqlsetcur);
END;
/

SELECT COUNT(*) FROM


TABLE(DBMS_SQLTUNE.SELECT_SQLSET('SQLSET_MY_SQLACCESS_WORKLOAD'));

Rem Cleanup anything left behind

Oracle Database 11g: SQL Tuning Workshop A - 390

Practice 11-2: Using SQL Access Advisor (continued)


execute dbms_advisor.delete_task('%');
execute dbms_advisor.delete_sqlwkld('%');
EXECUTE DBMS_STATS.UNLOCK_SCHEMA_STATS('SH');
execute dbms_stats.gather_schema_stats(ownname => 'SH',
estimate_percent=> DBMS_STATS.AUTO_SAMPLE_SIZE, method_opt => 'FOR
ALL COLUMNS SIZE AUTO', degree => 4 );
select distinct last_analyzed from dba_tab_statistics where
owner='SH';
REM EXECUTE DBMS_STATS.LOCK_SCHEMA_STATS('SH');
exit;
FIN!

2) Using Enterprise Manager, create a SQL Access Advisor tuning task based on the
captured workload held in the SH.SQLSET_MY_ACCESS_WORKLOAD SQL
tuning set using the SQLACCESS_WAREHOUSE template.
a) Connect to Enterprise Manager Database Control as the sh user (password: sh).
On the Home page, click the Advisor Central link in the Related Links section.
b) On the Advisor Central page, click the SQL Advisors link. Then on the SQL
Advisors page, click the SQL Access Advisor link.
c) On the Initial Options page, select Inherit Options from a previously saved Task
or Template, and then select the SQLACCESS_WAREHOUSE template. After
this is done, click Continue.
d) On the Workload Source page, select Use an existing SQL Tuning Set and enter
SH.SQLSET_MY_SQLACCESS_WORKLOAD in the SQL Tuning Set field.
(This SQL Tuning Set was generated earlier. It represents a warehouse workload
that you want to analyze.) Click Next.
e) On the Recommendation Options page, ensure that all possible access structures
are selected, and that Comprehensive is selected. After this is done, click Next.
f) On the Schedule page, enter MY_SQLACCESS_TASK in the Task Name field.
Select the first Time Zone from the provided list (Click the torch icon.). After this
is done, click Next.
g) On the Review page, click Submit.
h) Back to the Advisor Central page, click Refresh until the Status of your task is
COMPLETED.
i) After this is done, click the MY_SQLACCESS_TASK link in the Results table.
3) After this is done, investigate the proposed recommendations:
a) Back to the Advisor Central page, click the MY_SQLACCESS_TASK link in the
Results table. The task should have COMPLETED as the status.

Oracle Database 11g: SQL Tuning Workshop A - 391

Practice 11-2: Using SQL Access Advisor (continued)


b) This takes you to the Results page. From this page, you can see the potential
benefit of implementing the SQL Access Advisor recommendations on the
workload. There should be a huge difference between the original and the new
costs. Click the Recommendation subtab.
c) On the Recommendations subtab, you can see the high-level overview of the
recommendations. Basically, all possible types of recommendations were
generated for this workload (Indexes, Materialized Views, Materialized View
Logs, Partitions, and Others).
d) Ensure that all recommendations are selected, and click the Recommendation
Details button. This takes you to the Details page, where you can see more details
about each of the recommendations, as well as the corresponding SQL statements
from the workload that are affected by these recommendations. You should see
the following recommendations:
- Partition CUSTOMERS table.
- Create four materialized view log.
- Create a materialized view.
- Create one bitmap index.
- Create a function-based index.
- Create a B*-tree index on multiple columns.
e) Click OK.
4) Try to implement the generated recommendations. What happens, and why?
a) Back to the Recommendations subtab, click the Schedule Implementation button.
b) On the Schedule Implementation page, a warning is displayed indicating that the
wizard will not try to implement its recommendations because some of them are
very important changes that should be looked at closely by the administrator.
c) Click the Show SQL button to look at the script you could use to implement all
recommendations. In fact, you already created this script and you will use it later
in this lab. After you review the script, click Done.
d) Back to the Schedule Implementation page, click Cancel.
e) Click the Advisor Central locator link at the top of the Results for Task page.
f) On the Advisor Central page, select the SQL Access Advisor
MY_SQLACCESS_TASK task and click Delete.
g) On the Information page, click Yes.
5) Use Enterprise Manager to verify the performance improvement if you implement the
recommendations mentioned.
a) Click the Database tab at the top-right corner and then the Software and
Support tab. On the Software and Support tabbed page, click the SQL
Performance Analyzer link. You want to prove that implementing the
recommendations is beneficial.
b) On the SQL Performance Analyzer page, click the Guided Workflow link.

Oracle Database 11g: SQL Tuning Workshop A - 392

Practice 11-2: Using SQL Access Advisor (continued)


c) On the Guided Workflow page, click the Execute icon on the line corresponding
to step 1.
d) On the Create SQL Performance Analyzer Task page, enter MY_SPA_TASK in
the SQL Performance Analyzer Task Name field. Then, enter
SH.SQLSET_MY_SQLACCESS_WORKLOAD in the SQL Tuning Set Name field.
After this is done, click Create.
e) Back to the Guided Workflow page, click the Execute icon for step 2.
f) On the Create Replay Trial page, enter MY_SQL_REPLAY_BEFORE in the
Replay Trial Name field, and ensure that you select the Trial environment
established check box. Then, click Submit.
g) Wait on the Guided Workflow page until step 2 is completed.
h) From your terminal session, connect as the sh user (password: sh) in the
SQL*Plus session, and execute the implement.sql script. This script is a
precreated script corresponding to the recommendations previously generated by
your SQL Access Advisor session.
cd /home/oracle/solutions/SQL_Access_Advisor
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$ ls
implement.sql revert.sh sqlaccessadv_setup.sh
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$ sqlplus sh/sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 21:57:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
2
3
4
5
6
7
8
9
10
11
12
13

@implement
Rem
Rem Creating new partitioned table
Rem
CREATE TABLE "SH"."CUSTOMERS1"
(
"CUST_ID" NUMBER,
"CUST_FIRST_NAME" VARCHAR2(20),
"CUST_LAST_NAME" VARCHAR2(40),
"CUST_GENDER" CHAR(1),
"CUST_YEAR_OF_BIRTH" NUMBER(4,0),
"CUST_MARITAL_STATUS" VARCHAR2(20),
"CUST_STREET_ADDRESS" VARCHAR2(40),
"CUST_POSTAL_CODE" VARCHAR2(10),
"CUST_CITY" VARCHAR2(30),
"CUST_CITY_ID" NUMBER,
"CUST_STATE_PROVINCE" VARCHAR2(40),
"CUST_STATE_PROVINCE_ID" NUMBER,

Oracle Database 11g: SQL Tuning Workshop A - 393

Practice 11-2: Using SQL Access Advisor (continued)


14
"COUNTRY_ID" NUMBER,
15
"CUST_MAIN_PHONE_NUMBER" VARCHAR2(25),
16
"CUST_INCOME_LEVEL" VARCHAR2(30),
17
"CUST_CREDIT_LIMIT" NUMBER,
18
"CUST_EMAIL" VARCHAR2(30),
19
"CUST_TOTAL" VARCHAR2(14),
20
"CUST_TOTAL_ID" NUMBER,
21
"CUST_SRC_ID" NUMBER,
22
"CUST_EFF_FROM" DATE,
23
"CUST_EFF_TO" DATE,
24
"CUST_VALID" VARCHAR2(1)
25 ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS
NOLOGGING
26 TABLESPACE "EXAMPLE"
27 PARTITION BY RANGE ("CUST_ID") INTERVAL( 3000) ( PARTITION
VALUES LESS THAN (3000)
28 );
Table created.
SQL>
SQL>
SQL>
SQL>
SQL>

Rem
Rem Copying comments to new partitioned table
Rem
COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_ID" IS 'primary key';

Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_FIRST_NAME" IS 'first
name of the customer';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_LAST_NAME" IS 'last
name of the customer';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_GENDER" IS 'gender;
low cardinality attribute';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_YEAR_OF_BIRTH" IS
'customer year of birth';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_MARITAL_STATUS" IS
'customer marital status; low cardinality attribute';
Comment created.

Oracle Database 11g: SQL Tuning Workshop A - 394

Practice 11-2: Using SQL Access Advisor (continued)


SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_STREET_ADDRESS" IS
'customer street address';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_POSTAL_CODE" IS
'postal code of the customer';
Comment created.
SQL>
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_CITY" IS 'city where
the customer lives';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_STATE_PROVINCE" IS
'customer geography: state or province';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."COUNTRY_ID" IS 'foreign
key to the countries table (snowflake)';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_MAIN_PHONE_NUMBER" IS
'customer main phone number';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_INCOME_LEVEL" IS
'customer income level';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_CREDIT_LIMIT" IS
'customer credit limit';
Comment created.
SQL>
SQL> COMMENT ON COLUMN "SH"."CUSTOMERS1"."CUST_EMAIL" IS 'customer
email id';
Comment created.
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 395

Practice 11-2: Using SQL Access Advisor (continued)


SQL> COMMENT ON TABLE "SH"."CUSTOMERS1" IS 'dimension table';
Comment created.
SQL>
SQL> Rem
SQL> Rem Copying constraints to new partitioned table
SQL> Rem
SQL> ALTER TABLE "SH"."CUSTOMERS1" ADD CONSTRAINT "CUSTOMERS_PK1"
PRIMARY KEY ("CUST_ID")
2 USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 NOLOGGING
COMPUTE STATISTICS
3 TABLESPACE "EXAMPLE" ENABLE NOVALIDATE;
Table altered.
SQL>
SQL> Rem
SQL> Rem Copying referential constraints to new partitioned table
SQL> Rem
SQL> ALTER TABLE "SH"."CUSTOMERS1" ADD CONSTRAINT
"CUSTOMERS_COUNTRY_FK1" FOREIGN KEY ("COUNTRY_ID")
2
REFERENCES "SH"."COUNTRIES" ("COUNTRY_ID") ENABLE
NOVALIDATE;
Table altered.
SQL>
SQL> Rem
SQL> Rem Copying indexes to new partitioned table
SQL> Rem
SQL> CREATE BITMAP INDEX "SH"."CUSTOMERS_GENDER_BIX1" ON
"SH"."CUSTOMERS1" ("CUST_GENDER")
2 PCTFREE 10 INITRANS 2 MAXTRANS 255 NOLOGGING COMPUTE STATISTICS
3 TABLESPACE "EXAMPLE" LOCAL;
Index created.
SQL>
SQL>
SQL>
SQL>
SQL>

Rem
Rem Copying object grants to new partitioned table
Rem
GRANT SELECT ON "SH"."CUSTOMERS1" TO "BI";

Grant succeeded.
SQL>
SQL> Rem
SQL> Rem Populating new partitioned table with data from original
table
SQL> Rem
SQL> INSERT /*+ APPEND */ INTO "SH"."CUSTOMERS1"
2 SELECT * FROM "SH"."CUSTOMERS";
55500 rows created.
SQL> COMMIT;

Oracle Database 11g: SQL Tuning Workshop A - 396

Practice 11-2: Using SQL Access Advisor (continued)


Commit complete.
SQL>
SQL> begin
2 dbms_stats.gather_table_stats('"SH"', '"CUSTOMERS1"', NULL,
dbms_stats.auto_sample_size);
3 end;
4 /
PL/SQL procedure successfully completed.
SQL>
SQL> Rem
SQL> Rem Renaming tables to give new partitioned table the original
table name
SQL> Rem
SQL> ALTER TABLE "SH"."CUSTOMERS" RENAME TO "CUSTOMERS11";
Table altered.
SQL> ALTER TABLE "SH"."CUSTOMERS1" RENAME TO "CUSTOMERS";
Table altered.
SQL>
SQL>
SQL>
SQL>
SQL>

Rem
Rem Revalidating dimensions for use with new partitioned table
Rem
ALTER DIMENSION "SH"."CUSTOMERS_DIM" COMPILE;

Dimension altered.
SQL>
SQL>
SQL> CREATE MATERIALIZED VIEW LOG ON
2 "SH"."CUSTOMERS"
3 WITH ROWID,
SEQUENCE("CUST_ID","CUST_CITY","CUST_STATE_PROVINCE")
4 INCLUDING NEW VALUES;
Materialized view log created.
SQL>
SQL> CREATE MATERIALIZED VIEW LOG ON
2 "SH"."CHANNELS"
3 WITH ROWID,
SEQUENCE("CHANNEL_ID","CHANNEL_DESC","CHANNEL_CLASS")
4 INCLUDING NEW VALUES;
Materialized view log created.
SQL>
SQL>
2
3
4

CREATE MATERIALIZED VIEW LOG ON


"SH"."TIMES"
WITH ROWID, SEQUENCE("TIME_ID","CALENDAR_QUARTER_DESC")
INCLUDING NEW VALUES;

Oracle Database 11g: SQL Tuning Workshop A - 397

Practice 11-2: Using SQL Access Advisor (continued)


Materialized view log created.
SQL>
SQL> CREATE MATERIALIZED VIEW LOG ON
2 "SH"."SALES"
3 WITH ROWID,
SEQUENCE("CUST_ID","TIME_ID","CHANNEL_ID","AMOUNT_SOLD")
4 INCLUDING NEW VALUES;
Materialized view log created.
SQL>
SQL> CREATE MATERIALIZED VIEW "SH"."MV_01DF0000"
2 REFRESH FAST WITH ROWID
3 ENABLE QUERY REWRITE
4 AS SELECT SH.CUSTOMERS.CUST_STATE_PROVINCE C1,
SH.CUSTOMERS.CUST_CITY C2, SH.CHANNELS.CHANNEL_CLASS
5 C3, SH.CHANNELS.CHANNEL_DESC C4, SH.TIMES.CALENDAR_QUARTER_DESC
C5, SUM("SH"."SALES"."AMOUNT_SOLD")
6 M1, COUNT("SH"."SALES"."AMOUNT_SOLD") M2, COUNT(*) M3 FROM
SH.CUSTOMERS,
7 SH.CHANNELS, SH.TIMES, SH.SALES WHERE SH.SALES.CHANNEL_ID =
SH.CHANNELS.CHANNEL_ID
8 AND SH.SALES.TIME_ID = SH.TIMES.TIME_ID AND SH.SALES.CUST_ID =
SH.CUSTOMERS.CUST_ID
9 AND (SH.TIMES.CALENDAR_QUARTER_DESC IN ('1999-04', '1999-03',
'1999-02'
10 , '1999-01')) AND (SH.CHANNELS.CHANNEL_DESC IN ('Internet',
'Catalog'
11 )) AND (SH.CUSTOMERS.CUST_STATE_PROVINCE = 'CA') GROUP BY
SH.CUSTOMERS.CUST_STATE_PROVINCE,
12 SH.CUSTOMERS.CUST_CITY, SH.CHANNELS.CHANNEL_CLASS,
SH.CHANNELS.CHANNEL_DESC,
13 SH.TIMES.CALENDAR_QUARTER_DESC;
Materialized view created.
SQL>
SQL> begin
2
dbms_stats.gather_table_stats('"SH"','"MV_01DF0000"',NULL,dbms_stats
.auto_sample_size);
3 end;
4 /
PL/SQL procedure successfully completed.
SQL>
SQL>
2
3
4

CREATE BITMAP INDEX "SH"."CUSTOMERSJFV_IDX_01DF0000"


ON "SH"."CUSTOMERSJFV"
("CUST_STATE_PROVINCE")
COMPUTE STATISTICS;

Index created.
SQL>

Oracle Database 11g: SQL Tuning Workshop A - 398

Practice 11-2: Using SQL Access Advisor (continued)


SQL>
2
3
4

CREATE INDEX "SH"."TEMPJFV_IDX_01DF0001"


ON "SH"."TEMPJFV"
(ABS("C"))
COMPUTE STATISTICS;

Index created.
SQL>
SQL>
2
3
4

CREATE INDEX "SH"."CUSTOMERS_IDX_01DF0002"


ON "SH"."CUSTOMERS"
("COUNTRY_ID","CUST_CITY","CUST_LAST_NAME")
COMPUTE STATISTICS;

Index created.
SQL>
SQL> exit
Disconnected from Oracle Database 11g Enterprise Edition Release
11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$

i) Back to your Guided Workflow page, click the Execute icon corresponding to
step 3.
j) On the Create Replay Trial page, enter MY_SQL_REPLAY_AFTER in the Replay
Trial Name field. Ensure that you select the Trial environment established check
box, and click Submit.
k) Wait until step 3 is completed.
l) Back to your Guided Workflow Enterprise Manager page, click the Execute icon
corresponding to step 4.
m) On the Run Replay Trial Comparison page, ensure that you create a comparison
between MY_SQL_REPLAY_BEFORE and MY_SQL_REPLAY_AFTER. Click
Submit.
n) Wait until step 4 is completed.
o) Back to your Guided Workflow Enterprise Manager page, click the Execute icon
corresponding to step 5.
p) On the SQL Performance Analyzer Task Result page, you can clearly see that the
second trial is much faster than the original one. You should see that all five SQL
statements are improved in the second trial due a changed execution plan.
q) To get more details, analyze the differences in execution plan for all five
statements. You can do so directly from the SQL Performance Analyzer Task
Result page by clicking each SQL ID in the Top 10 table. Each time you click a
SQL ID, you can see in the SQL Details section all statistics differences between
the two trials as well as the differences in execution plans.

Oracle Database 11g: SQL Tuning Workshop A - 399

Practice 11-2: Using SQL Access Advisor (continued)


r) After this is done, go back to the SQL Performance Analyzer page (Home >
Software and Support > SQL Performance Analyzer), and delete
MY_SPA_TASK by selecting it and clicking Delete. On the Confirmation page,
click Delete.
s) Log out from Enterprise Manager.
6) From a terminal session, execute the revert.sh script to return to the situation you
were in before you started the lab.
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$ ./revert.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:02
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> Connected.
SQL> SQL>
Grant succeeded.
SQL> SQL>
User altered.
SQL> SQL> Connected.
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL> SQL> SQL>
Table dropped.
SQL> SQL>
System altered.
SQL> SQL>
Table dropped.
SQL> SQL>
Table dropped.
SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL>
PL/SQL procedure successfully completed.
SQL> SQL>
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 400

Practice 11-2: Using SQL Access Advisor (continued)


SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL>
Materialized view log dropped.
SQL> SQL>
Materialized view log dropped.
SQL> SQL>
Materialized view log dropped.
SQL> SQL>
Materialized view log dropped.
SQL> SQL>
Materialized view dropped.
SQL> SQL>
Index dropped.
SQL> SQL>
Table dropped.
SQL> SQL>
Table dropped.
SQL> SQL> Connected.
SQL> SQL>
Revoke succeeded.
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
SQL>
...
SQL>
SQL>
SQL>

SQL> SQL> Rem


Rem $Header: sh_main.sql 06-mar-2008.15:00:45 cbauwens Exp $
Rem
Rem sh_main.sql
Rem
Rem Copyright (c) 2001, 2008, Oracle. All rights reserved.
Rem
Rem
NAME
Rem
sh_main.sql - Main schema creation and load script
Rem
SET ECHO OFF

specify password for SH as parameter 1:


specify default tablespace for SH as parameter 2:
specify temporary tablespace for SH as parameter 3:
specify password for SYS as parameter 4:
specify directory path for the data files as parameter 5:
writeable directory path for the log files as parameter 6:

Oracle Database 11g: SQL Tuning Workshop A - 401

Practice 11-2: Using SQL Access Advisor (continued)


specify version as parameter 7:

Session altered.

User dropped.
old
new

1: CREATE USER sh IDENTIFIED BY &pass


1: CREATE USER sh IDENTIFIED BY sh

User created.
old
new
old
new

1: ALTER USER sh DEFAULT TABLESPACE &tbs


1: ALTER USER sh DEFAULT TABLESPACE example
2: QUOTA UNLIMITED ON &tbs
2: QUOTA UNLIMITED ON example

User altered.
old
new

1: ALTER USER sh TEMPORARY TABLESPACE &ttbs


1: ALTER USER sh TEMPORARY TABLESPACE temp

User altered.

Grant succeeded.

Grant succeeded.

...

Grant succeeded.

Grant succeeded.

PL/SQL procedure successfully completed.


Connected.
Grant succeeded.
old
1: CREATE OR REPLACE DIRECTORY data_file_dir AS '&data_dir'
new
1: CREATE OR REPLACE DIRECTORY data_file_dir AS
'/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/'
Directory created.
old
new

1: CREATE OR REPLACE DIRECTORY log_file_dir AS '&log_dir'


1: CREATE OR REPLACE DIRECTORY log_file_dir AS '/home/oracle/'

Oracle Database 11g: SQL Tuning Workshop A - 402

Practice 11-2: Using SQL Access Advisor (continued)


Directory created.
Grant succeeded.

Grant succeeded.

Grant succeeded.
Connected.
Session altered.

Session altered.

Table created.

Table created.

...

Table created.

Creating constraints ...


Table altered.

...

Table altered.

specify password for SH as parameter 1:


specify path for data files as parameter 2:
specify path for log files as parameter 3:
specify version as parameter 4:
Looking for indexes that could slow down load ...
no rows selected

loading TIMES using:

Oracle Database 11g: SQL Tuning Workshop A - 403

Practice 11-2: Using SQL Access Advisor (continued)


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/time_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/time_v
3.dat
/home/oracle/time_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:23
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 1000.


Load completed - logical record count 1826.

loading COUNTRIES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/coun_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/coun_v
3.dat
/home/oracle/coun_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Load completed - logical record count 23.

loading CUSTOMERS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/cust_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/cust1v
3.dat
/home/oracle/cust1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.
Save
Save
Save
Save
Save

data
data
data
data
data

point
point
point
point
point

reached
reached
reached
reached
reached

logical
logical
logical
logical
logical

All rights reserved.


record
record
record
record
record

count
count
count
count
count

10000.
20000.
30000.
40000.
50000.

Load completed - logical record count 55500.

loading PRODUCTS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prod_v
3.ctl

Oracle Database 11g: SQL Tuning Workshop A - 404

Practice 11-2: Using SQL Access Advisor (continued)


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prod1v
3.dat
/home/oracle/prod1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:24
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Load completed - logical record count 72.

loading PROMOTIONS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prom_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/prom1v
3.dat
/home/oracle/prom1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 10.


Save data point reached - logical record count 20.
...
Save data point reached - logical record count 500.
Load completed - logical record count 503.

loading CHANNELS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/chan_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/chan_v
3.dat
/home/oracle/chan_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Load completed - logical record count 5.

loading SALES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/sale_v
3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/sale1v
3.dat
/home/oracle/sale1v3.log

Oracle Database 11g: SQL Tuning Workshop A - 405

Practice 11-2: Using SQL Access Advisor (continued)


SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:25
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 100000.


...
Save data point reached - logical record count 900000.
Load completed - logical record count 916039.

loading COSTS using external table

Table created.

82112 rows created.

loading additonal SALES using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dmsal_
v3.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dmsal_
v3.dat
/home/oracle/dmsal_v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:38
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 100.


...
Save data point reached - logical record count 2800.
Load completed - logical record count 2804.

loading SUPPLEMENTARY DEMOGRAPHICS using:


/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dem_v3
.ctl
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/dem1v3
.dat
/home/oracle/dem1v3.log
SQL*Loader: Release 11.1.0.6.0 - Production on Thu Mar 20 15:22:38
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Save data point reached - logical record count 10.


...
Save data point reached - logical record count 4500.
Load completed - logical record count 4500.

Oracle Database 11g: SQL Tuning Workshop A - 406

Practice 11-2: Using SQL Access Advisor (continued)


Commit complete.

Enabling constraints ...


Table altered.

...

Table altered.

Creating additional indexes ...


Index created.

...

Index created.

Create dimensions ...


Dimension created.

Commit complete.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Oracle Database 11g: SQL Tuning Workshop A - 407

Practice 11-2: Using SQL Access Advisor (continued)


Dimension created.

PL/SQL procedure successfully completed.

no rows selected

Dimension created.

PL/SQL procedure successfully completed.

no rows selected
Creating MVs as tables ...

View created.

Table created.

Table created.

Index created.

Index created.

Index created.

Index created.
Creating materialized views ...

Materialized view created.

Materialized view created.

Creating comments ...


Comment created.

...

Oracle Database 11g: SQL Tuning Workshop A - 408

Practice 11-2: Using SQL Access Advisor (continued)


Comment created.

Creating OLAP metadata ...


<<<<< CREATE CWMLite Metadata for the Sales History Schema >>>>>
<<<<< CREATE CATALOG sh_cat for Sales History >>>>>
Catalog Dropped
CWM Collect Garbage
<<<<< CREATE the Sales CUBE >>>>>
Sales amount, Sales quantity
<TIMES CHANNELS PRODUCTS CUSTOMERS PROMOTIONS >
Drop SALES_CUBE prior to recreation
Cube Dropped
Add dimensions to SALES_CUBE and map the foreign keys
Create measures for SALES_CUBE and map to columns in the fact table
Set default aggregation method to SUM for all measures over TIME
Add SALES_CUBE to the catalog
SALES_CUBE successfully added to sh_cat
<<<<< CREATE the Cost CUBE >>>>>
Unit Cost, Unit Price < TIMES PRODUCTS CHANNELS PROMOTIONS >
Drop COST_CUBE prior to recreation
Cube Dropped
Add dimensions to COST_CUBE and map the foreign keys
Create measures for COST_CUBE and map to columns in the fact table
Set default aggregation method to SUM for all measures over TIME
Add COST_CUBE to the catalog
COST_CUBE successfully added to sh_cat
<<<<< TIME DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
- Period Number of Days created
- Period End Date created
Classify entity descriptor use
- Time dimension
- Long description
- Day name
- Calendar month description

Oracle Database 11g: SQL Tuning Workshop A - 409

Practice 11-2: Using SQL Access Advisor (continued)


-

Calendar quarter description


Fiscal month description
Fiscal quarter description
Short Description
Day name
Calendar month description
Calendar quarter description
Fiscal month description
Fiscal quarter description
Time Span
Days in calendar month
Days in calendar quarter
Days in calendar year
Days in fiscal month
Days in fiscal quarter
Days in fiscal year
End Date
End of calendar month
End of calendar quarter
End of calendar year
End of fiscal month
End of fiscal quarter
End of fiscal year

<<<<< CUSTOMERS DIMENSION >>>>>


Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
- Other Customer Information created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< PRODUCTS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy

Oracle Database 11g: SQL Tuning Workshop A - 410

Practice 11-2: Using SQL Access Advisor (continued)


- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< PROMOTIONS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< CHANNELS DIMENSION >>>>>
Dimension - display name, description and plural name
Level - display name and description
Hierarchy - display name and description
- default calculation hierarchy
- default display hierarchy
Level Attributes - name, display name, description
Drop dimension attributes prior to re-creation
No attribute to drop
Create dimension attributes and add their level attributes
- Long Description created
- Short Description created
Classify entity descriptor use
- Long Description
- Short Description
<<<<< FINAL PROCESSING >>>>>
- Changes have been committed
PL/SQL procedure successfully completed.

Commit complete.

gathering statistics ...


PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 411

Practice 11-2: Using SQL Access Advisor (continued)


PL/SQL procedure successfully completed.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl SQL_Access_Advisor]$

-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/SQL_Access_Advisor/sh
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin:/bin
cp * $ORACLE_HOME/demo/schema/sales_history
sqlplus / as sysdba <<FIN!
SET
SET
SET
SET
SET
SET
SET
SET

ECHO ON
FEEDBACK 1
NUMWIDTH 10
LINESIZE 8000
TRIMSPOOL ON
TAB OFF
PAGESIZE 100
LONG 1000

CONNECT / AS SYSDBA
grant dba to sh;
alter user sh identified by sh account unlock;
connect sh/sh
set serveroutput on size 32768;
set echo on;
variable norecs number;

Rem Clean up

declare
name varchar2(30);
cursor name_cur1 is
select task_name from user_advisor_templates
where task_name like '%SQLACCESS%';

Oracle Database 11g: SQL Tuning Workshop A - 412

Practice 11-2: Using SQL Access Advisor (continued)


begin
--------------------------------------------------------------------------- Get rid of templates, tasks and workloads.
-------------------------------------------------------------------------open name_cur1;
loop
fetch name_cur1 into name;
exit when name_cur1%NOTFOUND;

dbms_advisor.update_task_attributes(name,null,null,'FALSE','FALSE');
dbms_advisor.delete_task(name);
end loop;
close name_cur1;
end;
/

Rem make a temp table

DROP TABLE temp_table purge;


alter system flush shared_pool;
drop table tempjfv purge;
drop table customersjfv purge;

execute dbms_advisor.delete_task('%');
execute dbms_advisor.delete_sqlwkld('%');
execute dbms_sqltune.drop_sqlset('SQLSET_MY_SQLACCESS_WORKLOAD');

EXECUTE DBMS_STATS.UNLOCK_SCHEMA_STATS('SH');

DROP MATERIALIZED VIEW LOG ON "SH"."CUSTOMERS";


DROP MATERIALIZED VIEW LOG ON "SH"."CHANNELS";
DROP MATERIALIZED VIEW LOG ON "SH"."TIMES";
DROP MATERIALIZED VIEW LOG ON "SH"."SALES";
DROP MATERIALIZED VIEW "SH"."MV_01DF0000";
DROP INDEX "SH"."CUSTOMERS_IDX_01DF0002";

Oracle Database 11g: SQL Tuning Workshop A - 413

Practice 11-2: Using SQL Access Advisor (continued)


DROP TABLE "SH"."CUSTOMERS" PURGE;
DROP TABLE "SH"."CUSTOMERS11" CASCADE CONSTRAINTS PURGE;
connect / as sysdba
revoke dba from sh;
@sh_main sh example temp oracle
/u01/app/oracle/product/11.1.0/db_1/demo/schema/sales_history/
/home/oracle/ v3
exit;
FIN!

Oracle Database 11g: SQL Tuning Workshop A - 414

Practice 11-3: Using Automatic SQL Tuning


In this practice, you manually launch Automatic SQL Tuning to automatically tune a
small application workload. You then investigate the outcomes and configuration
possibilities.
1) On the Server page, click Automated Maintenance Tasks, check that Status is set to
Enabled, and click Configure. Click the Configure button next to Automatic SQL
Tuning. Select Yes for Automatic Implementation of SQL Profiles. Then, click
Apply. Execute the ast_setup.sh script from a terminal window connected as the
oracle user. This script creates the AST user used throughout this practice, turns off
automatic maintenance tasks, and drops any existing profiles on queries executed by
the AST user.
$ cd /home/oracle/solutions/Automatic_SQL_Tuning
$ ./ast_setup.sh
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$ ./ast_setup.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 15:31:49
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> drop user ast cascade
*
ERROR at line 1:
ORA-01918: user 'AST' does not exist

SQL> SQL>
User created.
SQL> SQL>
Grant succeeded.
SQL> SQL>
System altered.
SQL> SQL> SQL> SQL> SQL> SQL>
System altered.
SQL> SQL> SQL> SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL> SQL> SQL>
2
3
PL/SQL procedure successfully completed.

Oracle Database 11g: SQL Tuning Workshop A - 415

Practice 11-3: Using Automatic SQL Tuning (continued)


SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
set echo on
drop user ast cascade;
create user ast identified by ast;
grant dba to ast;
alter system flush shared_pool;
--- Turn off AUTOTASK
-alter system set "_enable_automatic_maintenance"=0;
--- Clear out old executions of auto-sqltune
-exec dbms_sqltune.reset_tuning_task('SYS_AUTO_SQL_TUNING_TASK');
--- Drop any profiles on AST queries
-declare
cursor prof_names is
select name from dba_sql_profiles where sql_text like '%AST%';
begin
for prof_rec in prof_names loop
dbms_sqltune.drop_sql_profile(prof_rec.name);
end loop;
end;

Oracle Database 11g: SQL Tuning Workshop A - 416

Practice 11-3: Using Automatic SQL Tuning (continued)


/
FIN!

2) In preparation for the practice, you should execute a workload. Execute the
run_workload_stream.sh script. This script executes, multiple times, a query that
is not correctly optimized. The query in question uses hints that force the optimizer to
pick a suboptimal execution plan. The script execute for approximately 30 seconds.
./run_workload_stream.sh
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
./run_workload_stream.sh
Tue Mar 18 15:38:05 GMT-7 2008
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 15:38:05
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
...
SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining

Oracle Database 11g: SQL Tuning Workshop A - 417

Practice 11-3: Using Automatic SQL Tuning (continued)


and Real Application Testing options
Tue Mar 18 15:38:23 GMT-7 2008
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
date
sqlplus ast/ast <<FIN!
set echo on
select /*+ USE_NL(s c) FULL(s) FULL(c) AST */ c.cust_id,
sum(s.quantity_sold) from sh.sales s, sh.customers c where s.cust_id
= c.cust_id and c.cust_id < 2 group by c.cust_id;

select /*+ USE_NL(s c) FULL(s) FULL(c) AST */ c.cust_id,


sum(s.quantity_sold) from sh.sales s, sh.customers c where s.cust_id
= c.cust_id and c.cust_id < 2 group by c.cust_id;
FIN!
date

3) Automatic SQL Tuning is implemented using an automated task that runs during
maintenance windows. However, you do not wait for the next maintenance window to
open. Instead, you force the opening of your next maintenance window now. This
automatically triggers the Automatic SQL Tuning task. Execute the run_ast.sh
script to open your next maintenance window now. The scripts execution takes a
couple of minutes.
./run_ast.sh
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$ ./run_ast.sh
Tue Mar 18 15:43:48 GMT-7 2008
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 15:43:48
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Oracle Database 11g: SQL Tuning Workshop A - 418

Practice 11-3: Using Automatic SQL Tuning (continued)


Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL>
2
3
4
PL/SQL procedure successfully completed.
SQL> SQL>
WINDOW
------------------------------------------------------------------------------TUESDAY_WINDOW
SQL> SQL> SQL> SQL> SQL> SQL>
System altered.

SQL> SQL> >


PL/SQL procedure successfully completed.
SQL> SQL> >
PL/SQL procedure successfully completed.
SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> SQL> SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL>
2
3
4
5
6
7
8
14
15
16
17
18
19
20
21
22
PL/SQL procedure successfully completed.

10

11

12

SQL> SQL>
2
System altered.
SQL> SQL> SQL> SQL> SQL> SQL> SQL> >
PL/SQL procedure successfully completed.
SQL> SQL> >
PL/SQL procedure successfully completed.
SQL> SQL> SQL> Disconnected from Oracle Database 11g Enterprise
Edition Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
Tue Mar 18 15:44:50 GMT-7 2008
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-------------------------------------------------------------#!/bin/bash

Oracle Database 11g: SQL Tuning Workshop A - 419

13

Practice 11-3: Using Automatic SQL Tuning (continued)


cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
date
sqlplus / as sysdba <<FIN!
set echo on
exec dbms_workload_repository.create_snapshot;
variable window varchar2(20);
begin
select upper(to_char(sysdate,'fmday'))||'_WINDOW' into :window from
dual;
end;
/
print window;
--- Open the corresponding maintenance window, but with other clients
disabled
-alter system set "_enable_automatic_maintenance"=1
/
exec dbms_auto_task_admin.disable( 'auto optimizer stats collection', null, :window);
exec dbms_auto_task_admin.disable( 'auto space advisor', null, :window);
exec dbms_scheduler.open_window(:window, null, true);
--- Close the maintenance window when sqltune is done
-exec dbms_lock.sleep(60);
declare
running number;
begin
loop
select count(*)

Oracle Database 11g: SQL Tuning Workshop A - 420

Practice 11-3: Using Automatic SQL Tuning (continued)


into
from
where

running
dba_advisor_executions
task_name = 'SYS_AUTO_SQL_TUNING_TASK' and
status = 'EXECUTING';

if (running = 0) then
exit;
end if;
dbms_lock.sleep(60);
end loop;
dbms_scheduler.close_window(:window);
end;
/
alter system set "_enable_automatic_maintenance"=0
/
--- Re-enable the other guys so they look like they are enabled in
EM.
-- Still they will be disabled because we have set the underscore.
-exec dbms_auto_task_admin.enable( 'auto optimizer stats collection', null, :window);
exec dbms_auto_task_admin.enable( 'auto space advisor', null, :window);

FIN!
date

4) Execute the run_workload_stream.sh script again. What do you observe?


a) You should see that the execution time for run_workload_stream.sh is much
faster than the original execution. This is probably due to the fact that Automatic
SQL Tuning implemented a profile for your statement automatically.
./run_workload_stream.sh
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
./run_workload_stream.sh
Tue Mar 18 15:46:42 GMT-7 2008
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 15:46:42
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:

Oracle Database 11g: SQL Tuning Workshop A - 421

Practice 11-3: Using Automatic SQL Tuning (continued)


Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
...
SQL>
no rows selected
SQL>
no rows selected
SQL>
no rows selected
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
Tue Mar 18 15:46:42 GMT-7 2008
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-----------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/AST
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin
date
sqlplus ast/ast <<FIN!
set echo on
select /*+ USE_NL(s c) FULL(s) FULL(c) AST */ c.cust_id,
sum(s.quantity_sold) from sh.sales s, sh.customers c where s.cust_id
= c.cust_id and c.cust_id < 2 group by c.cust_id;

Oracle Database 11g: SQL Tuning Workshop A - 422

Practice 11-3: Using Automatic SQL Tuning (continued)


select /*+ USE_NL(s c) FULL(s) FULL(c) AST */ c.cust_id,
sum(s.quantity_sold) from sh.sales s, sh.customers c where s.cust_id
= c.cust_id and c.cust_id < 2 group by c.cust_id;
FIN!
date

5) Force the creation of an Automatic Workload Repository (AWR) snapshot.


./create_snapshot.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 15:51:19
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL>
PL/SQL procedure successfully completed.
SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition
Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
set echo on
exec dbms_workload_repository.create_snapshot;
FIN!

Oracle Database 11g: SQL Tuning Workshop A - 423

Practice 11-3: Using Automatic SQL Tuning (continued)


6) How would you confirm that a SQL Profile was automatically implemented?
a) In Oracle Enterprise Manager, locate the Automatic SQL Tuning summary page
under Server > Automated Maintenance Tasks > Automatic SQL Tuning. The
task has already run in one maintenance window and has results ready to be
viewed.
b) View the tuning results.
c) Look at the graphs on the Task Activity Summary page.
d) Focus on understanding the pie chart and the bar graph next to it. You should be
able to get a feeling for the general finding breakdown, as well as the number of
SQL profiles implemented by the task.
e) Click View Report to see a detailed SQL-level report. Find the SQL that ran in the
AST schema. Note the green check mark meaning that the profile was
implemented.
f) Click the corresponding option button and then View Recommendations.
g) Click the Compare Explain Plans eyeglass icon for the SQL Profile entry.
h) View the old and new explain plans for the query.
i) Then click the Recommendations for SQL_ID locator link to return to the
previous screen.
j) Investigate a SQL profile. While still on the Recommendations for SQL_ID
page, click the SQL text to go to the SQL Details page for this SQL.
k) This takes you to the Tuning History tab. Note the link to
SYS_AUTO_SQL_TUNING_TASK that is there to show that the SQL was tuned
by this tuning task.
l) Look at the Plan Control subpage and note that a profile was created
automatically for this SQL. The AUTO type means it was automatically created.
m) Click the Statistics tab to take a look at the execution history for this SQL.
n) Depending on the speed of your machine, you may not see two hash values. If
that is the case, ignore this step and the following one. Select Real Time: Manual
Refresh from the View Data and then each of possible two Plan Hash Values from
the corresponding drop-down list. Choose one after the other and wait for the
page to refresh each time.
o) Depending on the speed of your environment, you should see one statement with
a relatively high elapsed time per execution, and one with very low elapsed time
per execution. This shows the improved plan. If you select All from the Plan Hash
Values drop-down list, you might not be able to see the execution corresponding
to the statement after tuning in the Summary graph. This might be because the
workload was too short to execute.
7) Generate a text report for more indepth information. From the command line, execute
the get_task_report.sh script. What do you observe?

Oracle Database 11g: SQL Tuning Workshop A - 424

Practice 11-3: Using Automatic SQL Tuning (continued)


a) Note the first queries that fetch execution name and object number from the
advisor schema, followed by the final query that gets the text report. In the text
report, look for the section about the SQL profile finding and peruse the
Validation Results section. This shows you the execution statistics observed
during test-execute and allows you to get a better idea about the profiles quality.
You can also use the report_auto_tuning_task API to get reports that span
multiple executions of the task.
./get_task_report.sh
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$ ./get_task_report.sh
SQL*Plus: Release 11.1.0.6.0 - Production on Tue Mar 18 16:02:18
2008
Copyright (c) 1982, 2007, Oracle.

All rights reserved.

Connected to:
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>
Session altered.
SQL> SQL>
2
3
4
EXECUTION_NAME
STATUS
EXECUTION_START
------------------------------ ----------- ------------------EXEC_1
COMPLETED
03/18/2008 15:43:54
SQL> SQL> SQL> SQL>
2
3
4
5
PL/SQL procedure successfully completed.

SQL> SQL>
LAST_EXEC
------------------------------------------------------------------------------EXEC_1
SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>
8
9
10
PL/SQL procedure successfully completed.

SQL> SQL>
OBJ_ID
---------3
SQL> SQL> SQL> SQL> SQL> SQL>
2
3 GENERAL INFORMATION SECTION
-----------------------------------------------------------------------------Tuning Task Name
: SYS_AUTO_SQL_TUNING_TASK
Tuning Task Owner
: SYS

Oracle Database 11g: SQL Tuning Workshop A - 425

Practice 11-3: Using Automatic SQL Tuning (continued)


Workload Type
Workload
Scope
Global Time Limit(seconds)
Per-SQL Time Limit(seconds)
Completion Status
Started at
Completed at
Number of Candidate SQLs
Cumulative Elapsed Time of SQL (s)

: Automatic High-Load SQL


:
:
:
:
:
:
:
:

COMPREHENSIVE
3600
1200
COMPLETED
03/18/2008 15:43:54
03/18/2008 15:44:14
4
27

-----------------------------------------------------------------------------Object ID : 3
Schema Name: AST
SQL ID
: by9m5m597zh19
SQL Text
: select /*+ USE_NL(s c) FULL(s) FULL(c) AST */
c.cust_id,
sum(s.quantity_sold) from sh.sales s, sh.customers c
where
s.cust_id = c.cust_id and c.cust_id < 2 group by
c.cust_id
-----------------------------------------------------------------------------FINDINGS SECTION (2 findings)
-----------------------------------------------------------------------------1- SQL Profile Finding (see explain plans section below)
-------------------------------------------------------A potentially better execution plan was found for this statement.
SQL profile "SYS_SQLPROF_01463043cc730000" was created
automatically for
this statement.
Recommendation (estimated benefit: 98.62%)
------------------------------------------ An automatically-created SQL profile is present on the system.
Name:
SYS_SQLPROF_01463043cc730000
Status: ENABLED
Validation results
-----------------The SQL profile was tested by executing both its plan and the
original plan
and measuring their respective execution statistics. A plan may
have been
only partially executed if the other could be run to completion in
less time.
Original Plan

With SQL Profile

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

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

--------

COMPLETE

COMPLETE

Improved
-Completion Status:

Oracle Database 11g: SQL Tuning Workshop A - 426

Practice 11-3: Using Automatic SQL Tuning (continued)


Elapsed Time(ms):
100%
CPU Time(ms):
100%
User I/O Time(ms):
Buffer Gets:
98.61%
Disk Reads:
Direct Writes:
Rows Processed:
Fetches:
Executions:

182

182

0
3177

0
44

0
0
0
0
1

0
0
0
0
1

2- Index Finding (see explain plans section below)


-------------------------------------------------The execution plan of this statement can be improved by creating
one or more
indices.
Recommendation (estimated benefit: 90.97%)
------------------------------------------ Consider running the Access Advisor to improve the physical
schema design
or creating the recommended index.
create index SH.IDX$$_00010001 on SH.SALES("CUST_ID");
Rationale
--------Creating the recommended indices significantly improves the
execution plan
of this statement. However, it might be preferable to run
"Access Advisor"
using a representative SQL workload as opposed to a single
statement. This
will allow to get comprehensive index recommendations which
takes into
account index maintenance overhead and additional space
consumption.
-----------------------------------------------------------------------------EXPLAIN PLANS SECTION
-----------------------------------------------------------------------------1- Original With Adjusted Cost
-----------------------------Plan hash value: 4005616876
------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows | Bytes | Cost
(%CPU)| Time
| Pstart| Pstop |
-------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 427

Practice 11-3: Using Automatic SQL Tuning (continued)


------------------|
0 | SELECT STATEMENT
|
|
1 |
13 |
902
(2)| 00:00:1
1 |
|
|
|
1 | HASH GROUP BY
|
|
1 |
13 |
902
(2)| 00:00:1
1 |
|
|
|
2 |
NESTED LOOPS
|
|
1 |
13 |
901
(2)| 00:00:1
1 |
|
|
|* 3 |
TABLE ACCESS FULL | CUSTOMERS |
1 |
5 |
405
(1)| 00:00:0
5 |
|
|
|
4 |
PARTITION RANGE ALL|
|
1 |
8 |
495
(3)| 00:00:0
6 |
1 |
28 |
|* 5 |
TABLE ACCESS FULL | SALES
|
1 |
8 |
495
(3)| 00:00:0
6 |
1 |
28 |
------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------3 - filter("C"."CUST_ID"<2)
5 - filter("S"."CUST_ID"<2 AND "S"."CUST_ID"="C"."CUST_ID")
2- Using SQL Profile
-------------------Plan hash value: 3070788227
--------------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows
| Bytes |
Cost (%CPU)| Time
| Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
13 |
55
(2)| 00:00:01 |
|
|
|
1 | HASH GROUP BY
|
|
1 |
13 |
55
(2)| 00:00:01 |
|
|
|
2 |
NESTED LOOPS
|
|
1 |
13 |
54
(0)| 00:00:01 |
|
|
|
3 |
PARTITION RANGE ALL
|
|
1 |
8 |
54
(0)| 00:00:01 |
1 |
28 |
|
4 |
TABLE ACCESS BY LOCAL INDEX ROWID| SALES
|
1 |
8 |
54
(0)| 00:00:01 |
1 |
28 |

Oracle Database 11g: SQL Tuning Workshop A - 428

Practice 11-3: Using Automatic SQL Tuning (continued)


|
|

5 |

|*
|

6 |

BITMAP CONVERSION TO ROWIDS

|
|

|
|
|
BITMAP INDEX RANGE SCAN

| SALES_CUST_BIX |

|
|
1 |
28 |
|* 7 |
INDEX UNIQUE SCAN
| CUSTOMERS_PK
|
1 |
5 |
0
(0)| 00:00:01 |
|
|
--------------------------------------------------------------------------------------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------6 - access("S"."CUST_ID"<2)
filter("S"."CUST_ID"<2)
7 - access("S"."CUST_ID"="C"."CUST_ID")
filter("C"."CUST_ID"<2)
3- Using New Indices
-------------------Plan hash value: 1871796534
--------------------------------------------------------------------------------------------------------------------| Id | Operation
| Name
| Rows
| Bytes |
Cost (%CPU)| Time
| Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------|
0 | SELECT STATEMENT
|
|
1 |
13 |
5
(0)| 00:00:01 |
|
|
|
1 | SORT GROUP BY NOSORT
|
|
1 |
13 |
5
(0)| 00:00:01 |
|
|
|
2 |
NESTED LOOPS
|
|
|
|
|
|
|
|
|
3 |
NESTED LOOPS
|
|
1 |
13 |
5
(0)| 00:00:01 |
|
|
|* 4 |
INDEX RANGE SCAN
| CUSTOMERS_PK
|
1 |
5 |
2
(0)| 00:00:01 |
|
|
|* 5 |
INDEX RANGE SCAN
| IDX$$_00010001 |
1 |
|
2
(0)| 00:00:01 |
|
|
|
6 |
TABLE ACCESS BY GLOBAL INDEX ROWID| SALES
|
1 |
8 |
3
(0)| 00:00:01 | ROWID | ROWID |
-------------------------------------------------------------------------------

Oracle Database 11g: SQL Tuning Workshop A - 429

Practice 11-3: Using Automatic SQL Tuning (continued)


--------------------------------------Predicate Information (identified by operation id):
--------------------------------------------------4 - access("C"."CUST_ID"<2)
5 - access("S"."CUST_ID"="C"."CUST_ID")
filter("S"."CUST_ID"<2)
------------------------------------------------------------------------------

SQL> SQL> Disconnected from Oracle Database 11g Enterprise Edition


Release 11.1.0.6.0 - Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
set echo on
set long 1000000000
set longchunksize 1000
--- Check the execution names
-alter session set nls_date_format = 'MM/DD/YYYY HH24:MI:SS';
select execution_name, status, execution_start
from
dba_advisor_executions
where task_name = 'SYS_AUTO_SQL_TUNING_TASK'
order by execution_start;
variable last_exec varchar2(30);
begin
select max(execution_name) keep (dense_rank last order by
execution_start)
into
:last_exec
from
dba_advisor_executions
where task_name = 'SYS_AUTO_SQL_TUNING_TASK';

Oracle Database 11g: SQL Tuning Workshop A - 430

Practice 11-3: Using Automatic SQL Tuning (continued)


end;
/
print :last_exec
--- Find the object ID for query AST with sql_id by9m5m597zh19
-variable obj_id number;
begin
select
into
from
where

object_id
:obj_id
dba_advisor_objects
task_name = 'SYS_AUTO_SQL_TUNING_TASK' and
execution_name = :last_exec and
type = 'SQL' and
attr1 = 'by9m5m597zh19';

end;
/
print :obj_id
--- Get a text report to drill down on this one query
-set pagesize 0
select dbms_sqltune.report_auto_tuning_task(
:last_exec, :last_exec, 'TEXT', 'TYPICAL', 'ALL', :obj_id)
from dual;
FIN!

8) Investigate how to configure Automatic SQL Tuning using Enterprise Manager.


a) Back in EM, go to the Automated Maintenance Tasks page.
b) The chart here shows times in the past when each client was executed, and times
in the future when they are scheduled to run again.
c) Modify the graphs begin and end points with the widgets at the upper right.
d) Click the Configure button.
e) This brings you to the Automated Maintenance Tasks Configuration page.
f) From this page, you can disable individual clients and change which windows
they run in.
g) Disable the Automatic SQL Tuning client entirely, click Apply, and then click the
locator link to return to the last page.
h) Note that no light blue bars appear for Automatic SQL Tuning in the future.
i) Return to the configuration page, enable the task again, and click Apply to undo
your changes.

Oracle Database 11g: SQL Tuning Workshop A - 431

Practice 11-3: Using Automatic SQL Tuning (continued)


j) Click the Automatic SQL Tuning link on the Automated Maintenance Tasks
Configuration page.
k) This takes you to the page where you can configure the task itself, and set beyond
when it will run.
l) Note that there are more fine-grained controls here, such as one that allows the
task to run but not implement profiles, and one that allows you to control the
maximum number of profiles created per run.
9) Investigate how to configure Automatic SQL Tuning using PL/SQL. From your
terminal session, execute the manual_config.sh script. What does it do?
a) Note the first action. You changed the total time limit for the task. Instead of
running for an unlimited amount of time (still bound by the maintenance window
boundaries), it now runs for a maximum of one hour. The
execute_tuning_task API call runs the task immediately, in the foreground.
Use this to run the task yourself whenever you want.
./manual_config.sh
-------------------------------------------------------------#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
connect / as sysdba
set echo on
--- Configure the task to run for at most 30 minutes. The value of
the
-- TIME_LIMIT parameter determines the total time allowed for a task
execution.
-select parameter_value
from
dba_advisor_parameters
where task_name = 'SYS_AUTO_SQL_TUNING_TASK' and
parameter_name = 'TIME_LIMIT';
exec dbms_sqltune.set_tuning_task_parameter( 'SYS_AUTO_SQL_TUNING_TASK', 'TIME_LIMIT', 1800);

Oracle Database 11g: SQL Tuning Workshop A - 432

Practice 11-3: Using Automatic SQL Tuning (continued)


select parameter_value
from
dba_advisor_parameters
where task_name = 'SYS_AUTO_SQL_TUNING_TASK' and
parameter_name = 'TIME_LIMIT';
--- Run the task immediately
-exec dbms_sqltune.execute_tuning_task('SYS_AUTO_SQL_TUNING_TASK');
FIN!

10) Note: In your case, the task executes quickly because the workload to take into
account is really small. However, you could use the interrupt_task.sh script
from another session to stop the task, should it last too long.
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$ cat interrupt_task.sh
#!/bin/bash
cd /home/oracle/solutions/Automatic_SQL_Tuning
export ORACLE_SID=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.1.0/db_1
export
PATH=/u01/app/oracle/product/11.1.0/db_1/bin:/bin:/usr/bin:/usr/loca
l/bin:/usr/X11R6/bin:/usr/java/jdk1.5.0_11/bin
sqlplus / as sysdba <<FIN!
connect / as sysdba
set echo on
--- Interrupt the task
-exec dbms_sqltune.interrupt_tuning_task('SYS_AUTO_SQL_TUNING_TASK');
FIN!
[oracle@edrsr33p1-orcl Automatic_SQL_Tuning]$

11) Ensure that you disable automatic implementation of SQL profiles to clean up your
environment.
a) On the EM Server page, click Automated Maintenance Tasks.
b) Check that Status is set to Enabled, and click Configure.
c) Click the Configure button next to Automatic SQL Tuning.
d) Select No for Automatic Implementation of SQL Profiles.
e) Then, click Apply.

Oracle Database 11g: SQL Tuning Workshop A - 433

Practice 11-3: Using Automatic SQL Tuning (continued)

Oracle Database 11g: SQL Tuning Workshop A - 434

You might also like