SQL Tuning Guide Note 20: Improving Real-World Performance Through Cursor Sharing

This article is a note for Chapter 20 "Improving Real-World Performance Through Cursor Sharing" of the SQL Tuning Guide .

Cursor sharing can improve database application performance by orders of magnitude.

important basic concepts

  • optimizer goal
    The prioritization of resource usage by the optimizer. Using the OPTIMIZER_MODE initialization parameter, you can set the optimizer goal best throughput or best response time.

  • bind variable
    A placeholder in a SQL statement that must be replaced with a valid value or value address for the statement to execute successfully. By using bind variables, you can write a SQL statement that accepts inputs or parameters at run time.

20.1 Overview of Cursor Sharing

Oracle Database can share cursors, which are pointers to private SQL areas in the shared pool.

20.1.1 About Cursors

The private SQL area holds information about parsed SQL statements and other session-specific information for processing.

When a server process executes SQL or PL/SQL code, the process uses private SQL areas to store bind variable values, query execution status information, and query execution work areas . Private SQL areas for each statement execution are not shared and may contain different values ​​and data.

A cursor is the name or handle of a specific private SQL area. Cursors contain session-specific state information, such as bind variable values ​​and result sets.

As shown in the diagram below, you can think of a cursor as a pointer on the client side and a state on the server side. Because cursors are closely related to private SQL areas, these terms are sometimes used interchangeably.
insert image description here

20.1.1.1 Private and Shared SQL Areas

Cursors in private SQL areas point to shared SQL areas in the library cache .

Unlike private SQL areas, which contain session state information, shared SQL areas contain a statement's parse tree and execution plan . For example, SELECT * FROM employeesthe execution of has a plan and parse tree stored in a shared SQL area. As another example, SELECT * FROM employeesthe execution of , which differs both in syntax and semantics, has a plan and parse tree stored in a separate shared SQL area.

Multiple private SQL areas in the same or different sessions can reference a single shared SQL area, a phenomenon known as cursor sharing . For example, executing in one session SELECT * FROM employeesand executing in a different session SELECT * FROM employees(accessing the same table) can use the same parse tree and plan. A shared SQL area accessed by multiple statements is called a shared cursor.
insert image description here
Oracle Database uses the following steps to automatically determine whether an issued SQL statement or PL/SQL block is textually identical to another statement currently in the library cache:

  1. Computes the hash value of the statement text .
  2. The database looks up matching hashes for existing SQL statements in the shared pool. The following options are possible:
    – There is no matching hash value.
    In this case, no SQL statement currently exists in the shared pool, so the database performs a hard parse . This will end the shared pool check.
    – There is a matching hash.
    In this case, the database goes to the next step, which is text matching.
  3. The database compares the text of the matching statement with the text of the hash statement to determine if they are the same. The following options are possible:
    – Text matching failed.
    In this case, the text matching process stops, resulting in hard parsing.
    – The text matches successfully.
    In this case, the database goes to the next step: determining whether the SQL can share the existing parent cursor.
    For text matching to occur, the text of an SQL statement or PL/SQL block must be identical character-by-character , including spaces, capitalization, and comments. For example, the following statements cannot use the same shared SQL area:
    SELECT * FROM employees;
    SELECT * FROM Employees;
    SELECT *  FROM employees;
    
    In general, SQL statements that differ only in literals (121 and 247 in this example) cannot use the same shared SQL area. For example, the following statements do not parse into the same SQL region:
    SELECT count(1) FROM employees WHERE manager_id = 121;
    SELECT count(1) FROM employees WHERE manager_id = 247;
    
    The only exception to this rule is when the parameter CURSOR_SHARING has been set to FORCE, in which case similar statements can share SQL regions.

20.1.1.2 Parent and Child Cursors

Each parsed SQL statement has a parent cursor and one or more child cursors.

The parent cursor stores the text of the SQL statement. Two statements share the same parent cursor if their text is the same. However, if the text is different, the database creates a separate parent cursor.

In this example, the first two statements are syntactically different (the letter "c" is lowercased in the first statement and uppercased in the second), but semantically identical. These statements have different parent cursors due to syntax differences. The third statement is syntactically identical to the first statement (lowercase "c"), but semantically different because it refers to the customer table in a different schema. Since the syntax is the same, the third statement can share a parent cursor with the first statement.

SQL> CONNECT oe@inst1
Enter password: *******
Connected.
SQL> SELECT COUNT(*) FROM customers;

  COUNT(*)
----------
       319

SQL> SELECT COUNT(*) FROM Customers;

  COUNT(*)
----------
       319

SQL> CONNECT sh@inst1
Enter password:  *******
Connected.
SQL> SELECT COUNT(*) FROM customers;

  COUNT(*)
----------
    55500

The following V$SQL query indicates two parent nodes. The statement with SQL ID 8h916vv2yw400, the lowercase "c" version of the statement, has a parent cursor and two child cursors: child 0 and child 1. The statement with SQL ID 5rn2uxjtpz0wd, the uppercase "c" version of the statement, has a different parent cursor and only one child cursor: child 0.

SQL> CONNECT SYSTEM@inst1
Enter password:  *******
Connected.

COL SQL_TEXT FORMAT a30
COL CHILD# FORMAT 99999
COL EXEC FORMAT 9999
COL SCHEMA FORMAT a6
SELECT SQL_ID, PARSING_SCHEMA_NAME AS SCHEMA, SQL_TEXT, 
CHILD_NUMBER AS CHILD#, EXECUTIONS AS EXEC FROM V$SQL 
WHERE SQL_TEXT LIKE '%ustom%' AND SQL_TEXT NOT LIKE '%SQL_TEXT%' ORDER BY SQL_ID;

SQL_ID        SCHEMA SQL_TEXT                       CHILD#  EXEC
------------- ------ ------------------------------ ------ -----
5rn2uxjtpz0wd OE     SELECT COUNT(*) FROM Customers      0     1
8h916vv2yw400 OE     SELECT COUNT(*) FROM customers      0     1
8h916vv2yw400 SH     SELECT COUNT(*) FROM customers      1     1
20.1.1.2.1 Parent Cursors and V$SQLAREA

The V$SQLAREA view contains one row for each parent cursor.

In the example below, the V$SQLAREA query shows two parent cursors, each identified with a different SQL_ID . VERSION_COUNT indicates the number of child cursors .
There is an error in the original text of this SQL

COL SQL_TEXT FORMAT a30
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%ustomer%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';
 
SQL_TEXT                       SQL_ID        VERSION_COUNT HASH_VALUE
------------------------------ ------------- ------------- ----------
SELECT COUNT(*) FROM customers 8h916vv2yw400             2 3320713216
SELECT COUNT(*) FROM Customers 5rn2uxjtpz0wd             1 1935639437

In the preceding output, SELECT * FROM customersa VERSION_COUNT of 2 indicates multiple child cursors, which is necessary because the statement was executed against two different objects. In contrast, the statement SELECT * FROM Customers (note the capital "E") is executed only once, so there is a parent cursor and a child cursor (VERSION_COUNT is 1).

20.1.1.2.2 Child Cursors and V$SQL

Each parent cursor has one or more child cursors.

A subcursor contains an execution plan, bind variables, metadata about the objects referenced in the query, the optimizer environment, and other information. In contrast to parent cursors, child cursors do not store the text of the SQL statement .

