说到软解析(soft prase)和硬解析(hard prase),就不能不说一下Oracle对sql的处理过程。当你发出一条sql语句交付Oracle,在执行和获取结果前,Oracle对此sql将进行几个步骤的处理过程:
1、语法检查(syntax check)
检查此sql的拼写是否语法。
2、语义检查(semantic check)
诸如检查sql语句中的访问对象是否存在及该用户是否具备相应的权限。
3、对sql语句进行解析(prase)
利用内部算法对sql进行解析,生成解析树(parse tree)及执行计划(execution plan)。
4、执行sql,返回结果(execute and return)
其中,软、硬解析就发生在第三个过程里。
Oracle利用内部的hash算法来取得该sql的hash值,然后在library cache里查找是否存在该hash值;
假设存在,则将此sql与cache中的进行比较;
假设"相同",就将利用已有的解析树与执行计划,而省略了优化器的相关工作。这也就是软解析的过程。
诚然,如果上面的2个假设中任有一个不成立,那么优化器都将进行创建解析树、生成执行计划的动作。这个过程就叫硬解析。
创建解析树、生成执行计划对于sql的执行来说是开销昂贵的动作,所以,应当极力避免硬解析,尽量使用软解析。
这就是在很多项目中,倡导开发设计人员对功能相同的代码要努力保持代码的一致性,以及要在程序中多使用绑定变量的原因。
有如下两句查询语句:
1.SELECT * FROM EMP WHERE EMPNO = 123;
2.SELECT * FROM EMP WHERE EMPNO = :EMP_NO;
1句中查询员工编号是123的员工信息,ORACLE第一次经过分析编译后执行。但如果下次还要再查询编号为456和789的员工信息时,ORACLE将会再将这句SQL分析编译,然后再执行。
再看2句,首先定义变量EMP_NO,我们将123赋给变量,第一次的时候也是经过分析编译后再执行,但是到了接下来再想查询其他员工编号的信息时,ORACLE会将第一次编译后的查询方案(在第一次编译执行之后已经储存在共享池中)用来进行下一次的查询。
这就像JAVA,想想看,如果你用JAVA写了一个软件,给客户的是你写的JAVA代码,客户在每次使用的时候都耀编译代码,然后执行。这将会影响多大啊。
所以说,分析一个带有硬编码变量的语句(称为硬分析)要明显的比重用一个已经分析过的查询方案(软分析)要花费更长的时间和耗费更多的资源。
如果使用绑定变量,提交引用相同变量的完全相同的查询的人将会使用共享池中的编译方案,只需编译子例程一次,就可以重复使用。这样不仅可以使用较少的时间,而且可以减少锁存时间,降低锁存频率。这将会提高软件性能,大大提高可伸缩性。
下面的试验将更能说明这个道理:
ALTER SYSTEM FLUSH SHARED_POOL;
SET SERVEROUTPUT ON;
SET TIMING ON;
DECLARE
TYPE rc IS REF CURSOR;
l_rc rc;
l_dummy all_objects.object_name%TYPE;
l_start NUMBER DEFAULT dbms_utility.get_time;
BEGIN
FOR i IN 1 .. 1000 LOOP
OPEN l_rc FOR 'select object_name from all_objects where object_id = '||i;
FETCH l_rc INTO l_dummy;
CLOSE l_rc;
END LOOP;
dbms_output.put_line(round((dbms_utility.get_time-l_start)/100,2)|| 'seconds ...');
END;
PL/SQL 过程已成功完成。
已用时间: 00: 00: 53.05
上述代码使用动态SQL从ALL_OBJECTS表中查询单行。它用值1,2,3.......1000等硬编码产生1000条不同的查询进入WHERE子句,看看运行时间53秒。
再看下面代码:
ALTER SYSTEM FLUSH SHARED_POOL;
SET SERVEROUTPUT ON;
SET TIMING ON;
DECLARE
TYPE rc IS REF CURSOR;
l_rc rc;
l_dummy all_objects.object_name%TYPE;
l_start NUMBER DEFAULT dbms_utility.get_time;
BEGIN
FOR i IN 1 .. 1000 LOOP
OPEN l_rc FOR 'select object_name from all_objects where object_id = :x' USING i;
FETCH l_rc INTO l_dummy;
CLOSE l_rc;
END LOOP;
dbms_output.put_line(round((dbms_utility.get_time-l_start)/100,2)|| 'seconds ...');
END;
PL/SQL 过程已成功完成。
已用时间: 00: 00: 00.03
!!!!!!! 0.03秒,差距竟然这么大,回头看代码,第二次只是在循环体中使用了变量X,将i的值赋给了X,这样一来,ORACLE在执行的时候只需要编译一次,其他999次都是从共享池中使用查询方案,查询时间和速度当然更快了。
所以。从软件开发的一开始就要认识到绑定变量的重要性。