绑定变量对于Oracle数据库应用程序中的游标共享至关重要。
本节包含以下主题:
20.1.3.1文字和游标
在构造SQL语句时,某些Oracle应用程序使用文字而不是绑定变量。
例如,该语句SELECT SUM(salary) FROM hr.employees WHERE employee_id < 101 使用文字值101作为员工ID。默认情况下,当类似的语句不使用绑定变量时,Oracle数据库将无法利用游标共享。因此,Oracle数据库将除value 102或任何其他随机值之外相同的语句视为全新的语句,需要进行硬解析。
真实世界性能小组确定使用文字的应用程序经常是导致性能,可伸缩性和安全性问题的原因。在现实世界中,在不考虑游标共享的情况下快速编写应用程序的情况并不少见。一个经典的示例是“屏幕抓取”应用程序,该应用程序将内容从Web表单中复制出来,然后连接字符串以动态构造SQL语句。
使用文字值导致的主要问题包括:
- 连接最终用户输入的文字的应用程序容易受到SQL注入攻击。仅重写应用程序以使用绑定变量可以消除这种威胁。
- 如果每个语句都经过硬解析,则不会共享游标,因此数据库必须消耗更多内存才能创建游标。
- 硬解析时,Oracle数据库必须锁存共享池和库缓存。随着硬解析的数量增加,等待锁存共享池的进程数量也随之增加。这种情况会降低并发性并增加争用。
视频:
示例20-6文字和游标共享
考虑执行以下语句的应用程序,这些语句仅在文字上有所不同:
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;以下查询V$SQLAREA显示,这三个语句需要三个不同的父游标。如所示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
也可以看看:
“ 不要使用CURSOR_SHARING = FORCE作为永久修复程序 ”以了解SQL注入
20.1.3.2绑定变量和游标
您可以开发Oracle应用程序以使用绑定变量而不是文字。
绑定变量是在查询中的占位符。例如,该语句SELECT SUM(salary) FROM hr.employees WHERE employee_id < :emp_id将绑定变量:emp_id用于员工ID。
真实世界性能小组发现,使用绑定变量的应用程序性能更好,伸缩性更好,更安全。
使用绑定变量所带来的主要好处包括:
- 使用绑定变量的应用程序不像使用文字的应用程序一样容易受到相同的SQL注入攻击。
- 当相同的语句使用绑定变量时,当将不同的值绑定到同一条语句时,Oracle数据库可以利用游标共享,并共享计划和其他信息。
- Oracle数据库避免了闩锁硬解析所需的共享池和库缓存的开销。
视频:
示例20-7绑定变量和共享游标
下面的示例使用VARIABLE在SQL * Plus命令创建emp_id绑定变量,然后执行使用三种不同的绑定值的查询(101,120,和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;
以下查询V$SQLAREA显示了一个唯一的SQL语句:
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
VERSION_COUNT的值1指示数据库重用了相同的子游标,而不是创建三个单独的子游标。使用绑定变量使这种重用成为可能。
20.1.3.3绑定变量 偷看
在绑定变量窥视(也称为bind peeking)中,当数据库执行语句的硬解析时,优化器查看绑定变量中的值。
优化器不会在每次解析之前查看绑定变量值。而是,优化器仅在硬解析期间首次调用优化器时才进行窥视。
当查询使用文字时,优化器可以使用文字值来找到最佳计划。但是,当查询使用绑定变量时,优化器必须选择最佳计划,而SQL文本中不存在文字。此任务可能非常困难。通过在初始硬解析期间查看绑定值,优化器可以确定WHERE子句条件的基数,就好像使用了文字一样,从而改进了计划。
因为优化器仅在硬解析期间查看绑定值,所以该计划可能并非对所有可能的绑定值都是最佳的。以下示例说明了这一原理。
示例20-8文字导致不同的执行计划
假定您执行以下语句,其执行使用不同文字三种不同的语句(101,120,和165),然后显示每个执行计划:
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());数据库对这三个不同的语句进行了硬解析。DISPLAY_CURSOR为了清楚起见,对输出进行了编辑,该输出表明优化器为前两个语句选择了相同的索引范围扫描计划,但为使用文字的语句选择了全表扫描计划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)
前面的输出表明,对于返回更多行的查询,优化器认为全表扫描比索引扫描更有效率。
示例20-9绑定变量导致游标重用
本示例重写在示例20-8中执行的查询,以使用绑定变量代替文字。您将相同的值(101,120和165)绑定到bind变量:emp_id,然后显示每个的执行计划:
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());该DISPLAY_CURSOR输出显示优化程序选择恰好为所有三个语句同样的计划:
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)相反,当使用文字执行前面的语句时,当雇员ID值为时,优化器选择了低成本的全表扫描165。这是通过自适应游标共享解决的问题。