If the statement can reuse a parent cursor, the database checks to see if the statement can reuse an existing child cursor. The database performs several checks, including the following:

  • The database compares the objects referenced in the issued statement with the objects referenced by the statements in the pool to ensure they are all the same.
    References to schema objects within an SQL statement or PL/SQL block must resolve to the same object within the same schema. For example, if two users issue the following SQL statement, and each user has its own employee table, the following statement is not identical because it references a different employee table for each user:
SELECT * FROM employees;

Note: Databases can share cursors on private temporary tables, but only within the same session. The database associates the session identifier as part of the cursor context. During soft resolution, the database can only share child cursors if the current session ID matches a session ID in the cursor context.

  • The database determines if the optimizer modes are the same.
    For example, SQL statements must be optimized using the same optimizer goal.

Example 20-2 Multiple Subcursors
V$SQL describes statements currently residing in the library cache. It contains one row for each child cursor, as shown in the following example:

COL SQL_TEXT FORMAT A30
COL USR FORMAT A6
SELECT SQL_TEXT, SQL_ID, USERNAME AS USR, CHILD_NUMBER AS CHILD#, 
       HASH_VALUE, PLAN_HASH_VALUE AS PLAN_HASHV
FROM   V$SQL s, DBA_USERS d
WHERE  SQL_TEXT LIKE '%ustomer%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%'
AND    d.USER_ID = s.PARSING_USER_ID;

SQL_TEXT                       SQL_ID        USR        CHILD# HASH_VALUE PLAN_HASHV
------------------------------ ------------- ------ ---------- ---------- ----------
SELECT COUNT(*) FROM customers 8h916vv2yw400 OE              0 3320713216 1140397121
SELECT COUNT(*) FROM Customers 5rn2uxjtpz0wd OE              0 1935639437 1140397121
SELECT COUNT(*) FROM customers 8h916vv2yw400 SH              1 3320713216  237477902
20.1.1.2.3 Cursor Mismatches and V$SQL_SHARED_CURSOR

If a parent cursor has multiple child cursors, the V$SQL_SHARED_CURSOR view provides information on why the cursors are not shared. For several types of incompatibilities, the TRANSLATION_MISMATCH column indicates a mismatch with the value Y or N.

Example 20-3 Translation Mismatch
In this example, the TRANSLATION_MISMATCH column shows that two statements (SELECT * FROM employees) refer to different objects, resulting in a TRANSLATION_MISMATCH value of Y for the last statement. Since it cannot be shared, each statement has a separate child cursor, as indicated by 0 and 1 for CHILD_NUMBER.

SELECT S.SQL_TEXT, S.CHILD_NUMBER, s.CHILD_ADDRESS, 
       C.TRANSLATION_MISMATCH
FROM   V$SQL S, V$SQL_SHARED_CURSOR C
WHERE  SQL_TEXT LIKE '%ustomer%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%'
AND S.CHILD_ADDRESS = C.CHILD_ADDRESS;

SQL_TEXT                       CHILD_NUMBER CHILD_ADDRESS    T
------------------------------ ------------ ---------------- -
SELECT COUNT(*) FROM customers            0 00000000A7208390 N
SELECT COUNT(*) FROM customers            1 00000000A7192130 Y
SELECT COUNT(*) FROM Customers            0 00000000A71F3EF8 N

20.1.2 About Cursors and Parsing

If the application issues a statement, and Oracle Database cannot reuse the cursor, it must build a new executable version of the application code. This operation is called hard parsing .

A soft parse is any parse that is not a hard parse, and occurs when the database can reuse existing code . Some soft parses take up fewer resources than others. For example, Oracle Database can perform various optimizations and store child cursors in a shared SQL area if the statement's parent cursor already exists. However, Oracle Database must also store the parent cursor in a shared SQL area if the parent cursor does not exist, which incurs additional memory overhead.

In effect, hard parsing recompiles the statement before running it. Hard-parsing SQL statements before each execution is similar to recompiling a C program before each execution . Hard parsing performs the following operations :

  • Check the syntax of SQL statements
  • Check the semantics of SQL statements
  • Check the access rights of the user issuing the statement
  • Create an execution plan
  • Access the library cache and data dictionary cache multiple times to check the data dictionary

A particularly resource-intensive aspect of hard parsing is accessing the library cache and data dictionary cache multiple times to check the data dictionary. When the database accesses these areas, it uses a serialization device called a latch so that their definition does not change during the check. Latch contention increases statement execution time and reduces concurrency.

For all of the above reasons, the CPU and memory overhead of hard parsing can cause serious performance problems. These problems are especially evident in Web applications that accept user input from forms and then dynamically generate SQL statements. The Real World Performance Group strongly recommends minimizing hard parses as much as possible .

https://youtu.be/WcRcmfSIajc

20.1.3 About Literals and Bind Variables

Bind variables are critical to cursor sharing in Oracle Database applications.

20.1.3.1 Literals and Cursors

Some Oracle applications use literals instead of bind variables when constructing SQL statements.

For example, the statement SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101 uses the literal value 101 as the employee ID. By default, Oracle Database cannot take advantage of cursor sharing when similar statements do not use bind variables. Therefore, Oracle Database treats an identical statement except for the value 102 or any other random value as a completely new statement that needs to be hard-parsed.

The Real-World Performance Group has identified applications that use text as a common cause of performance, scalability, and security issues . In the real world, it's not uncommon to write applications quickly without considering cursor sharing. A typical example is a "screen scraping" application that copies content from a Web form and then concatenates strings to dynamically construct SQL statements.

The main problems caused by using literal values ​​include:

  • Applications that concatenate text entered by end users are vulnerable to SQL injection attacks. This threat can only be eliminated by rewriting the application to use bind variables.
  • If every statement is hard parsed, the cursors are not shared, so the database has to consume more memory to create the cursors.
  • Oracle Database must lock the shared pool and library cache during hard resolves. As the number of hard parses increases, so does the number of processes waiting to lock the shared pool. This situation reduces concurrency and increases contention.

https://youtu.be/EBdZ-RE2HFs

Example 20-6 Text and Cursor Sharing

Consider an application that executes the following statements, which differ only in words:

SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 120;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 165;

The V$SQLAREA query below shows that the three statements require three different parent cursors. Each parent cursor requires its own child cursor, as indicated by VERSION_COUNT.

COL SQL_TEXT FORMAT a30
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT                              SQL_ID VERSION_COUNT HASH_VALUE
------------------------------ ------------- ------------- ----------
SELECT SUM(salary) FROM hr.emp b1tvfcc5qnczb             1  191509483
loyees WHERE employee_id < 165
SELECT SUM(salary) FROM hr.emp cn5250y0nqpym             1 2169198547
loyees WHERE employee_id < 101
SELECT SUM(salary) FROM hr.emp au8nag2vnfw67             1 3074912455
loyees WHERE employee_id < 120

20.1.3.2 Bind Variables and Cursors

You can develop Oracle applications to use bind variables instead of literals.

Bind variables are placeholders in queries. For example, the statement SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id uses the bind variable: emp_id as the employee ID.

The Real-World Performance team has found that applications that use bind variables perform better, scale better, and are more secure. The main benefits of using bind variables include:

  • Applications that use bind variables are not vulnerable to the same SQL injection attacks as applications that use literals.
  • Oracle Database can take advantage of cursor sharing when the same statement uses bind variables and share plans and other information when different values ​​are bound to the same statement.
  • Oracle Database avoids the overhead of locking the shared pool and library cache required for hard parsing.

