Oracle AskTOM 绑定变量深度解析
Oracle AskTOM 绑定变量深度解析
来源:AskTOM (asktom.oracle.com) 适用版本:Oracle Database 全版本 文档版本:v1.0 / 2026-07-22
1. 概述
绑定变量是 Tom Kyte 在 AskTOM 反复强调的核心主题[1]。
详细见:Oracle 绑定变量窥探详解。
2. 什么是绑定变量
2.1 字面量 SQL
SELECT * FROM emp WHERE id = 1;
SELECT * FROM emp WHERE id = 2;
SELECT * FROM emp WHERE id = 3;
2.2 绑定变量 SQL
SELECT * FROM emp WHERE id = :1;
2.3 区别
- 字面量:每次都不同 → 硬解析
- 绑定变量:相同 → 软解析
3. 解析机制
3.1 硬解析
1. 语法检查
2. 语义检查
3. 优化器生成执行计划
4. 编译
5. 缓存到 Library Cache
3.2 软解析
1. 计算 hash
2. 查找 Library Cache
3. 找到 → 复用
4. 执行
3.3 性能差距
- 硬解析:CPU 密集
- 软解析:轻量
- 差距:100-1000 倍
4. Tom Kyte 经典示例
4.1 未使用绑定变量
CREATE OR REPLACE PROCEDURE bad_example IS
v_cnt NUMBER;
BEGIN
FOR i IN 1..10000 LOOP
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE id=' || i
INTO v_cnt;
END LOOP;
END;
/
4.2 使用绑定变量
CREATE OR REPLACE PROCEDURE good_example IS
v_cnt NUMBER;
BEGIN
FOR i IN 1..10000 LOOP
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM emp WHERE id=:1'
INTO v_cnt USING i;
END LOOP;
END;
/
4.3 性能对比
- bad_example: 10+ 秒
- good_example: 0.5 秒
- 差距 20 倍
5. Library Cache 影响
5.1 未使用绑定变量
- Library Cache 充满
- 大量子游标
- 共享池压力
- ORA-04031 风险
5.2 使用绑定变量
- 一个父游标 + 子游标
- 共享池高效
- 性能稳定
5.3 查看解析
SELECT sql_text, executions, parse_calls
FROM v$sql
WHERE sql_text LIKE 'SELECT COUNT(*) FROM emp%'
ORDER BY sql_text;
6. 何时使用绑定变量
6.1 OLTP
- 必须使用
- 高并发
- 性能关键
6.2 数据仓库
- 不一定
- 全表扫描多
- 字面量更好(CBO 信息更准)
6.3 报表
- 评估
- 长查询
- 字面量可接受
7. 绑定变量窥探
7.1 11g 之前
- 第一次窥探
- 后续复用
- 数据倾斜时问题
7.2 11g ACS
- Adaptive Cursor Sharing
- 自适应游标
- 多个执行计划
详细见:Oracle 自适应游标共享详解。
8. 强制绑定变量
8.1 CURSOR_SHARING
ALTER SYSTEM SET cursor_sharing=FORCE;
8.2 注意
- 临时方案
- 不如代码改
- 有副作用
- 监控
8.3 EXACT vs FORCE
- EXACT:默认,字面量
- FORCE:自动替换
- SIMILAR:11g 弃用
9. 监控
9.1 解析率
SELECT name, value
FROM v$sysstat
WHERE name IN ('parse count (hard)', 'parse count (total)');
-- hard/total < 5% 为佳
9.2 字面量 SQL
SELECT sql_text, executions
FROM v$sql
WHERE parse_calls > executions * 0.9
AND executions < 5
ORDER BY sql_text;
9.3 共享池
SELECT * FROM v$sgastat WHERE pool='shared pool' AND name='free memory';
10. 绑定变量捕获
10.1 查看绑定
SELECT sql_id, name, value_string
FROM v$sql_bind_capture
WHERE sql_id = 'abc123';
10.2 历史
SELECT * FROM dba_hist_sqlbind WHERE sql_id = 'abc123';
11. Java/JDBC
11.1 PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM emp WHERE id=?");
ps.setInt(1, 100);
ResultSet rs = ps.executeQuery();
11.2 错误
// 拼接字符串 → 字面量
String sql = "SELECT * FROM emp WHERE id=" + id;
Statement s = conn.createStatement();
12. Python
12.1 oracledb
import oracledb
cursor = conn.cursor()
cursor.execute("SELECT * FROM emp WHERE id=:1", (100,))
12.2 错误
# 拼接 → SQL 注入 + 字面量
cursor.execute(f"SELECT * FROM emp WHERE id={id}")
13. 常见问题
13.1 数据倾斜
- 绑定变量窥探
- 执行计划不准
- ACS
13.2 字面量更好场景
- 数据仓库
- 全表扫描决策
- 评估
13.3 ORA-04031
- 字面量过多
- 共享池耗尽
- 改绑定变量
14. Tom Kyte 名言
"Bind variables are not optional, they are mandatory"
"If you are not using bind variables, you are broken"
15. 最佳实践
- OLTP:必用
- 应用代码:PreparedStatement
- 监控:解析率
- CURSOR_SHARING:临时
- ACS:数据倾斜
- 测试:性能
- 代码审查:禁止拼接
- 文档:规范
- 培训:开发
- 优化:持续
16. 参考资料
[1] AskTOM, “Bind Variables”, https://asktom.oracle.com [2] Tom Kyte, “Expert Oracle Database Architecture”, Chapter 5