深入shared pool
解析SQL语句 |
生成执行计划 |
中间考虑的因素很多,包括访问的对象的存在、权限、等等 |
执行SQL语句 |
解析的过程(生成最优执行计划)是一个耗费资源的过程,随着用户的并发数量的增加。数据库的性能也会降低的很快。
ORCLE对解析的执行
1、ORACLE将SQL语句分成为两个部分:静态部分+动态部分
2、静态部分:SQL 语句的关键词(所涉及的表名称、列名称、等)
3、动态部分:字面值(表里面的表数据,例如 where name=‘xkj’中的xkj)
静态部分是有限的、动态部分是无限的
绑定变量:实现SQL共享(静态部分),减少解析。在实际中,不同的SQL语句的静态部分的重复率非常的高。实际上,动态部分对SQL语句的解析的影响可以忽略不计
通过使用绑定变量,用来提高SQL语句的缓存命中率(减少解析次数、减少sql缓存)
declare
v1 varchar2(10);
n1 int;
begin
n1:=1
select salary into v1 from test where id=n1;
end;
在上面的SQL语句中,使用了绑定变量n1,在解析的时候,使用的绑定变量。执行的时候,将字面值传入语句中。因此这样SQL语句的命中率将会提高。
ORCLE将解析过的SQL语句缓存在shared pool中,碰到相同的SQL语句再次执行的时候,ORACLE直接使用已经解析过的执行计划。
Shared pool缓存内容:SQL语句、执行计划、PL/SQL代码、PL/SQL机器码等
Shared pool细分
1、库缓存:最近执行的SQL语句、存储过程、函数、解析树、执行计划,最活跃部分
2、数据字典缓存:SQL执行过程中涉及的数据字典
递归调用:shared pool为生成执行计划,又进行了很多别的调用(如查询数据字典)
row cache 缓存数据字典信息,取消了递归调用
Shared Pool的内存结构(由许多的内存块组成,这些内存块称为chunk)
1、chunk是shared pool中内存分配的最小单元(类似于extent)
2、一个chunk在内存中是连续的
Chunk的分类
1、free:chunk中没有有效的对象,可以不受限制的分配
如果用于存放和SQL相关的数据,那么chunk就属于library cache,如果用于存放数据字典,那么这个chunk就属于dictionary cache
2、recr:recreatable,chunk里面包含的对象可以被临时性的移 走,如果需要,可以重建,例如共享SQL语句
3、freeabl:session用过这个trunk,里面存放的对象数据是session 在处理过程中产生的,没有办法重建,这点不同于recr。因此这个 trunk不能被临时性的移走。但是在合适的时间段可以被释放。
4、perm:permanent,chunk中包含永久性的对象,但是大型的 permanent类型的chunk中可能包含可用空间,需要的时候,这些空 间可以被释放
Shared pool中chunk的分配
1、shared pool中的chunk的大小是不一样的,但是肯定是连续的
2、因为chunk是分配的最小单元,因此session需要给对象分配空间的时候,会以chunk为单位进行申请
3、可用的chunk(free)会形成一个链表,便于进行分配的时候,可以通过遍历链表寻找到可用的适合的chunk,链表是chunk进行组织和管理的一种方式
4、一个可用的chunk链表是一个bucket,shared pool中会有很多的bucket,不同的bucket中的chunk的大小不同,一般是随着bucket编号的增加,bucket的大小是增长的
5、当需要从shared pool中寻找chunk的时候,首先会定位一个bucket,然后遍历bucket,寻找最合适的chunk
如果chunk的空间比需要的空间大,那么这个chunk就拆分成两个,一个被分配、一个成为free,重新挂接到这个bucket上。
6、在寻找chunk的过程中,如果一个bucket中没有合适的chunk,接着寻找另外一个非空的bucket,如果所有的bucket中都没有合适的chunk,那么就从rec类型的链表中释放一部分的空间,为free,或将free做适当合并。
7、shared pool中所有类型的chunk都是以链表的方式进行管理的
关于shared pool有两个问题
1、shared pool latch的争用问题
2、ORA-4013错误
shared pool latch争用(出现了大量的小的free chunk)
lache锁(对内存结构保护,很快):控制buckct是否能使用,一个lache控制一类buckct
只有rec类型的chunk能够被释放空间,即使释放了空间,这些空间可能都不是连续的,都是一些很小的chunk,这样可能形成这样一种情况,shared pool中是大量的非常小的chunk,这样在寻找chunk的时候,也很难寻找到合适的trunk
同时因为free chunk数量非常的多,因此在分配chunk的时候,就会占用大量的时间,因为对bucket进行扫描、管理、分配的时候,需要获得shared pool latch,在扫描free chunk的时候,占用太多的时间,自然会发生latch争用的情况,原因就是因为大量的小的free chunk的情况
select addr,KSMCHSIZ,KSMCHCLE from x$ksmsp where KSMCHCLE=free;查看小chunk数量
在语句被解析后要放入shared pool 中但是shared pool 中没有空间是会报ORA-4031错误。其原因可能是:
1、 共享池设的空间太小
2、 SQL语句没有共享,造成被解析的语句过多占用空间大,自然会造成压力
3、 有大量的free空间但是都是零碎的空间,都不能被使用。
对于第二种原因就要解决语句共享问题,另外SQL 语句不共享会导致cpu负载很高
desc v$sql; --> SQL_TEXT 查看共享池所有被共享的语句(子游标信息)
select sql_text from v$sql order by sql_text;看是否开启sql共享
select sql_text from v$sqlarea (父游标信息)
select addr,KSMCHSIZ,KSMCHCLE from x$ksmsp where KSMCHCLE=free; 查看小chunk数量
alter system set cursor_sharing=force; 在解析字面之前强行转化城绑定变量(但要不能随便改)
很多的情况会导致SQL不被共享:
1、SQL文本大小写不一致
2、SQL语句的绑定变量的类型不一致
3、SQL涉及的对象属于不同的schema
4、SQL的优化器不一致: 添加了提示、修改了optimizer_mode参数等
解决方法:
1.应用端(JAVA可通过JDBC将字面值转为占位符) (严格要求开发人员使用绑定变量)但是商品化老的软件改代码几乎不可能
2.数据库端 有局限性 使用procedures、function>package存储在后台,让应用端直接调用
对于第三种原因(同稀疏表)就要shrink 对表进行收缩然后降高水位线(移动表中的行不影响生产但降高水位线一定会影响生产,所以要先将表锁住。锁住以后别人无法访问但有人访问时就锁不住)
优化怎么入手?
使用工具、日志和5大报告
工具:视图、日志、数据字典
五大报告:ADDM/AWR/ASH/STATSPACK/RPA
对于shared pool 要关心的只需要知道libary chache 共享的sql和执行计划
Library cache
用户提交SQL语句或者PL/SQL以后,在library cache中会生成一个可执行的对象
这个可执行对象叫做游标(不同于SQL语句中的游标),对于所有的进程来说,这些可执行对象是共享的
游标可以认为是sql+执行计划
句柄就是指向文件的一个指针,指向文件的物理地址
Hash 算法
Oracle在内部管理中大量的使用到了hash
1、使用hash的目的就是为了快速查找和定位
对数值进行hash运算,产生一个索引号,然后根据索引号将数值放置到相应的hash bucket中去,根据hash运算的法则,会产生多个索引号,每一个索引号对应一个hash bucket(一个数值列)。我们在寻找数值的时候,将搜寻的数值进行hash,产生一个索引号,那么这个数值一定在这个索引号对应的 hash bucket中,于是直接跳转到这个hash bucket中进行遍历。这样我们定位数据的时候,就能够大大的减少遍历的数量。
库缓存中就是使用hash bucket来管理的
1. 首先根据shared_pool_size指定的尺寸来自动计算hash bucket的个数
2. 每个hash bucket后面都串联着多个句柄
句柄描述了library cache里面的对象的一些属性,包括名称、标记、指向对象所处的内存地址的指针
一条SQL语句被获取后在shared pool中解析的过程:
一条SQL语句进入à将SQL文本转化为ASCII值-à对ASCII等参数进行hash函数的运算-à得到一个数值、就是hash bucket的号码-à将对象句柄放进相应的hash bucket中去
进程要处理某个对象,例如一条SQL语句
首先对语句进行hash,得到一个编号-à进入hash bucket中进行扫描-à
1、该对象句柄存在,ok,直接使用
2、该对象句柄存在,对象被交换出内存、装载对象
3、句柄不存在,重新构建一个对象,挂在到hash bucket上
SQL的解析过程(硬解析和软解析)
用户识别的是SQL语句,oracle可以执行的是代码,这个转换过程叫做解析
解析分为硬解析和软解析,SQL语句第一次解析时必须进行硬解析
硬解析:
1、客户端发出一条SQL语句,SQL语句进入共享池
该SQL语句以及相关的辅助信息组成游标
2、对SQL语句进行hash运算,得到一个hash值,转入相应的bucket中去
3、对bucket进行遍历,查找是否存在这条SQL语句
4、如果不存在这条语句
1、获得shared pool latch,然后在shared pool中进行chunk链条的遍历,找到合适的chunk,之后释放shared pool latch,chunk进入library cache中
2、硬解析开始
硬解析过程:
1、对SQL语句进行语法的检查。如果有错误,那么就退出解析过程
2、到数据字典中校验SQL语句所涉及的对象和列是否存在,如果不存在,退出解析过程,这个过程会加载dictionary cache
3、对对象进行名称转换,同名词等要转换成实际的对象,转换失败就退出解析
4、检查发出SQL语句的用户是否具有访问sql语句里面所引用的对象的权限,无权限就退出解析过程
5、通过优化器创建一个最优的执行计划,这个过程会根据数据字典里面记录的对象的统计信息,来计算最优的执行计划,这一步涉及的运算量很大,最耗CPU资源
6、该游标所产生的执行计划、sql文本等装载进 library cache的heap中
在解析的过程中,进程会一直持有library cache latch,一直到硬解析结束
游标
1、硬解析结束以后,产生两个游标: 父游标和子游标
父游标里面包含的信息包括SQL文本和优化目标。
session打开该游标以后,就会锁定父游标,所有的session都关闭该游标以后,锁定才能够释放
父游标在被锁定的时候,不能被交换出内存
父游标交换出内存、子游标也会被交换出内存
子游标被交换出内存、父游标可以不被交换出内存
因为一个父游标可能会有多个子游标
子游标包换所有的信息:执行计划、绑定变量等
子游标随时可能被交换出内存
Oracle根据父游标的信息可以构建出一个子游标,这个过程叫reload
select 100*sum(reloads)/sum(pins) Reload_Ratio from v$librarycache
上面的语句确定reload的比率,我们希望低一些最好。
一般的情况是父游标不会被交换出内存,子游标会被交换出内存。
为什么会产生父游标和子游标呢?
1、一个父游标可以对应多个子游标
子游标的个数可以从视图v$sqlarea中的version_count字段中体现出来
每个具体的子游标则全部在视图v$sql中体现
产生子游标的一些情况举例
1、具体绑定变量的值与上次绑定变量得值有较大的差距
例如上次是6位,现在是200位
2、sql语句完全相同,但是所引用的表属于不同的用户
等等
软解析
1、硬解析是一个完整的解析过程,如果解析过程中去掉一个或者多个步骤的话,这样的解析叫做软解析
2、在bucket中找到了sql语句,就会进行软解析,因为如果没有找到,那么说明这个语句是第一次执行,第一次执行肯定是硬解析
软解析的三种类型
1、某个session发出的SQL语句与library cache里其他session发出的SQL语句相同,解析可以省略5和6,但是2、3、4还是需要执行的
2、某个session发出的sql语句是这个session之前发出的曾经执行过的语句,那么2、3、5、6可以省略,但是4不能省略,因为中间可能发生过grant等操作
3、设置了初始化参数session_cached_cursors时,当某个session第三次执行相同的sql语句时,这个sql语句的游标信息会转移到该session的PGA中去,这样以后可以直接在session的PGA中寻找SQL,提高了效率,但是内存的消耗会很大
软解析省去的步骤:
1. 不需要获取shared pool lache (free)
2. 不需要生成执行计划
3. 不需要将生成的计划写到chunk中
Shared pool 的大小设置
1、不同的系统需要不同的值
2、不要太大、不要太小,无论大还是小,对性能都有影响
通用的方法是
1、设置一个初始值、让系统运行一段时间
初始设置shared pool的大小是SGA的10%、不要超过1G
2、对shared pool的使用情况进行观察和监控
9i以后,我们可以使用advisor进行监控,然后查询找到合适的值
3、得到一个比较合理的值
4、继续观察、根据负载情况的变化,需要相应的调整参数
5、shared pool不要设置1G以上