Example 20-7 Bind Variables and Shared Cursors

The following example creates an emp_id bind variable using the VARIABLE command in SQL*Plus, then executes a query with three different bind values ​​(101, 120, and 165):

VARIABLE emp_id NUMBER

EXEC :emp_id := 101;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
EXEC :emp_id := 120;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
EXEC :emp_id := 165;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;

The following V$SQLAREA query shows a unique SQL statement:

COL SQL_TEXT FORMAT a34
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT                           SQL_ID        VERSION_COUNT HASH_VALUE
---------------------------------- ------------- ------------- ----------
SELECT SUM(salary) FROM hr.employe 4318cbskba8yh             1 615850960
es WHERE employee_id < :emp_id

A VERSION_COUNT value of 1 indicates that the database reuses the same child cursor instead of creating three separate child cursors. This reuse is made possible by using bind variables .

20.1.3.3 Bind Variable Peeking

In bind variable lookup (also known as bind lookup), the optimizer looks at the value in a bind variable when the database performs a hard parse of the statement.

The optimizer doesn't look at bind variable values ​​before each parse. Instead, the optimizer only looks the first time the optimizer is called, which is during hard parsing.

When the query uses literals, the optimizer can use the literal values ​​to find the best plan. However, when a query uses bind variables, the optimizer must choose the best plan in the absence of literals in the SQL text. This task can be very difficult. By looking at bound values ​​during the initial hard parse, the optimizer can determine the cardinality of WHERE clause conditions as if literals were used, thereby improving plans.

Because the optimizer only looks at bound values ​​during hard parsing, plans may not be optimal for all possible bound values. The following example illustrates this principle.

Example 20-8 Literals result in different execution plans

Suppose you execute the following statements, which execute three different statements with different literals (101, 120, and 165), and then display the execution plan for each statement:

SET LINESIZE 167
SET PAGESIZE 0
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 120;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 165;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());

The database hard-parsed all three statements that were not identical. The DISPLAY_CURSOR output (edited for clarity) shows that the optimizer chose the same index range scan plan for the first two statements, but a full table scan plan for the statement using literal 165:

SQL_ID	cn5250y0nqpym, child number 0
-------------------------------------
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101

Plan hash value: 2410354593

-------------------------------------------------------------------------------------
|Id| Operation                            | Name         |Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                     |               |  |   |2 (100)|          |
| 1|  SORT AGGREGATE                      |               |1 | 8 |       |          |
| 2|   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES     |1 | 8 |2 (0)  | 00:00:01 |
|*3|    INDEX RANGE SCAN                  | EMP_EMP_ID_PK |1 |   |1 (0)  | 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("EMPLOYEE_ID"<101)

SQL_ID	au8nag2vnfw67, child number 0
-------------------------------------
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 120

Plan hash value: 2410354593

-------------------------------------------------------------------------------------
|Id| Operation                            | Name         |Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                     |               |  |   |2 (100)|          |
| 1|  SORT AGGREGATE                      |               |1 | 8 |       |          |
| 2|   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES     |20|160|2 (0)  | 00:00:01 |
|*3|    INDEX RANGE SCAN                  | EMP_EMP_ID_PK |20|   |1 (0)  | 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("EMPLOYEE_ID"<120)

SQL_ID	b1tvfcc5qnczb, child number 0
-------------------------------------
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 165

Plan hash value: 1756381138

-------------------------------------------------------------------------
| Id  | Operation          | Name      |Rows| Bytes |Cost(%CPU)| Time   |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |           |    |       | 2 (100)|          |
|   1 |  SORT AGGREGATE    |           |  1 |     8 |        |          |
|*  2 |   TABLE ACCESS FULL| EMPLOYEES | 66 |   528 | 2   (0)| 00:00:01 |
-------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("EMPLOYEE_ID"<165)

The preceding output shows that the optimizer considers full table scans to be more efficient than index scans for queries that return more rows.

Example 20-9 Bind Variables Cause Cursor Reuse

This example rewrites the query performed in Example 20-8 to use bind variables instead of literals. You bind the same values ​​(101, 120 and 165) to the bind variable: emp_id, and then display the execution plan for each value:

VAR emp_id NUMBER

EXEC :emp_id := 101;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
EXEC :emp_id := 120;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
EXEC :emp_id := 165;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());

The DISPLAY_CURSOR output shows that the optimizer chose the exact same plan for all three statements:

SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id

Plan hash value: 2410354593

-------------------------------------------------------------------------------------
| Id | Operation                            | Name      |Rows|Bytes|Cost (%CPU)|Time|
-------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT                     |               | |  |2 (100)|          |
|  1 |  SORT AGGREGATE                      |               |1|8 |       |          |
|  2 |   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES     |1|8 |  2 (0)| 00:00:01 |
|* 3 |    INDEX RANGE SCAN                  | EMP_EMP_ID_PK |1|  |  1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("EMPLOYEE_ID"<:EMP_ID)

In contrast, when the above statement is executed literally, the optimizer chooses the less expensive full table scan when the employee ID value is 165. That's the problem adaptive cursor sharing solves.

20.1.4 About the Life Cycle of Shared Cursors

When the optimizer parses out a new non-DDL SQL statement, the database will allocate a new shared SQL area. The amount of memory required depends on the complexity of the statement.

A database can drop a shared SQL region from the shared pool even if the region corresponds to an open cursor that has not been used for a long time. If an open cursor is later used to run its statement, the database will reparse the statement and allocate a new shared SQL area. The database does not delete cursors whose statements are executing or whose rows have not been fully fetched.

Shared SQL regions may become invalid due to changes in dependent schema objects or optimizer statistics. Oracle Database uses two techniques to manage cursor lifecycle: invalidation and scroll invalidation.

20.1.4.1 Cursor Marked Invalid

When a shared SQL area is marked invalid, the database can remove it from the shared pool, along with valid cursors that have not been used for some time.

In some cases, the database must execute statements associated with an invalid shared SQL area in the shared pool. In this case, the database hard-parses the statement before executing it.

The database immediately marks a dependent shared SQL region as invalid when the following conditions are met:

  • When the NO_INVALIDATE parameter is FALSE, DBMS_STATS collects statistics for tables, table clusters, or indexes.
  • SQL statements refer to schema objects that are later modified by DDL statements using immediate cursor invalidation (the default).
    You can manually specify immediate invalidation on statements such as ALTER TABLE ... IMMEDIATE VALIDATION and ALTER INDEX ... IMMEDIATE VALIDATION , or set the CURSOR_INVALIDATION initialization parameter to IMMEDIATE at the session or system level.
    Note: DDL statements using the DEFERRED VALIDATION clause override the IMMEDIATE setting of the CURSOR_INVALIDATION initialization parameter.

When the above conditions are met, the database will re-parse the affected statement the next time it is executed.

When the database invalidates a cursor, the VSQL.INVALIDATIONS value is incremented (for example, from 0 to 1), and the VSQL.INVALIDATIONS value is incremented (for example, from 0 to 1), and VSQL.INVALIDATIONS is incremented (for example, from 0 to 1), and VSQ L . I N V A L I D A T I ONS value increments (for example, from 0 to 1 ), and V SQL.OBJECT_STATUS shows INVALID_UNAUTH.

Example 20-10 Forces the cursor to be invalidated by setting NO_INVALIDATE=FALSE

