5.2 解析过程

上面我们简单描述了一下SQL语句的解析步骤,下面将通过图5-2具体说明整个解析及游标查找过程。

图5-2 SQL解析及游标查找

首先我们来说明几个概念。前面讲到了SQL解析,它的生成结果保存在库高速缓存中。保存的对象我们称为库缓存对象(Library Cache Object)。所有的库缓存对象都是以一种名为库缓存对象句柄(Library Cache Object Handles)的结构保存的。具体形式上,库缓存对象句柄是以哈希表的形式存储在库缓存中的。整个库缓存可以看成是由一组哈希桶(Hash Bucket)组成的。

当用户提交一条SQL语句后,优化器会根据目标SQL的文本计算一个哈希值,然后去库缓存中找匹配的Hash Bucket,如图5-2所示的步骤①。这里需要强调一点,不同SQL文本可能计算的哈希值相同。此外,相同SQL文本也可能代表的是不同的语句(后面会看到这样的示例)。

在找到对应的Hash Bucket后,在这个桶的后面是一个Library Cache Object Handles的链表。链表中的每一个Library Cache Object Handle都对应着一个SQL文本解析后的内存结构(游标)。每一个Library Cache Object Handle对应的内存结构都可分为一个Parent Cursor和若干个Child Cursor,在Parent Cursor中保存有指向Child Cursor的指针结构。在Parent Cursor中保存着SQL文本,在Child Cursor中保存着SQL解析树和执行计划,如图5-2中所示的步骤②。

针对每个Parent Cursor,会有多个Child Cursor。每个Child Cursor都对应一套SQL执行计划。下面就需要遍历这个Child Cursor的链表,找到适合的Child Cursor了。如果找不到,会生成一个新的Child Cursor,并挂在Parent Cursor的下面,如图5-2所示的步骤③。

下面我们分别看看不同的解析类型,对应于上面的结构是如何进行的。

1.硬解析

根据上面所述,如果根据SQL文本计算的哈希值(sql_id)无法在库高速缓存中找到,则开启一个硬解析的过程。在这一过程中,首先需要在共享池中获取一个栓锁(Latch),然后在共享池的可用Chunk链表(也就是Bucket)中找到一个可用的Chunk,然后释放Latch。在获得了Chunk后,这块Latch就可以认为是进入Library Cache了,从而开始硬解析的过程。在经过一系列的步骤后,优化器创建一个最优的执行计划。数据库会将产生的执行计划、SQL文本等装载进Library Chache中的若干个Heap。对应于上面的结构,会生成一个Parent Cursor,下面挂着一个Child Cursor。

2.软解析

如果在Bucket中找到了某一SQL语句,则说明该SQL语句以前运行过,于是进行软解析。软解析是相对于硬解析而言的。如果解析过程中,可以从硬解析中去掉一个或多个步骤,则这样的解析就是软解析。它又可以细分为三种类型:

·类型-hard parse:某个Session发出的SQL语句与Library Cache里其他Session发出的SQL语句一致。这时,该解析过程中可以去掉硬解析中某些步骤,但仍要进行数据字典检查、名称转换和权限检查。对应于上面的结构,就是找到一个对应的Parent Cursor,后续操作生成一个Child Cursor,并挂在其他Child Cursor的后面。

·类型-soft parse:某个Session发出的SQL语句与Library Cache里同一个session之前发出的SQL语句一致。这时,该解析过程中只需要进行权限检查,因为可能通过Grant改变了该Session用户的权限。对应于上面的结构来说,就是在Parent Cursor下挂的Child Cursor找到你所需要的解析结果。这种情况可以省略很多解析动作,直接返回结构即可。

·类型-soft soft parse:当设置了初始化参数session_cached_cursors,且某个Session对相同的Cursor进行第三次访问时,将在该Session的PGA里创建一个标记,并且该游标即使已经被关闭也不会从Library Cache中交换出去。这样,该Session以后再执行相同的SQL语句时,将跳过硬解析的所有步骤。这种情况是最高效的解析方式,但是会消耗很大的内存。对应于上面的结构来说,就是根本不需要再访问共享池,直接在会话自有的内存区域中就可以找到解析结果。这是效率最高的一种方式。

3.解析优化

从上面过程的说明可见,数据库硬解析的过程就是生成游标的过程;软解析的过程就是找到以前生成的游标的过程;软软解析就是直接在客户端就找到缓存在本地游标的过程。从性能的角度讲,需要尽可能地避免发生硬解析。这也是为什么数据库要将共享游标保存在库缓存中的原因。因为这样,属于这个实例的每一个进程都可以重用它们。

有两个原因可以解释为什么硬解析的开销较高。第一个原因是硬解析过程很长,涉及大量复杂操作,这些都非常依赖CPU操作。第二个原因是要分配内存来将父游标与子游标保存在库缓存中。由于库缓存是在所有会话之间共享的,库缓存中的内存分配必须串行执行。在实际操作中,在分配父游标和子游标所需的内存之前,必须取得一个保护共享池的闩锁。

虽然软解析的影响已经远比硬解析要小,但还是需要尽量避免软解析,因为它也会导致某种串行处理。事实上,为了所有共享的父游标,也必须取得一个保护库缓存的闩锁。总的来讲,需要尽可能避免硬解析和软解析,因为它们都会抑制应用程序的可扩展性。

下面通过一个示例说明生成游标的过程,大家也可以从操作中看到如何通过数据字典查看语句缓存的游标情况。