SGA中的共享池由库缓存(Library Cache)、字典缓存(Dictionary Cache)、用于并行执行消息的缓冲以及控制结构组成。
Shared Pool的大小由参数SHARED_POOL_SIZE决定。在32位系统中,这个参数的默认值是8M,而64位系统中的默认值位64M。最大为4G。
对于Shared Pool的内存管理,是通过修正过的LRU算法表来实现的。
下面分别介绍Shared Pool的几个组成部分。
Library Cache中(的对象,即叫Library Cache Object)包括共享SQL区(Shared SQL Areas,即游标对象)、PL/SQL存储过程和包以及控制结构(如锁、库缓存句柄)。
任何用户都可以访问共享SQL区(可以通过v$sqlarea访问,随后会介绍这个重要视图)。因此库缓存存在于SGA的共享池中。
Oracle会为每一条SQL语句运行(每运行一条语句Oracle都会打开一个游标)提供一个共享SQL区(Shared SQL Areas)和私有SQL区(Private SQL Areas属于PGA)。当发现两个(或多个)用户都在运行同一SQL语句时,Oracle会重新组织SQL区,使这些用户能重用共享SQL区。但他们还会在私有SQL区中保存一份这条SQL语句的拷贝(?)。
一个共享SQL区中保存了一条语句的解析树和查询计划。在多用户系统中,Oracle通过为SQL语句使用同一共享SQL区多次运行来节省内存。
当一条新的SQL语句被解析时,Oracle从共享池中分配一块内存空间来存储共享SQL区。这块内存的大小与这条语句的复杂性相关。如果Shared Pool不够空间分配给共享SQL区,Oracle将释放从Shared Pool对应所拥有的LRU链表中查找到最近最少使用的内存块,直到有足够空间给新的语句的共享SQL区。如果Oracle释放的是一个共享SQL区的内存,那么相应的语句在下次执行时需要再次解析并重新分配共享SQL区。而从解析语句到分配共享SQL区是一个比较消耗CPU的工程。这就是为什么我们提倡使用绑定变量的原因了。在没有使用绑定变量时,语句中的变量的数值不同,oracle就视为一条新的语句(9i后可以通过cursor_sharing来控制),重复上面的解析、内存分配的动作,将大大消耗系统资源,降低系统性能。
Oracle对于PL/SQL程序单元(存储过程、函数、包、匿名PL/SQL块和触发器)的处理过程和对单个的SQL语句的处理过程相似:它会(在Shared Pool上)分配一个共享区(即Shared Pool共享池中的一个空闲chunk)来存储被解析、编译过的程序单元;
同时(在PGA(中的UGA里的Private SQL Areas中))分配一个私有区域来存放运行程序单元的会话所指定的程序单元的参数值(包括本地变量(local,?局部变量)、全局变量和包变量——这也叫做包的实例化)和用于执行程序所需的内存。如果多个用户运行同一个程序单元,则他们共享同一个共享区域,并且各自保持一份私有区域,用于用户会话中指定的变量值。
而一个PL/SQL程序单元中的每条单个SQL语句的处理过程则和上面描述的SQL语句的处理过程相同。要注意一点,尽管这些语句是从PL/SQL程序单元中来的,但是Oracle还是会为这些语句分配一块共享SQL区,同时为每个用户分配一个相应的私有SQL区。
数据字典是有关于数据库的参考信息、数据库的结构信息和数据库中的用户信息的一组表和视图的集合,如我们常用到的V$视图、DBA_视图都属于数据字典。在SQL语句解析的过程中,Oracle可以非常迅速的访问(如果需要的话)这些数据字典,在SQL Trace中,这种对数据字典的访问就被统计为回调(recursive calls)。看下面例子:
第一调用语句,需要做硬解析:
SQL> select * from T_COMPANY;
9999 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3356521258
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10000 | 156K| 9 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T_COMPANY | 10000 | 156K| 9 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
355 recursive calls
0 db block gets
764 consistent gets
39 physical reads
116 redo size
305479 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
9999 rows processed
可以看到,Recursive Calls高达355。第二次调用,无需解析,直接使用共享SQL区中缓存:
SQL> /
9999 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3356521258
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10000 | 156K| 9 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T_COMPANY | 10000 | 156K| 9 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
705 consistent gets
0 physical reads
0 redo size
305479 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9999 rows processed
由于没做解析,这时recursive calls为0。
当然,recursive calls并不仅仅发生在解析的时候。由于数据字典记录了所有对象的结构、数据信息,因此在对象结构、数据发生变化时都会访问数据字典:
SQL> delete from t_company where rownum=1;
1 row deleted.
...
Statistics
----------------------------------------------------------
360 recursive calls
...
SQL> /
1 row deleted.
...
Statistics
----------------------------------------------------------
4 recursive calls
...
SQL> /
...
Statistics
----------------------------------------------------------
4 recursive calls
...
可以看到,上面的delete语句在第一次执行时,包括因(SQL语句)解析和数据改动导致对数据字典的访问,因此
recursive calls较高,为360。在随后的执行中,因为没有做(SQL语句)解析,所以recursive calls大大减少,只有
4,而这4个recursive calls是因为数据改变而需要对数据字典的访问。
因为Oracle对数据字典访问如此频繁,因此内存中有两处地方被专门用于存放数据字典:
一个地方就是数据字典缓存(Data Dictionary Cache)。数据字典缓存也被称为行缓存(Row Cache),因为它是以记录行为单元存储数据的,而不像Buffer Cache是以数据块为单元存储数据。
内存中另外一个存储数据字典的地方是库缓存。
所有Oracle的用户都可以访问这两个地方以获取数据字典信息。