SQL> SELECT COUNT(*) FROM sales;

  COUNT(*)
----------
    918843

SQL> SELECT PREV_SQL_ID, SQL_ID FROM V$SESSION WHERE SID = SYS_CONTEXT('userenv', 'SID');

SQL_ID
-------------
1y17j786c7jbh

SQL> SELECT CHILD_NUMBER, EXECUTIONS,
  2  PARSE_CALLS, INVALIDATIONS, OBJECT_STATUS 
  3  FROM V$SQL WHERE SQL_ID = '1y17j786c7jbh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS INVALIDATIONS OBJECT_STATUS
------------ ---------- ----------- ------------- -------------
           0          1           1             0 VALID

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(null,'sales',no_invalidate => FALSE);

PL/SQL procedure successfully completed.

SQL> SELECT CHILD_NUMBER, EXECUTIONS,
  2  PARSE_CALLS, INVALIDATIONS, OBJECT_STATUS
  3  FROM V$SQL WHERE SQL_ID = '1y17j786c7jbh';

CHILD_NUMBER EXECUTIONS PARSE_CALLS INVALIDATIONS OBJECT_STATUS
------------ ---------- ----------- ------------- --------------
           0          1           1             1 INVALID_UNAUTH

20.1.4.2 Cursors Marked Rolling Invalid

When the cursor is marked as rolling invalid (V$SQL.IS_ROLLING_INVALID is Y), the database will gradually perform hard parsing over a long period of time.

Note: When V$SQL.IS_ROLLING_REFRESH_INVALID is Y, the underlying object has changed, but the cursor does not need to be recompiled. The database updates the metadata in the cursor.

Purpose of scrolling invalidation

Since the dramatic increase in hard parses can significantly degrade performance, scroll invalidation (also known as delayed invalidation) is useful for workloads that invalidate many cursors at the same time. The database assigns each invalid cursor a randomly generated time period. SQL regions that expire at the same time usually have different time periods.

Hard parsing occurs only when the query accessing the cursor is executed after the time period has expired. In this way, the database spreads the overhead of hard parsing over time.

Note: If the parallel SQL statement is marked as rolling invalid, then the database will perform hard parse the next time it is executed, regardless of whether the time has expired. In an Oracle Real Application Clusters (Oracle RAC) environment, this technology ensures consistency between the execution plans of the parallel execution server and the query coordinator.

An analogy to rolling failure might be the gradual replacement of worn-out office furniture. Instead of replacing all of the furniture at once, forcing a significant financial outlay, one company assigned different expiration dates to each piece of furniture. Over the course of a year, an item is used until it is replaced, at which point costs are incurred.

Delayed failure specification

By default, DDL specifies that statements that access objects use immediate cursor invalidation. For example, if you create a table or index, cursors referencing that table or index will use immediate invalidation.

If the DDL statement supports delayed cursor invalidation, the default behavior can be overridden with statements such as ALTER TABLE ... DEFERRED INVALIDATION. Options depend on the DDL statement. For example, ALTER INDEX only supports DEFERRED INVALIDATION when the UNUSABLE or REBUILD options are also specified.

An alternative to DDL is to set the CURSOR_INVALIDATION initialization parameter to DEFERRED at the session or system level. DDL statements that use the IMMEDIATE INVALIDATION clause override the DEFERRED setting of the CURSOR_INVALIDATION initialization parameter.

when scroll failure occurs

If the DEFERRED INVALIDATION attribute applies to an object, either as a result of DDL or initialization parameter settings, statements that access that object may be subject to delayed invalidation. The database marks a shared SQL area as rolling invalid in any of the following cases:

  • When the NO_INVALIDATE parameter is set to DBMS_STATS.AUTO_INVALIDATE, DBMS_STATS collects statistics for tables, table clusters, or indexes. This is the default setting.

  • Without preventing the use of delayed invalidation, issue one of the following statements with delayed invalidation:

    • ALTER TABLE on partitioned tables
    • ALTER TABLE … PARALLEL
    • ALTER INDEX … UNUSABLE
    • ALTER INDEX … REBUILD
    • CREATE INDEX
    • DROP INDEX
    • TRUNCATE TABLE on partitioned tables

    A subset of DDL statements require immediate invalidation of cursors for DML (INSERT, UPDATE, DELETE, or MERGE), but SELECT statements do not. A number of factors related to a particular DDL statement and the cursors affected determine whether an Oracle Database uses delayed invalidation.

20.2 CURSOR_SHARING and Bind Variable Substitution

This topic explains what the CURSOR_SHARING initialization parameter is, and how setting it to different values ​​affects the way Oracle Database uses bind variables.

20.2.1 CURSOR_SHARING Initialization Parameter

The CURSOR_SHARING initialization parameter controls how the database handles statements with bind variables.

In Oracle Database 12c, this parameter supports the following values:

  • EXACT
    This is the default. The database only allows statements with the same text to share a cursor. The database will not attempt to replace literal values ​​with system-generated bind variables. In this case, the optimizer generates a plan for each statement based on the literal value.
  • The FORCE
    database replaces all literals with system-generated bind variables . For identical statements after bind variable substitution literals, the optimizer uses the same plan.

NOTE: The SIMILAR value for CURSOR_SHARING is deprecated.

You can set CURSOR_SHARING at the system or session level, or use the CURSOR_SHARING_EXACT hint at the statement level.

20.2.2 Parsing Behavior When CURSOR_SHARING = FORCE

When the SQL statement uses literals instead of bind variables, setting the CURSOR_SHARING initialization parameter to FORCE enables the database to replace the literals with system-generated bind variables . Using this technique, the database can sometimes reduce the number of parent cursors in a shared SQL area.

Note: If the statement uses an ORDER BY clause, the cursor is not necessarily shareable, although the database may perform literal substitution in that clause, because different values ​​for column numbers as literals mean different query results and may have Different execution plans. The column numbers in the ORDER BY clause affect query planning and execution, so the database cannot share two cursors with different column numbers .

When CURSOR_SHARING is set to FORCE, the database performs the following steps during parsing:

  1. Copy all literals in the statement to PGA and replace them with system-generated bind variables
-- 替换前
SELECT SUBSTR(last_name, 1, 4), SUM(salary) 
FROM   hr.employees 
WHERE  employee_id < 101 GROUP BY last_name
-- 替换后
SELECT SUBSTR(last_name, :"SYS_B_0", :"SYS_B_1"), SUM(salary) 
FROM   hr.employees 
WHERE  employee_id < :"SYS_B_2" GROUP BY last_name
  1. Search the shared pool for the same statement (same SQL hash value)
    If the same statement is not found, the database performs a hard parse. Otherwise, the database continues to the next step.
  2. Soft parsing of execution statements

As shown in the previous steps, setting the CURSOR_SHARING initialization parameter to FORCE does not decrease the resolve count. Conversely, FORCE enables the database to perform soft parsing instead of hard parsing under certain circumstances. Also, FORCE does not protect against SQL injection attacks because Oracle Database binds values ​​after any injection has occurred.

Example 20-11 Replacing literals with system bind variables
This example sets CURSOR_SHARING to FORCE at the session level, executes three statements containing literals, and displays the plan for each statement:

ALTER SESSION SET CURSOR_SHARING=FORCE;
SET LINESIZE 170
SET PAGESIZE 0
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 120;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());
SELECT SUM(salary) FROM hr.employees WHERE employee_id < 165;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR());

