zftang
作者zftang·2020-04-15 12:22
其它·小白一枚

Oracle的硬解析和软解析

字数 9455阅读 554评论 0赞 0

提到软解析(soft parse)和硬解析(hard parse),就不能不说一下Oracle对sql的处理过程。当你发出一条sql语句交付Oracle,在执行和获取结果前,Oracle对此sql将进行几个步骤的处理过程:

1、语法检查(syntax check)

检查此sql的拼写是否语法。

2、语义检查(semantic check)

诸如检查sql语句中的访问对象是否存在及该用户是否具备相应的权限。

3、对sql语句进行解析(parse)

利用内部算法对sql进行解析,生成解析树(parse tree)及执行计划(execution plan)。

4、执行sql,返回结果(execute and return)

其中,软、硬解析就发生在第三个过程里。

Oracle利用内部的hash算法来取得该sql的hash值,然后在library cache里查找是否存在该hash值;

假设存在,则将此sql与cache中的进行比较;

假设“相同”,就将利用已有的解析树与执行计划,而省略了优化器的相关工作。这也就是软解析的过程。

诚然,如果上面的2个假设中任有一个不成立,那么优化器都将进行创建解析树、生成执行计划的动作。这个过程就叫硬解析。

创建解析树、生成执行计划对于sql的执行来说是开销昂贵的动作,所以,应当极力避免硬解析,尽量使用软解析。

这就是在很多项目中,倡导开发设计人员对功能相同的代码要努力保持代码的一致性,以及要在程序中多使用绑定变量的原因。

Oracle中的SQL在执行之前进行解析,一个硬解析包括下面的步骤:

  1. 加载到共享池中 - SQL源代码被加载到内存中。
  2. 语法解析 - Oracle检查语法拼写错误。
  3. 语义解析 - Oracle验证来自数据字典的所有表名和列名并且验证你是否有权访问这些数据。
  4. 查询转换 - 如果允许(query_rewrite=true),oracle将把复杂的SQL转换为等价的简单形式。
  5. 优化 - 根据模式的统计信息创建执行计划(在10g中或许会使用动态的样本统计信息)。
  6. 创建可执行文件 - Oracle创建一个服务于SQL查询的、调用本地文件的可执行文件。

Oracle提供了 shared_pool_size参数来缓存SQL从而使我们不需要重复解析SQL。但是,如果shared_pool_size设置太小或者在代码中使用了非重用的SQL(例如包含直接量where name="fred"),SQL语句可能会过期。

在Oracle中软解析和硬解析的区别是什么?仅仅是上面用红色标明的第一步。也就是说,软解析不需要重新装载到共享池(以及相关的内存分配)。

通常很高的解析调用次数(>10/秒)表明你的系统有大量不同的SQL语句,或者你的SQL语句没有被重用(例如,没有使用绑定变量)。

硬解析需要把 SQL语句加载到共享池中。硬解析比软解析差很多,因为它牵涉到共享池中内存的分配和管理。一旦被加载,SQL必须完全重新检查语法和语义以及生成可执行目标。

如果shared_pool_size设置的太小或者SQL语句没有重用,则会发生大量的硬解析。

所以要适当的设置共享池大小并且通过主机变量重用SQL。

软解析、硬解析的一个小测试:

SQL> create global temporary table sess_event on commit preserve rows as select * from v$session_event where 1=0;

Table created

SQL> insert into sess_event

2 select * from v$session_event

3 where sid=(select sid from v$mystat where rownum=1);

9 rows inserted

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%'

3 ;

NAME VALUE


parse count (total) 32127

parse count (hard) 30365

parse count (failures) 1

SQL>

SQL> begin

2 for i in 1..10000 loop

3 execute immediate 'insert into test values('||i||','||i||')';

4 end loop;

5 commit;

6 end;

7 /

PL/SQL procedure successfully completed

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42201

parse count (hard) 40379

parse count (failures) 1

没有使用绑定变量(这种写法往往有人误认为使用了绑定,这里就不多解释了)总解析次数:10074

硬解析次数:10014

上边2个值除了我们的循环10000次外,还包括其他一些系统表的读写,所以解析次数大于10000,但是我们要注意下面的结果,对于每个SQL

都有一个版本,也就是ORACLE对于每个不同的SQL分别执行了硬解析,下面是共享池最后缓存的数据(部分已经被覆盖)

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


insert into test values(9630,9630) 1 1 1

insert into test values(9950,9950) 1 1 1

insert into test values(9625,9625) 1 1 1

insert into test values(9592,9592) 1 1 1

insert into test values(9940,9940) 1 1 1

insert into test values(9897,9897) 1 1 1

insert into test values(9679,9679) 1 1 1

insert into test values(9850,9850) 1 1 1

insert into test values(9744,9744) 1 1 1

insert into test values(9938,9938) 1 1 1

insert into test values(9977,9977) 1 1 1

insert into test values(9907,9907) 1 1 1

insert into test values(9809,9809) 1 1 1

insert into test values(9800,9800) 1 1 1

insert into test values(9645,9645) 1 1 1

insert into test values(9724,9724) 1 1 1

insert into test values(9799,9799) 1 1 1

insert into test values(9818,9818) 1 1 1

