Oracle 绑定变量和普通变量效率详解

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;
/

运行结果:
在这里插入图片描述

发布了43 篇原创文章 · 获赞 32 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_34745941/article/details/86603445
今日推荐