The following DISPLAY_CURSOR output, edited for readability, shows that all three statements use the same plan. The optimizer chose the index range scan plan because it looked at the first value (101) bound to the system bind variable and chose that plan as the best plan for all values. In fact, this plan is not the best plan for all values. When the value is 165, the full table scan is more efficient.

SQL_ID	cxx8n1cxr9khn, child number 0
-------------------------------------
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :"SYS_B_0"

Plan hash value: 2410354593

-------------------------------------------------------------------------------------
| Id  | Operation                           | Name       |Rows|Bytes|Cost(%CPU)|Time|
-------------------------------------------------------------------------------------
|  0 | SELECT STATEMENT                     |               |  |   |2 (100)|        |
|  1 |  SORT AGGREGATE                      |               |1 | 8 |       |        |
|  2 |   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES     |1 | 8 |2 (0)  |00:00:01|
|* 3 |    INDEX RANGE SCAN                  | EMP_EMP_ID_PK |1 |   |1 (0)  |00:00:01|
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("EMPLOYEE_ID"<101)

The query on V$SQLAREA confirms that Oracle Database replaced the text with the system bind variable: "SYS_B_0" and created a parent cursor and a child cursor (VERSION_COUNT=1) for all three statements, which means that all executions share the same plan of .

COL SQL_TEXT FORMAT a36
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT                             SQL_ID        VERSION_COUNT HASH_VALUE
------------------------------------ ------------- ------------- ----------
SELECT SUM(salary) FROM hr.employees cxx8n1cxr9khn             1  997509652
 WHERE employee_id < :"SYS_B_0"

20.3 Adaptive Cursor Sharing

Adaptive cursor sharing enables multiple execution plans for a single statement containing bind variables .

Cursor sharing is "adaptive" in that the cursor adjusts its behavior so the database doesn't always use the same plan for each execution or bind variable value.

20.3.1 Purpose of Adaptive Cursor Sharing

With bound viewing, the optimizer looks at the value of a user-defined bind variable the first time the cursor is called.

The optimizer determines the cardinality of any WHERE clause conditions as if literals were used instead of bind variables. However, if a column in the WHERE clause has skewed data, there may be a histogram on that column. When the optimizer looks at the values ​​of a user-defined bind variable and chooses a plan, that plan may not work for all values.

In adaptive cursor sharing, the database monitors data accessed over time for different bound values, ensuring that the best cursor is selected for a particular bound value. For example, the optimizer might choose one plan for a bound value of 10 and a different plan for a bound value of 50. Cursor sharing is "adaptive" in that the cursor adjusts its behavior so the optimizer does not always choose the same plan variable values ​​for each execution or bind. Therefore, the optimizer automatically detects when different executions of a statement would benefit from different execution plans.

Note: Adaptive cursor sharing is independent of the CURSOR_SHARING initialization parameter . Adaptive cursor sharing applies equally to statements containing user-defined and system-generated bind variables. Adaptive cursor sharing does not apply to statements containing only literals.

20.3.2 How Adaptive Cursor Sharing Works: Example

Adaptive cursor sharing monitors statements that use bind variables to determine whether the new plan is more efficient.

Suppose an application executes the following statement five times, each time binding a different value:

SELECT * FROM employees WHERE salary = :sal AND department_id = :dept

It is also assumed in this example that a histogram exists on at least one column in the predicate. The database processes this statement as follows:

  1. The first time the application issues a statement, resulting in a hard parse. During parsing, the database performs the following tasks:
    • Review bind variables to generate an initial plan.
    • Marks the cursor as binding sensitive. A bind-sensitive cursor is a cursor whose optimal plan may depend on the value of a bind variable. To determine whether different plans are beneficial, the database monitors the behavior of bind-sensitive cursors using different bind values.
    • Store metadata about the predicate, including the cardinality of the bound value (in this example, assume only 5 rows are returned).
    • Create an execution plan (in this case, index access) based on the viewed values.
  2. The database executes the cursor, storing bound values ​​and execution statistics in the cursor.
  3. The application issues the statement a second time, using a different bind variable, causing the database to perform a soft parse and find a matching cursor in the library cache.
  4. The database implements cursors.
  5. The database performs the following post-execution tasks:
    • The database compares the execution statistics of the second execution with the statistics of the first execution.
    • The database observes all previously executed statistical patterns before deciding whether to mark the cursor as binding-aware. In this example, assume that the database decides that the cursor is binding-aware.
  6. The application issues the statement a third time, using a different bind variable, which causes a soft parse. Because cursors are binding-aware, the database does the following:
    • Determines whether the base of the new value is in the same range as the stored base. In this example, the cardinality is similar: 8 rows instead of 5.
    • Reuse the execution plan in an existing child cursor.
  7. The database implements cursors.
  8. The application issues the statement a fourth time, using a different bind variable, causing a soft parse. Because cursors are binding-aware, the database does the following:
    • Determines whether the base of the new value is in the same range as the stored base. In this example, the cardinality is quite different: 102 rows (in a table with 107 rows) instead of 5 rows.
    • No matching child cursor found.
  9. The database performs hard parsing. As a result, the database does the following:
    • Create a new child cursor using the second execution plan (full table scan in this case)
    • Store metadata about the predicate in the cursor, including the cardinality of bound values
  10. The database executes the new cursor.
  11. The database stores the new bound values ​​and execution statistics in the new child cursor.
  12. The application issues the statement a fifth time, using a different bind variable, which causes a soft parse. Because cursors are binding-aware, the database does the following:
    • Determines whether the base of the new value is in the same range as the stored base. In this example, the base is 20.
    • No matching child cursor found.
  13. The database performs hard parsing. As a result, the database does the following:
    • Create a new child cursor using the third execution plan (index access in this case)
    • Make sure that this index access execution plan is the same as the index access execution plan used when the statement was first executed
    • Merges two subcursors containing index access plans, which involves storing the combined cardinality statistics into one subcursor and deleting the other
  14. The database accesses the execution plan using the index to execute the cursor.

20.3.3 Bind-Sensitive Cursors

A bind-sensitive cursor is a cursor whose optimal plan may depend on the value of a bind variable.

The database checks the bound value when calculating cardinality and considers the query "sensitive" to plan changes based on different bound values. The database monitors the behavior of bind-sensitive cursors that use different bind values ​​to determine whether different plans are beneficial.

The optimizer uses the following criteria to decide whether a cursor is binding-sensitive:

  • The optimizer has looked at bound values ​​to generate cardinality estimates.
  • Binding is used for equality or range predicates.

For each query executed with a new bound value, the database records the execution statistics for the new value and compares them to the execution statistics for the previous value. If the execution statistics vary widely, the database marks the cursor as bind-aware.

Example 20-12 Columns with significant data skew
This example assumes that the hr.employees.department_id column has significant data skew. SYSTEM executes the following setup code to add 100,000 employees in department 50 to the employee table in the example schema, for a total of 100,107 rows, and then collects table statistics:

DELETE FROM hr.employees WHERE employee_id > 999;

ALTER TABLE hr.employees DISABLE NOVALIDATE CONSTRAINT emp_email_uk;

DECLARE
v_counter NUMBER(7) := 1000;
BEGIN
 FOR i IN 1..100000 LOOP    
 INSERT INTO hr.employees 
   VALUES (v_counter, null, 'Doe', '[email protected]', null,'07-JUN-02',
     'AC_ACCOUNT', null, null, null, 50);
 v_counter := v_counter + 1;
 END LOOP;
