文章目录
1、概念
1.1 什么是绑定变量
sql 语句的执行要经过
"解析、执行、提取"
等几个阶段,其中解析最消耗资源,解析的过程中要进行语法、语义和权限的检查,如果这些检查都通过了,才开始执行,执行完成之后将 sql 语句的执行计划(v$sql
)存储在共享池中,如果下一次有相同的 sql 语句要执行,则不需要解析,直接按照已经存在的执行计划执行,就可以节省资源。
绑定变量:
(SQL语句 执行 时
)将条件谓语(不能是表名等、只能是 where 后的部分)
中不同的值保存在一个中间变量中,Oracle 对用户每次发起的 sql 语句作hash 运算
时,都产生相同的值,使用相同的执行计划,作为一个 sql 语句来执行。- 以下两点仅为个人理解
- 绑定变量,常用于 dml 操作时(insert、update、delete)
- 其余情况(如:procedure、declare … end、function 中)绑定变量 和 普通变量 的执行无区别(可通过
select * from v$sql t where t.sql_fulltext like '%查询部分%'
查看)
例如:
A 用户:SELECT * FROM t WHERE t.id = 1;
B 用户:SELECT * FROM t where t.id = 2;
绑定变量:
SELECT * FROM t WHERE t.ID = :b1;
-- 个人理解部分
ALTER system flush shared_pool;
DECLARE
t NUMBER;
t1 NUMBER;
v_1 NUMBER;
BEGIN
t := dbms_utility.get_time;
FOR i IN 1 .. 10000 LOOP
SELECT COUNT(1) INTO v_1 FROM stu t WHERE t.s_id = i;
END LOOP;
t1 := dbms_utility.get_time;
dbms_output.put_line(t1 - t);
END;
/
SELECT * FROM v$sql t WHERE t.SQL_FULLTEXT LIKE '%SELECT COUNT(1) INTO v_1%';
运行结果:
1.2 常用相关表
1.2.1 清空共享池(缓存)
ALTER system flush shared_pool; -- 请勿在 生产库 执行
1.2.2 查询是否在共享池(缓存)
SELECT * FROM V$sql t WHERE t.SQL_FULLTEXT LIKE '%你执行过的sql语句%';
1.3 数据准备
DROP TABLE stu; -- if exists
DROP TABLE stu1; -- if exists
DROP TABLE stu2; -- if exists
CREATE TABLE stu (
s_id NUMBER,
s_xm VARCHAR(30)
);
CREATE TABLE stu1 AS SELECT * FROM stu WHERE 1 = 2;
CREATE TABLE stu2 AS SELECT * FROM stu WHERE 1 = 2;
2、实例分析
2.1 经典例子(dml变量时)
DECLARE
t NUMBER;
t1 NUMBER;
BEGIN
t := dbms_utility.get_time;
FOR i IN 1 .. 100000 LOOP
EXECUTE IMMEDIATE 'INSERT INTO stu(s_id) VALUES('||i||')';
END LOOP;
t1 := dbms_utility.get_time;
dbms_output.put_line(t1 - t);
t := dbms_utility.get_time;
FOR i IN 1 .. 100000 LOOP
EXECUTE IMMEDIATE 'INSERT INTO stu(s_id) VALUES(:b1)' USING i;
END LOOP;
t1 := dbms_utility.get_time;
dbms_output.put_line(t1 - t);
END;
/
-------------
-- 查询 绑定变量 时,共享池中的数据
SELECT t.HASH_VALUE,t.* FROM v$sql t WHERE t.SQL_FULLTEXT LIKE '%INSERT INTO stu(s_id) VALUES(:b1)%';
-- 查询 非绑定变量 时,共享池中的数据
SELECT t.HASH_VALUE,t.* FROM v$sql t WHERE t.SQL_FULLTEXT LIKE '%INSERT INTO stu(s_id) VALUES(%';
运行结果:
特别注意:
- declare 一直到 end; 仅有一条执行计划
- 绑定变量的 insert 也仅有一条执行计划
- 非绑定变量的 insert 有 10,0000 条执行计划
2.2 易混淆例子(参数变量时)
TRUNCATE TABLE stu;
DECLARE
BEGIN
FOR I IN 1 .. 10000 LOOP
EXECUTE IMMEDIATE 'INSERT INTO stu(s_id, s_xm) VALUES(:b1, ''a'')' using i;
END LOOP;
COMMIT;
END;
ALTER system flush shared_pool; -- 请勿在 生产库 执行
----------------- 以上为数据准备 -----------------
CREATE OR REPLACE PROCEDURE p_update_sql1(p_stu IN system.stu%ROWTYPE,
p_flag OUT VARCHAR) AS
v_count NUMBER;
BEGIN
p_flag := '0';
SELECT COUNT(1)
INTO v_count
FROM stu t
WHERE t.s_id = p_stu.s_id;
IF v_count >= 1 THEN
UPDATE stu t
SET t.s_xm = 'a'
WHERE t.s_id = p_stu.s_id;
END IF;
EXCEPTION
WHEN OTHERS THEN
p_flag := '1';
END p_update_sql1;
/
CREATE OR REPLACE PROCEDURE p_update_sql2(p_stu IN system.stu%ROWTYPE,
p_flag OUT VARCHAR) AS
v_count NUMBER;
BEGIN
p_flag := '0';
SELECT COUNT(1)
INTO v_count
FROM stu t
WHERE t.s_id = p_stu.s_id;
IF v_count >= 1 THEN
EXECUTE IMMEDIATE 'UPDATE stu t
SET t.s_xm = ''b''
WHERE t.s_id = :b1'
USING p_stu.s_id;
END IF;
EXCEPTION
WHEN OTHERS THEN
p_flag := '1';
END p_update_sql2;
/
DECLARE
t NUMBER;
t1 NUMBER;
p_flag VARCHAR2(3);
CURSOR cur_stu IS
SELECT *
FROM stu;
v_stu cur_stu%ROWTYPE;
BEGIN
OPEN cur_stu;
t := dbms_utility.get_time;
LOOP
FETCH cur_stu
INTO v_stu;
EXIT WHEN cur_stu%NOTFOUND;
p_update_sql1(v_stu, p_flag);
END LOOP;
t1 := dbms_utility.get_time;
dbms_output.put_line(t1 - t);
CLOSE cur_stu;
OPEN cur_stu;
t := dbms_utility.get_time;
LOOP
FETCH cur_stu
INTO v_stu;
EXIT WHEN cur_stu%NOTFOUND;
p_update_sql2(v_stu, p_flag);
END LOOP;
t1 := dbms_utility.get_time;
dbms_output.put_line(t1 - t);
CLOSE cur_stu;
EXCEPTION
WHEN OTHERS THEN
IF cur_stu%ISOPEN THEN
CLOSE cur_stu;
END IF;
END;
/
运行结果: