有客户遇到SQL性能不稳定突然变差导致系统性能出现严重问题的情况对于大型的系统来说SQL性能不稳定有时突然变差这是常常遇到的问题这也是一些DBA的挑战
对于使用Oracle数据库的应用系统有时会出现运行得好好的SQL性能突然变差特别是对于OLTP类型系统执行频繁的核心SQL如果出现性能问题通常会影响整个数据库的性能进而影响整个系统的正常运行对于个别的SQL比如较少使用的查询报表之类的SQL如果出现问题通常只影响少部分功能模块而不会影响整个系统
那么应该怎么样保持SQL性能的稳定性?
SQL的性能变差通常是在SQL语句重新进行了解析解析时使用了错误的执行计划出现的下列情况是SQL会重新解析的原因
SQL语句没有使用绑定变量这样SQL每次执行都要解析
SQL长时间没有执行被刷出SHARED POOL再次执行时需要重新解析
在SQL引用的对象(表视图等)上执行了DDL操作甚至是结构发生了变化比如建了一个索引
对SQL引用的对象进行了权限更改
重新分析(收集统计信息)了SQL引用的表和索引或者表和索引统计信息被删除
修改了与性能相关的部分参数
刷新了共享池
当然重启数据库也会使所有SQL全部重新解析
SQL重新解析后跟以前相比性能突然变差通常是下列原因
表和索引的优化统计信息被删除或者重新收集后统计信息不准确重新收集统计信息通常是由于收集策略(方法)不正确引起比如对分区表使用analyze命令而不是用dbms_stats包收集统计信息时采样比例过小等等Oracle优化器严重依赖于统计信息如果统计信息有问题则很容易导致SQL不能使用正确的执行计划
SQL绑定变量窥探(bind peeking)同时绑定变量对应的列上有直方图或者绑定变量的值变化范围过大分区数据分布极不均匀
) 绑定变量的列上有直方图
假如表orders存储所有的订单state列有种不同的值表示未处理表示处理成功完成表示处理失败State列上有一个索引表中绝大部分数据的state列为和占少数有下面的SQL
select * from orders where state=:b
这里:b是变量在大多数情况下这个值为则应该使用索引但是如果SQL被重新解析而第一次执行时应用传给变量b值为则不会使用索引采用全表扫描的方式来访问表对于绑定变量的SQL只在第一次执行时才会进行绑定变量窥探并以此确定执行计划该SQL后续执行时全部按这个执行计划这样在后续执行时b变量传入的值为的时候仍然是第一次执行时产生的执行计划即使用的是全表扫描这样会导致性能很差
) 绑定变量的值变化范围过大
同样假如orders表有一列created_date表示一笔订单的下单时间orders表里面存储了最近年的数据有如下的SQL
Select * from orders where created_date >=:b;
假如大多数情况下应用传入的b变量值为最近几天内的日期值那么SQL使用的是created_date列上的索引而如果b变量值为个月之前的一个值那么就会使用全表扫描与上面描述的直方图引起的问题一样如果SQL第次执行时传入的变量值引起的是全表扫描那么将该SQL后续执行时都使用了全表扫描从而影响了性能
) 分区数据量不均匀
对于范围和列表分区可能存在各个分区之间数据量极不均匀的情况下比如分区表orders按地区area进行了分区P分区只有几千行而P分区有万行数据同时假如有一列product_id其上有一个本地分区索引有如下的SQL
select * from orders where area=:b and product_id =:b
这条SQL由于有area条件因此会使用分区排除如果第 次执行时应用传给b变量的值正好落在P分区上很可能导致SQL采用全表扫描访问如前面所描述的导致SQL后续执行时全部使用了全表扫描
其他原因比如表做了类似于MOVE操作之后索引不可用对索引进行了更改当然这种情况是属于维护不当引起的问题不在本文讨论的范围
综上所述SQL语句性能突然变差主要是因为绑定变量和统计信息的原因注意这里只讨论了突然变差的情况而对于由于数据量和业务量的增加性能逐步变差的情况不讨论
为保持SQL性能或者说是执行计划的稳定性需要从以下几个方面着手
规划好优化统计信息的收集策略对于Oracle g来说默认的策略能够满足大部分需求但是默认的收集策略会过多地收集列上的直方图由于绑定变量与直方图固有的矛盾为保持性能稳定对使用绑定变量的列不收集列上的直方图对的确需要收集直方图的列在SQL中该列上的条件就不要用绑定变量统计信息收集策略可以考虑对大部分表使用系统默认的收集策略而对于有问题的可以用DBMS_STATSLOCK_STATS锁定表的统计信息避免系统自动收集该表的统计信息然后编写脚本来定制地收集表的统计信息脚本中类似如下
exec dbms_statsunlock_table_stats…
exec dbms_statsgather_table_stats…
exec dbms_statslock_table_stats…
修改SQL语句使用HINT使SQL语句按HINT指定的执行计划进行执行这需要修改应用同时需要逐条SQL语句进行加上测试和发布时间较长成本较高风险也较大
修改隐含参数 _optim_peek_user_binds为FALSE修改这个参数可能会引起性能问题(这里讨论的是稳定性问题)
使用OUTLINE对于曾经出现过执行计划突然变差的SQL语句可以使用OUTLINE来加固其执行计划在g中DBMS_OUTLNCREATE_OUTLINE可以根据已有的执行正常的SQL游标来创建OUTLINE如果事先对所有频繁执行的核心SQL使用OUTLINE加固执行计划将最大可能地避免SQL语句性能突然变差
注DBMS_OUTLN可以通过$ORACLE_HOME/rdbms/admin/dbmsolsql脚本来安装
使用SQL ProfileSQL Profile是Oracle g之后的新功能此处不再介绍请参考相应的文档
除此之外可以调整一些参数避免潜在的问题比如将_btree_bitmap_plans参数设置为FALSE(这个参数请参考互联网上的文章或Oracle文档)
而在实际工作中通过使用定制的统计信息收集策略以及在部分系统上使用OUTLINE系统基本上不会出现已有的SQL性能突然变差的情况当然也有维护人员操作不当引起的SQL性能突然变差比如建了某个索引而没有收集统计信息导致SQL使用了新建的索引而该索引并不适合于那条SQL维护人员意外删除了表个索引的统计信息