END;
/
COMMIT; 

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS (ownname = 'hr',tabname => 'employees');
END;
/

ALTER SYSTEM FLUSH SHARED_POOL;

The following query displays a histogram for the employees.department_id column:

COL TABLE_NAME FORMAT a15
COL COLUMN_NAME FORMAT a20
COL HISTOGRAM FORMAT a9

SELECT TABLE_NAME, COLUMN_NAME, HISTOGRAM
FROM   DBA_TAB_COLS
WHERE  OWNER = 'HR'
AND    TABLE_NAME = 'EMPLOYEES'
AND    COLUMN_NAME = 'DEPARTMENT_ID';

TABLE_NAME      COLUMN_NAME          HISTOGRAM
--------------- -------------------- ---------
EMPLOYEES       DEPARTMENT_ID        FREQUENCY

Example 20-13 Low cardinality query

This example continues the example in Example 20-12. The following query shows that the value 10 has an extremely low cardinality for the column department_id, occupying .00099% of the rows:

VARIABLE dept_id NUMBER
EXEC :dept_id := 10;
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id;

  COUNT(*) MAX(EMPLOYEE_ID)
---------- ----------------
         1              200

The optimizer chooses an index range scan, as expected for such low-cardinality queries:

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID	a9upgaqqj7bn5, child number 0
-------------------------------------
select COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id

Plan hash value: 1642965905

-------------------------------------------------------------------------------------
| Id| Operation                           | Name       |Rows|Bytes|Cost (%CPU)|Time |
-------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                     |                   |  |  |2(100)|        |
| 1|  SORT AGGREGATE                      |                   |1 |8 |      |        |
| 2|   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES         |1 |8 |2  (0)|00:00:01|
|*3|    INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX |1 |  |1  (0)|00:00:01|
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("DEPARTMENT_ID"=:DEPT_ID)

The following V$SQL query obtains information about cursors:

COL BIND_AWARE FORMAT a10
COL SQL_TEXT FORMAT a22
COL CHILD# FORMAT 99999
COL EXEC FORMAT 9999
COL BUFF_GETS FORMAT 999999999
COL BIND_SENS FORMAT a9
COL SHARABLE FORMAT a9

SELECT SQL_TEXT, CHILD_NUMBER AS CHILD#, EXECUTIONS AS EXEC, 
       BUFFER_GETS AS BUFF_GETS, IS_BIND_SENSITIVE AS BIND_SENS, 
       IS_BIND_AWARE AS BIND_AWARE, IS_SHAREABLE AS SHARABLE