insert into test values(9642,9642) 1 1 1

insert into test values(9624,9624) 1 1 1

中间内容略

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


insert into test values(9838,9838) 1 1 1

insert into test values(9716,9716) 1 1 1

insert into test values(9691,9691) 1 1 1

insert into test values(9831,9831) 1 1 1

insert into test values(9992,9992) 1 1 1

insert into test values(9883,9883) 1 1 1

insert into test values(9865,9865) 1 1 1

insert into test values(9901,9901) 1 1 1

insert into test values(9657,9657) 1 1 1

insert into test values(10000,10000) 1 1 1

insert into test values(9659,9659) 1 1 1

insert into test values(9746,9746) 1 1 1

insert into test values(9695,9695) 1 1 1

insert into test values(9869,9869) 1 1 1

insert into test values(9804,9804) 1 1 1

insert into test values(9843,9843) 1 1 1

435 rows selected

SQL> truncate talbe sess_event;

truncate talbe sess_event

ORA-03290: 无效的截断命令 - 缺失 CLUSTER 或 TABLE 关键字

SQL> truncate table sess_event;

Table truncated

SQL> alter system flush shared_pool;

System altered

SQL>

SQL> insert into sess_event

2 select * from v$session_event

3 where sid=(select sid from v$mystat where rownum=1);

10 rows inserted

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42643

parse count (hard) 40456

parse count (failures) 2

SQL>

SQL> begin

2 for i in 1..10000 loop

3 execute immediate 'insert into test values(:v1,:v2)' using i,i;

4 end loop;

5 commit;

6 end;

7 /

PL/SQL procedure successfully completed

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42688

parse count (hard) 40466

parse count (failures) 2

下面看下使用绑定变量的情况(真正使用了绑定)总解析次数:45

硬解析次数:10

我们可以看出差异是多么大了,呵呵,对于SQL本身只有一次软解析,执行次数为10000

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


begin for i in 1..10000 loop execute immediate 'insert into test values(:v1,:v2 1 1 1

update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_dat 1 1 1

insert into test values(:v1,:v2) 1 1 10000

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

SQL> alter system flush shared_pool;

System altered

但是我们要注意一个情况,即时同样的SQL如果没有使用绑定变量,ORACLE也会对其执行一次软解析的,但是没有硬解析,如下:

每执行一次SQL,也会同时产生其他写系统表等很多相关的软解析包括查询SQL本身。硬解析不变。

SQL> insert into test values('1','1111111111');

1 row inserted

SQL> commit;

Commit complete

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_dat 1 1 1

insert into test values('1','1111111111') 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

SQL> insert into test values('1','1111111111');

1 row inserted

SQL> commit;

Commit complete

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_dat 1 1 1

insert into test values('1','1111111111') 1 2 2

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 2 2

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42906

parse count (hard) 40520

parse count (failures) 2

SQL> insert into test values('1','1111111111');

1 row inserted

SQL> commit;

Commit complete

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


begin dbsnmp.mgmt_response.get_latest_curs(:a); end; 1 1 1

/ OracleOEM / BEGIN IF (:1 = 'READ WRITE') THEN ---- -- For a read- 1 1 1

/ OracleOEM / DECLARE instance_number NUMBER; latest_task_id NUMBER; 1 1 1

update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_dat 1 4 4

insert into test values('1','1111111111') 1 3 3

DELETE FROM MGMT_DB_LATEST_HDM_FINDINGS WHERE TARGET_GUID = :B2 AND COLLECTION_T 2 3 3

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 3 3

8 rows selected

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42922

parse count (hard) 40520

parse count (failures) 2

SQL> insert into test values('1','1111111111');

1 row inserted

SQL> commit;

Commit complete

SQL> insert into test values('1','1111111111');

1 row inserted

SQL> commit;

Commit complete

SQL> select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper(sql_text) like '%TEST%';

SQL_TEXT VERSION_COUNT PARSE_CALLS EXECUTIONS


begin dbsnmp.mgmt_response.get_latest_curs(:a); end; 1 1 1

/ OracleOEM / BEGIN IF (:1 = 'READ WRITE') THEN ---- -- For a read- 1 1 1

/ OracleOEM / DECLARE instance_number NUMBER; latest_task_id NUMBER; 1 1 1

update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_dat 1 5 5

insert into test values('1','1111111111') 1 5 5

DELETE FROM MGMT_DB_LATEST_HDM_FINDINGS WHERE TARGET_GUID = :B2 AND COLLECTION_T 2 3 3

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 1 1

select SQL_TEXT,VERSION_COUNT,PARSE_CALLS,EXECUTIONS from v$sqlarea where upper( 1 4 4

8 rows selected

SQL>

SQL> SELECT NAME,VALUE FROM V$MYSTAT A,V$STATNAME B WHERE A.STATISTIC#=B.STATISTIC#

2 AND NAME LIKE 'parse count%';

NAME VALUE


parse count (total) 42946

parse count (hard) 40520

parse count (failures) 2

SQL>

通过这个例子我们也就知道V$SQLAREA中的解析包含什么内容了。

————————————————

版权声明:本文为CSDN博主「秋雨飘落」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zftang/article/details/6319332

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

0

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广