对于OLTP类型的应用系统而言,据库端性能的优劣对系统整体的运行有至关重要的影响,而影响SQL语句,PL/SQL代码执行效率的因素多种多样,其中一条比较重要则是对于SQL语句的硬解析数量在SQL解析中所占的比重,如果SQL硬解析所占的比重较高,将会对系统性能产生较大影响。如下是关于对Oracle中通过合理使用绑定变量带来的性能改善的一些探索
Cursor是Oracle数据库中SQL解析和执行得载体,从本质上来说,可以将其理解为C语言中一种复杂数据结构。Cursor分为两种类型,一种是Shared Cursor,另一种是Session Cursor。
在描述Shared Cursor之前,需要先介绍一下数据库中库缓存(Library Cache)的作用和其组成结构以便对Shared Cursor有更加详细的认识
库缓存是Shared Pool中的一块内存区域,它的主要作用是缓存刚刚执行过的sql语句和PL/SQL语句(存储过程,函数,包,触发器)所对应的执行计划,解析树等对象,当同样的SQL语句和PL/SQL语句再次被执行时,就可以利用已经缓存在Library Cache中的那些相关对象而无需再次从头解析,这样就提高了这些SQL语句和PL/SQL语句重复执行时的效率。
缓存在库缓存中的对象称为库缓存对象,所有的库缓存对象都以一种名为库缓存对象句柄的结构存储在库缓存中,Oracle通过访问相关的库缓存对象句柄来访问相关的库缓存对象。而库缓存对象句柄实际上是以哈希表(Hash Table)的方式存储在库缓存中,这意味着Oracle会通过相关的哈希运算来存储和访问对应的库缓存对象句柄。站在哈希表的角度看,整个库缓存的结构如下图所示
从上图可以看出,整个库缓存可以看做是由一组Hash Bucket所组成,每一个Hash Bucket都对应不同的哈希值。对于单个Hash Bucket而言,里面存储的就是哈希值相同的所有库缓存对象句柄,同一个Hash Bucket中不同的库缓存对象句柄之间会用指针连接起来,即同一个Hash Bucket中不同的库缓存对象句柄之间实际上组成了一个库缓存对象句柄链表。
当Oracle要执行目标SQL "select * from emp"时,首先会对该SQL的SQL文本施以哈希运算,然后根据得到的哈希值去相关的Hash Bucket中遍历对应的库缓存对象句柄链表,如果找到了对应的库缓存对象句柄,就可以直接访问该SQL的执行计划,解析树等对象这意味着可以直接重用这些对象而无需从头开始解析,如果找不到对应的库缓存对象句柄,则意味着必须从头开始解析,并且把解析后的执行计划,解析树等以库缓存对象句柄的方式链接在相关的Hash Bucket中的库缓存对象句柄链表中。
库缓存对象句柄的内部组成如下图
从上图中可以看出库缓存对象句柄有很多属性,各个属性的含义如下:
属性"Name"表示的是库缓存对象句柄所对应的库缓存对象的名称。如果是SQL语句所对应的库缓存对象句柄,则属性"Name"的值就是该SQL的SQL文本;如果是表对应的库缓存对象句柄,则属性"Name"就是该表的表名。
属性:"NameSpace"表示的是库缓存对象句柄对应的库缓存对象所在的分组名,不同类型的库缓存对象可能属于同一个分组,即不同类型的库缓存对象所对应的库缓存对象句柄的NameSpace的值有可能是相同的,比如SQL语句和匿名PL/SQL语句所对应的NameSpace的值都是CRSR。
Shared Cursor总结
如上对库缓存对象的认识后可知Shared Cursor其实就是指缓存在库缓存里的一种库缓存对象,它是指缓存在库缓存里的SQL语句和PL/SQL语句所对应的库缓存对象。它是Oracle缓存在Library Cache中的几十种库缓存对象之一。Shared Cursor里面会存储目标SQL的SQL文本,解析树,该SQL所涉及的对象定义,该SQL所使用的绑定变量类型和长度,以及该SQL的执行计划等信息。Oracle数据库中的Shared Cursor又细分为Parent Cursor和Child Cursor两种类型,Parent Cursor和Child Cursor的结构相同,区别在于目标SQL的SQL文本会存储在Parent Cusor所对应的库缓存对象句柄的Name属性中(Child Cursor所对应的库缓存对象句柄的Name属性值为空,这意味着只有通过Parent Cursor才能找到相应的Child Cursor),而该SQL的解析树和执行计划则会存储在Child Cursor所对应的库缓存对象句柄中,这种Parent Cursor和Child Cursor的结构决定了在Orace数据库里任意一个目标SQL一定会同时存在两个Shared Cursor,一个是Parent Cursor,一个是Child Cursor,Parent Cursor会存储该SQL的SQL文本,Child Cursor会存储该SQL真正可以被重用的解析树和执行计划。
思考:Oracle为什么要设计这种Parent Cursor和Child Cursor并存的结构?
因为Oracle是根据目标SQL的哈希值去Hash Bucket的库缓存对象句柄链表里寻找对应的库缓存对象句柄的,但是不同的SQL对应的Hash值可能相同,而且同一个SQL也可能有多分不同的解析树和执行计划,如果他们都处于同一个Hash Bucket的库缓存对象句柄链表里,那么这个库缓存对象句柄链表的长度就不是其最优长度,这意味着会增加Oracle从头到尾搜索这个库缓存对象句柄链表所耗费的时间和工作量,为了能够尽量减少对应Hash Bucket中库缓存对象句柄链表的长度,Oracle才设计了这种嵌套的Parent Cursor和Child Cursor并存的结构。
硬解析
硬解析是指Oracle在执行目标SQL时,在库缓存中找不到可重用的解析树和执行计划,从而不得不从头开始解析目标SQL并生成功相应的Parent Cursor和Child Cursor的过程,硬解析也分为两种情况,一种是在库缓存中找不到相应的Parent Cursor,此时Oracle会从头开始解析目标SQL并生成相应的Parent Cursor和Child Cursor然后将他们挂到对于的Hash Bucket中,另一种情况是找到了目标SQL的Parent Cursor但是未找到Child Cursor,此时Oracle也会从头开始解析目标SQL,并生成相应的Child Cursor挂在对应的Parent Curosr中。
硬解析的危害
硬解析会导致Shared pool Latch的争用,无论是哪种类型的硬解析都至少会生成一个Child Cursor,并把目标SQL的解析树和执行计划存储在该Child Cursor里,然后把这个Child Cursor存储在内存中,这意味着必须在Shared Pool中分配一块内存空间用于存储Child Cursor,而分配内存空间这个动作是需要持有Shared pool Latch的,Shared pool Latch的作用之一是保护共享内存的分配,所以如果有一定数量的并发硬解析可能会导致Shared pool Latch的争用,而一旦发生大量的Shared pool Latch争用,系统性能和可扩展性将会收到影响,严重将导致CPU占用飙升。
软解析
软解析是指Oracle在执行目标SQL时在Library cache中找到了目标SQL的Parent Cursor和Child Cursor并将Child Cursor中的解析树和执行计划直接拿过来重用的过程。因为软解析能在库缓存中找到对应的Parent Cursor和Child Cursor,不需要生成新的Parent Cursor和Child Cursor,这意味这软解析不需要持有Shared pool Latch,由于Shared pool Latch的争用所带来的性能和扩展性问题在软解析中是不存在的。
Oracle中的绑定变量
绑定变量是一种特殊类型的变量,或者称为占位符,通常出现在目标SQL的SQL文本中用于替换SQL文本中where条件或者VALUES子句中具体的输入值。
绑定变量的作用
如上已经提到当Oracle在执行目标SQL时,会根据目标SQL的SQL文本的哈希值去库缓存中寻找匹配的Parent Cursor,那就意味着只要目标SQL的文本内容不同那么计算出来的哈希值就有可能不同,也就是说这些文本值不完全相同的SQL之间无法重用解析树和执行计划,对于大多数OLTP类型应用系统而言,同一类型的SQL都有可能被不同的用户并发地反复执行(同一类型指的是除文本中对对应的输入值不同外其他部分一模一样)例如"select ename from emp where empno =7369 "和"select ename from emp where empno = 7370"显然这种同类型的SQL在并发地被不同用户反复执行时批彼此之间能否重用解析树和执行计划对系统性能和扩展性有至关重要的影响,因为如果不能重用解析树和执行计划那么就每次执行SQL时都要使用硬解析。那么如何能有效降低硬解析的数量,答案是使用绑定变量。使用了绑定变量之后对于"select ename from emp where empno =7369 "和"select ename from emp where empno = 7370"这样的两条SQL将变成"select ename from emp where empno = :x",如此使用了绑定变量之后,对于那些
除SQL文本中对对应的输入值不同外其他部分一模一样的同一类型的SQL就变得完全相同了,据此计算出来的哈希值也就完全相同,也就意味着这些同类型SQL具备了重用解析树和执行计划的条件,我们只需要对绑定变量x分别传入7369和7370就可以实现和原先那两个SQL一模一样的效果,不同之处在于第二次执行时可以重用第一次的解析树和执行计划,避免了硬解析。
结论
综合上述对库缓存,Shared Cursor,硬解析和软解析相关认识可知,在SQL文本中合理正确的使用绑定变量的作用在于它可以有效降低硬解析的数量进而提升系统性能。