FROM   V$SQL
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT               CHILD#  EXEC  BUFF_GETS BIND_SENS BIND_AWARE SHARABLE
---------------------- ------ ----- ---------- --------- ---------- --------
SELECT COUNT(*), MAX(e      0     1        196         Y          N        Y
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

The preceding output shows a subcursor executed once for a low-cardinality query. A cursor is marked as bind-sensitive because the optimizer believes that the best plan may depend on the value of a bind variable.

When a cursor is marked as bind-sensitive, Oracle Database monitors the behavior of the cursor with different bind values ​​to determine whether different plans for different bind values ​​are more efficient. The database marks this cursor as binding sensitive because the optimizer uses the histogram on the department_id column to calculate the selectivity of the predicate WHERE department_id = :dept_id. Because the presence of a histogram indicates that the column is skewed, different values ​​of bind variables may require different plans.

Example 20-14 High cardinality query

This example continues the example in Example 20-13. The following code re-executes the same query with a value of 50, which occupies 99.9% of the rows:

EXEC :dept_id := 50;
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id;

  COUNT(*) MAX(EMPLOYEE_ID)
---------- ----------------
    100045	     100999

Although this non-selective query would be more efficient with a full table scan, the optimizer chooses the same index range scan used for department_id=10. This is because the database assumes that existing plans in the cursor can be shared:

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID	a9upgaqqj7bn5, child number 0
-------------------------------------
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id

Plan hash value: 1642965905

-------------------------------------------------------------------------------------
| Id| Operation                           | Name       |Rows|Bytes|Cost (%CPU)|Time |
-------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                     |                   |  |  |2(100)|        |
| 1|  SORT AGGREGATE                      |                   |1 |8 |      |        |
| 2|   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES         |1 |8 |2  (0)|00:00:01|
|*3|    INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX |1 |  |1  (0)|00:00:01|
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("DEPARTMENT_ID"=:DEPT_ID)

The V$SQL query shows that the subcursor has now been executed twice:

SELECT SQL_TEXT, CHILD_NUMBER AS CHILD#, EXECUTIONS AS EXEC, 
       BUFFER_GETS AS BUFF_GETS, IS_BIND_SENSITIVE AS BIND_SENS, 
       IS_BIND_AWARE AS BIND_AWARE, IS_SHAREABLE AS SHARABLE
FROM   V$SQL
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT               CHILD#  EXEC  BUFF_GETS BIND_SENS BIND_AWARE SHARABLE
---------------------- ------ ----- ---------- --------- ---------- --------
SELECT COUNT(*), MAX(e      0     2       1329         Y          N        Y
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

20.3.4 Bind-Aware Cursors

A binding-aware cursor is a binding-aware cursor that can use different plans for different binding values.

After making a cursor binding-aware, the optimizer chooses a plan for future executions based on the bound values ​​and their cardinality estimates. So "binding aware" essentially means "the best plan for the current bound values".

When executing a statement with a bind-aware cursor, the optimizer uses an internal algorithm to determine whether to mark the cursor as bind-aware. That decision depends on whether the cursor produces significantly different data access patterns for different bind values, resulting in a different performance cost than expected.

If the database marks the cursor as binding-aware, the next time the cursor executes the database will do the following:

  • Generate a new plan based on bound values
  • The raw cursor generated for the statement is marked as not shareable (V$SQL.IS_SHAREABLE is N). The original cursor is no longer available and is eligible to age from the library cache

When the same query is executed repeatedly with different bind values, the database adds and classifies the new bind values ​​in the SQL statement's "signature" (including optimizer environment, NLS settings, etc.). The database checks the bound value and considers whether the current bound value results in a significantly different data volume, or whether the existing plan is sufficient. The database does not need to create a new plan for each new value.

Consider a situation where you execute a statement with 12 different bound values ​​(two for each value), which causes the database to fire 5 hard parses and create 2 additional plans. Because the database performed 5 hard parses, it created 5 new child cursors, even though some cursors had the same execution plan as existing cursors. The database marks redundant cursors as unusable, which means that these cursors will eventually age out from the library cache.

During the initial hard parse, the optimizer is actually mapping the relationship between the bound value and the appropriate execution plan. After this initial phase, the database eventually reaches a steady state. Executing with the new bound value causes the best child cursor to be chosen in the cache without hard parsing. Therefore, the number of resolutions does not vary with the number of different bound values.

Example 20-15 Binding-Aware Cursors
This example continues the example in "Binding-Aware Cursors". The following code issues a second query employees with the bind variable set to 50:

EXEC :dept_id := 50;
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id;

  COUNT(*) MAX(EMPLOYEE_ID)
---------- ----------------
    100045           100999

During the first two executions, the database was monitoring the query's behavior and determined that different bind values ​​caused the query to differ significantly in cardinality. Based on this difference, the database adjusts its behavior so that the same plan is not always shared for that query. Therefore, the optimizer generates a new plan based on the current bound value, which is 50:

SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID	a9upgaqqj7bn5, child number 1
-------------------------------------
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id

Plan hash value: 1756381138

---------------------------------------------------------------------------
| Id  | Operation         | Name      | Rows  | Bytes | Cost(%CPU)| Time  |
---------------------------------------------------------------------------
|  0 | SELECT STATEMENT   |           |       |      |254 (100)|          |
|  1 |  SORT AGGREGATE    |           |     1 |    8 |         |          |
|* 2 |   TABLE ACCESS FULL| EMPLOYEES |  100K | 781K |254  (15)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("DEPARTMENT_ID"=:DEPT_ID)

The following V$SQL query obtains information about cursors:

SELECT SQL_TEXT, CHILD_NUMBER AS CHILD#, EXECUTIONS AS EXEC, 
       BUFFER_GETS AS BUFF_GETS, IS_BIND_SENSITIVE AS BIND_SENS, 
       IS_BIND_AWARE AS BIND_AWARE, IS_SHAREABLE AS SHAREABLE
FROM   V$SQL
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT               CHILD#  EXEC  BUFF_GETS BIND_SENS BIND_AWARE SHAREABLE
---------------------- ------ ----- ---------- --------- ---------- ---------
SELECT COUNT(*), MAX(e      0     2       1329         Y          N         N
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

SELECT COUNT(*), MAX(e      1     1        800         Y          Y         Y
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

The preceding output shows that the database created an additional child cursor (CHILD# of 1). Cursor 0 is now marked as not shareable. Cursor 1 shows many buffers below cursor 0 and marked as bind-sensitive and bind-aware. Binding-aware cursors can use different plans for different bind values, depending on the selectivity of the predicates containing the bind variables.

Example 20-16 Binding-Aware Cursor: Choosing the Best Plan

This example continues the example in "Example 20-15". The following code executes the same employees query with a value of 10 and very low cardinality (only one row):

EXEC :dept_id := 10;
SELECT COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id;

  COUNT(*) MAX(EMPLOYEE_ID)
---------- ----------------
         1              200

The following output shows that the optimizer chose the best plan, an index scan, based on a low cardinality estimate of the current bound value of 10:

SQL> SELECT * from TABLE(DBMS_XPLAN.DISPLAY_CURSOR);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID	a9upgaqqj7bn5, child number 2
-------------------------------------
select COUNT(*), MAX(employee_id) FROM hr.employees WHERE department_id = :dept_id

Plan hash value: 1642965905

-------------------------------------------------------------------------------------
| Id| Operation                           | Name       |Rows|Bytes|Cost (%CPU)|Time |
-------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                     |                   |  |  |2(100)|        |
| 1|  SORT AGGREGATE                      |                   |1 |8 |      |        |
| 2|   TABLE ACCESS BY INDEX ROWID BATCHED| EMPLOYEES         |1 |8 |2  (0)|00:00:01|
|*3|    INDEX RANGE SCAN                  | EMP_DEPARTMENT_IX | 1|  |1  (0)|00:00:01|
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("DEPARTMENT_ID"=:DEPT_ID)

The V$SQL output now shows that there are three child cursors:

SELECT SQL_TEXT, CHILD_NUMBER AS CHILD#, EXECUTIONS AS EXEC, 
       BUFFER_GETS AS BUFF_GETS, IS_BIND_SENSITIVE AS BIND_SENS, 
       IS_BIND_AWARE AS BIND_AWARE, IS_SHAREABLE AS SHAREABLE
FROM   V$SQL
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT               CHILD#  EXEC  BUFF_GETS BIND_SENS BIND_AWARE SHAREABLE
---------------------- ------ ----- ---------- --------- ---------- ---------
SELECT COUNT(*), MAX(e      0     2       1329         Y          N         N
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

SELECT COUNT(*), MAX(e      1     1        800         Y          Y         Y
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

SELECT COUNT(*), MAX(e      2     1          3         Y          Y         Y
mployee_id) FROM hr.em
ployees WHERE departme
nt_id = :dept_id

When the cursor is switched to binding-aware mode, the database discards the original cursor (CHILD# of 0). This is a one-time expense. The database marks cursor 0 as not shareable (SHAREABLE is N), which means that this cursor is not available and will be the first to age out of the cursor cache.

20.3.5 Cursor Merging

If the optimizer creates a plan for a binding-aware cursor, and if that plan is the same as an existing cursor, the optimizer can perform a cursor merge.

In this case, the database merges the cursors to save space in the library cache. The database increases the selectivity range of the cursor to include the selectivity of the new bound value.

When a query uses a new bind variable, the optimizer tries to find a cursor it deems appropriate based on the selective similarity of the bind value. If the database cannot find such a cursor, a new one is created. If the plan of the new cursor is the same as one of the existing cursors, the database merges the two cursors to save space in the library cache. The merge caused the database to mark a cursor as not shareable. If the library cache is under space pressure, the database ages out unshared cursors first.

20.3.6 Adaptive Cursor Sharing Views

You can use the V$ view for adaptive cursor sharing to view selectivity ranges, cursor information (such as whether the cursor is binding-aware or binding-aware), and execution statistics.
Specifically, use the following views:

  • V$SQL shows whether the cursor is binding-sensitive or binding-aware.
  • V$SQL_CS_HISTOGRAM shows the distribution of execution counts in a three-bucket execution history histogram.
  • If selectivity is used to check cursor sharing, V$SQL_CS_SELECTIVITY shows the selectivity scope stored for each predicate that includes bind variables. It contains the text of the predicate, and the low and high values ​​of the selectivity range.
  • V$SQL_CS_STATISTICS summarizes information used by the optimizer to determine whether to mark a cursor as binding-aware. For the execution example, the database keeps track of rows processed, buffer fetches, and CPU time. The PEEKED column displays YES when the cursor is built using the binding set; otherwise, the value is NO.

20.4 Real-World Performance Guidelines for Cursor Sharing

The Real-World Performance team has developed guidance on how to optimize cursor sharing in Oracle Database applications.

20.4.1 Develop Applications with Bind Variables for Security and Performance

The Real-World Performance Group strongly recommends that all enterprise applications use bind variables .

Oracle Database applications are designed to be written using bind variables. Avoid application designs that cause large numbers of users to issue dynamic, unshared SQL statements .

Whenever Oracle Database cannot find a match for a statement in the library cache, it must perform a hard parse. Despite the dangers of developing applications using literals, not all real-world applications use bind variables. Developers sometimes find it faster and easier to write programs that use literals . However, reduced development time does not lead to better performance and security after deployment .

The main benefits of using bind variables are as follows:

  • Resource Efficiency
    Compiling a program before each execution does not use resources efficiently, but that's actually what Oracle Database does when it performs a hard parse. The database server must consume a lot of CPU and memory to create cursors, generate and evaluate execution plans, and so on. By making the database share cursors, soft parsing consumes far fewer resources. If the application uses literals instead of bind variables and executes only a few queries per day, the DBA may not see the extra overhead as a performance problem. However, if the application executes hundreds or thousands of queries per second, the additional resource overhead can easily reduce performance to unacceptable levels. Using bind variables causes the database to perform the hard parse only once, regardless of how many times the statement is executed .

  • Scalability
    When the database performs a hard parse, the database spends more time acquiring and holding latches in the shared pool and library cache. Latches are low-level serialized devices. The longer and more frequently the database latches structures in shared memory, the longer the queue for those latches. When multiple statements share the same execution plan, requests for latches and the duration of latches decrease. This behavior increases scalability.

  • Throughput and Response Time
    When the database avoids constantly reparsing and creating cursors, more of its time is spent in user space. The Real-World Performance team found that changing the literals to use bindings often resulted in orders of magnitude improvements in throughput and user response time.

  • Security
    The only way to prevent SQL injection attacks is to use bind variables. Malicious users can exploit connection string applications by "injecting" code into the application.

20.4.2 Do Not Use CURSOR_SHARING = FORCE as a Permanent Fix

Best practice is to write shareable SQL and use the default EXACT for CURSOR_SHARING.

However, for applications with many similar statements, setting CURSOR_SHARING to FORCE can sometimes significantly improve cursor sharing. Replacing literals with system-generated bound values ​​reduces memory usage, speeds up parsing, and reduces latch contention. However, FORCE is not a permanent development solution.

As a general guideline, the Real-World Performance Group recommends against setting CURSOR_SHARING to a FORCE exception in rare cases, and then only if all of the following conditions are true:

  • Statements in the shared pool differ only in literal values.
  • Response times are not optimal due to high number of library cache misses.
  • You have serious security and scalability bugs in your existing code - missing bind variables - and you need a temporary Band-Aid until the source code is fixed.
  • You set this initialization parameter at the session level, not the instance level.

Setting CURSOR_SHARING to FORCE has the following disadvantages:

  • It indicates that the application is not using user-defined bind variables, which means it is open to SQL injection. Setting CURSOR_SHARING to FORCE will not fix SQL injection errors or make the code more secure. Only after any malicious SQL text is injected will the database bind the values.
  • The database has to do extra work during soft parsing to find similar statements in the shared pool.
  • The database deletes every word, which means it can delete useful information. For example, the database strips literal values ​​in the SUBSTR and TO_DATE functions. Using system-generated bind variables where the literals are more optimized can negatively affect the execution plan.
  • The maximum length (returned by DESCRIBE) of any selected expression containing literals in a SELECT statement has been increased. However, the actual length of the returned data has not changed.
  • Star transformations are not supported.

20.4.3 Establish Coding Conventions to Increase Cursor Reuse

By default, any change in the text of the two SQL statements prevents the database from sharing cursors , including the names of bind variables. Also, changes in the size of bind variables can cause cursor mismatches. Therefore, the use of bind variables in application code is not sufficient to guarantee cursor sharing.

The Real-World Performance Team recommends that you standardize spacing and capitalization conventions for SQL statements and PL/SQL blocks . Also establish conventions for the naming and definition of bind variables . If the database is not sharing cursors as expected, start the diagnosis by querying V$SQL_SHARED_CURSOR.

Example 20-17 Changes to SQL text

In this example, an application that uses bind variables executes seven statements with the same bind variable value, but the statements are not textually identical:

VARIABLE emp_id NUMBER
EXEC :emp_id := 101;

SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :EMP_ID;
SELECT SUM(salary) FROM hr.employees WHERE employee_id < :Emp_Id;
SELECT SUM(salary)  FROM hr.employees WHERE employee_id < :emp_id;
select sum(salary) from hr.employees where employee_id < :emp_id;
Select sum(salary) From hr.employees Where employee_id < :emp_id;
Select sum(salary) From hr.employees Where employee_id< :emp_id;

A query of V$SQLAREA shows that no cursor sharing occurs:

COL SQL_TEXT FORMAT a35
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT                            SQL_ID        VERSION_COUNT HASH_VALUE
----------------------------------- ------------- ------------- ----------
SELECT SUM(salary) FROM hr.employee bkrfu3ggu5315             1 3751971877
s WHERE employee_id < :EMP_ID
SELECT SUM(salary) FROM hr.employee 70mdtwh7xj9gv             1  265856507
s WHERE employee_id < :Emp_Id
Select sum(salary) From hr.employee 18tt4ny9u5wkt             1 2476929625
s Where employee_id< :emp_id
SELECT SUM(salary)  FROM hr.employe b6b21tbyaf8aq             1 4238811478
es WHERE employee_id < :emp_id
SELECT SUM(salary) FROM hr.employee 4318cbskba8yh             1  615850960
s WHERE employee_id < :emp_id
select sum(salary) from hr.employee 633zpx3xm71kj             1 4214457937
s where employee_id < :emp_id
Select sum(salary) From hr.employee 1mqbbbnsrrw08             1  830205960
s Where employee_id < :emp_id

7 rows selected.

Example 20-18 Binding Length Mismatch

The following code defines a bind variable with a different length (20 bytes vs. 100 bytes), and then executes a statement with the same text using the same bind value:

VARIABLE lname VARCHAR2(20)
EXEC :lname := 'Taylor';
SELECT SUM(salary) FROM hr.employees WHERE last_name = :lname;
VARIABLE lname VARCHAR2(100)
EXEC :lname := 'Taylor';
SELECT SUM(salary) FROM hr.employees WHERE last_name = :lname;

The following query shows that the database has no shared cursors:

COL SQL_TEXT FORMAT a35
SELECT SQL_TEXT, SQL_ID, VERSION_COUNT, HASH_VALUE
FROM   V$SQLAREA
WHERE  SQL_TEXT LIKE '%mployee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%';

SQL_TEXT                            SQL_ID	       VERSION_COUNT HASH_VALUE
----------------------------------- ------------- ------------- ----------
SELECT SUM(salary) FROM hr.employee buh8j4557r0h1             2 1249608193
s WHERE last_name = :lname

The reason is because of the binding length:

COL BIND_LENGTH_UPGRADEABLE FORMAT a15
SELECT s.SQL_TEXT, s.CHILD_NUMBER, 
       c.BIND_LENGTH_UPGRADEABLE
FROM   V$SQL s, V$SQL_SHARED_CURSOR c
WHERE  SQL_TEXT LIKE '%employee%'
AND    SQL_TEXT NOT LIKE '%SQL_TEXT%'
AND    s.CHILD_ADDRESS = c.CHILD_ADDRESS;

SQL_TEXT                            CHILD_NUMBER BIND_LENGTH_UPG
----------------------------------- ------------ ---------------
SELECT SUM(salary) FROM hr.employee            0 N
s WHERE last_name = :lname
SELECT SUM(salary) FROM hr.employee            1 Y
s WHERE last_name = :lname

20.4.4 Minimize Session-Level Changes to the Optimizer Environment

It is a best practice to prevent users of your application from changing the optimization method and goals for their individual sessions. Any changes to the optimizer environment can prevent identical statements from sharing cursors.

Example 20-19 Environment Mismatch

This example shows two statements with the same text, but they do not share a cursor:

VARIABLE emp_id NUMBER

EXEC :emp_id := 110;

ALTER SESSION SET OPTIMIZER_MODE = FIRST_ROWS;
SELECT salary FROM hr.employees WHERE employee_id < :emp_id;
ALTER SESSION SET OPTIMIZER_MODE = ALL_ROWS;
SELECT salary FROM hr.employees WHERE employee_id < :emp_id;

The query for V$SQL_SHARED_CURSOR shows an optimizer mode mismatch:

在这里插入代码片

Guess you like

Origin blog.csdn.net/stevensxiao/article/details/125263221