做了一大半年的业务数据库优化了,有一点感想,写出来自励一下。
公司开发的产品基本上都使用ORACLE数据库,而且业务系统中存储和使用的数据量很大,使用业务系统的用户也很多。在系统忙时,大约会有一千个用户同事访问数据库系统,因此经常会有用户抱怨系统慢,点击查询后,系统出现假死机现象,后台运行ORACLE系统的小型机的IDLE值偏低(甚至出现IDLE为0的情况,这时用户会惊呼数据库系统会瘫掉,虽然小型型机在IDLE值为0的情况下也不一定会瘫机,但这无疑会增加用户的担忧),系统运行在一个不安全的状态等等。
对于我现在所做的ORACLE优化,其实还停留在SQL优化的层次(以前我的前辈曾给我说关于数据库优化的三个层次:一是针对SQL的优化,如使用正确是索引,使用ORACLE提示等;二是针对数据库对象的优化,如增加索引,微调表结构等;三针对业务的优化,需要更改业务逻辑或者表结果,此类优化一般代价比较大,一般很少针对正在运行的系统做类似的操作)。
公司的客户很多,大多数情况会在公司远程处理客户的数据库问题。作为问题的定位者,一定要搞清楚实际运行的数据库系统到底出现了什么问题。现场的维护人员和用户最喜欢使用的形容词是:系统很慢;数据库锁表了等等。用户向外面传达的信心往往是非常模糊的,在开始接触这个问题时候起,我们就要引导用户去帮外面了解系统真实的情况。有几个东西是一定要看看的,一个是ORACLE系统的警告日志文件,在系统运行的有问题或者是忙时的几份STATSPACK报告(一般间隔时间10到15分钟),UNIX下SAR命令的输入结果(该命令可以按既定的时间间隔收集小型机系统CPU的使用情况)。通过这三个从现场收集的结果,我们基本可以了解现场数据库的运行情况。
其中ORACLE系统的警告日志文件能让我们了解ORACLE系统运行有没有一些重大的问题。
STATSPACK报告中概括了数据库系统的运行基本情况,其中关于如何解读报告可以写一本书了,不过我们首先要关注的是报告中有一段“Top 5 Timed Events”,这一段描述了当前ORACLE主要的等待事件是什么(关于ORACLE等待事件的概念可以参考相关的资料)。
SAR命令的输入我们要关注三个输出的分布情况USR、WIO、IDLE。其中SYS+USR+WIO+IDLE应该等于100%,USR占的比例高,一般说明SQL语句执行效率有问题,这种问题一般是由于索引选择性不高、表连接顺序和方式不对等等;WIO高一般说明SQL语句频繁进行I/O操作。对于具体的问题,则需要对具体的SQL语句进行分析,在分析过程中,阅读执行计划是我们的一个重要的工具。
在对ORACLE系统的整体情况有了一定的了解之后,下一步需要分析的就是系统运行过程效率不高的SQL,这是对业务优化的一个起点。如果这时不能够在实际系统中操作,了解SQL的运行过程是一个比较费时的过程的。不管怎么样,对于我们怀疑有问题的SQL,首先要阅读的就是该SQL的在实际系统中的执行计划,语句涉及到的表的数据量,访问表使用索引的选择性如何,表连接的顺序,多表之间的关联关系等等。
对于ORACLE应用系统的优化,大方向上有一个顺序,首先考虑优化业务系统、再考虑优化ORACLE系统本身的参数(如内存分配等),再考虑操作系统本身的优化;在优化业务系统中,首先是首先相关的SQL,以SQL入手分析表是否缺少索引,表连接顺序是否正确,使用的索引是否正确等,然后再考虑调整表结构,调整业务逻辑等等。因此,SQL语句是我们对一个ORACLE业务系统进行优化的敲门砖。
对于SQL优化,前面提到了,ORACLE的执行计划是我们必须使用的工具。本来按ORACLE系统本身提供的方法获取执行计划是一件非常麻烦的事情,不过现在的可视化工具比如PL/SQL DEVELOP或者TOAD等都给我们提供了非常方便的手段来获取SQL语句的执行计划,不过我认为ORACLE本身提供的方法还是有必要会的,特别是在远程处理问题的时候(我也不会,一定要学习一下)。
获取有性能问题的SQL语句,我们主要有两个途径,一个是通过STATSPACK报告。报告中有两节是我们需要重点关注的:《SQL ordered by Gets for》和《SQL ordered by Reads for DB》,这两节中分别按语句读取内存数据库块和读取的物理数据库块(数据库块是指ORACLE的块大小,一般是操作系统最小块的整数倍)的数量倒序排列,如果其中的语句不全(太长),可通过HASH_VALUE值到ORACLE的动态视图V$SQLTEXT中获取该SQL的全部语句。第二个是通过ORACLE系统的动态视图,V$SQL,该视图记录了每个SQL语句的执行次数,物理读和内存读、执行时间等等很多SQL语句的执行信息,可以通过如下语句选择一下物理读和内存读较高的语句:
引用:
SELECT
t.HASH_VALUE,
t.EXECUTIONS,
t.DISK_READS,
round(t.DISK_READS/t.EXECUTIONS) AS perDiskReads,
t.BUFFER_GETS,
round(t.BUFFER_GETS/t.EXECUTIONS) AS perBufferReads,
t.ELAPSED_TIME,
round(t.ELAPSED_TIME/t.EXECUTIONS) AS perElayTime,
t.CPU_TIME,
round(t.CPU_TIME/t.EXECUTIONS) AS perCpuTime,
t.FIRST_LOAD_TIME,
t.SQL_TEXT
FROM v$sql t
WHERE (t.DISK_READS/t.EXECUTIONS >
500 OR t.BUFFER_GETS/t.EXECUTIONS > 20000)
AND t.EXECUTIONS > 0
ORDER BY 6 DESC;