前言
随着网络、电子商务和信息化管理的不断发展,各个网络站点和系统的访问量、数据量也都越来越大。对软件开发服务性能的提高提出了很大挑战,尤其对数据访问的响应时间和吞吐量提出了很高要求。其实这也就是我们一直强调的数据库性能优化问题。数据库的性能优化一直是数据库管理中的重要环节之一,也是最复杂的内容之一。对于数据库管理人员而言,数据库建立起来后,最主要的工作主要有3个:系统优化、数据恢复和数据备份。要实现高性能地运行数据库,优化性能至少占了70%的工作量。而目前大多数信息量较大的网站或系统都不约而同地采用Oracle数据库作为其的数据管理软件。那Oracle数据库又是如何来使有限的计算机系统资源为更多的用户服务?如何保证用户的响应速度和服务质量呢?
本文将结合本人这段时间参与项目的实际维护经验,相应地提出实际项目数据处理的一些优化方法,仅供参考。
*注:Oracle版本不同可能具体的设置参数或形式不同。
Oracle数据库优化概述
不同需求的应用,Oracle数据库的优化侧重点也有所不同,优化方案也有所不同。例如我们现在这个项目主要关心的是每次从画面查询开始到数据在画面上展现出来的时间长短及事务处理的时间长短。
根据我们软件开发及Oracle数据库本身的特点,我们可以把数据库的优化工作划分为2个阶段:初始优化参数设置、针对性的微优化。在初始优化参数设置时,我们只能根据已有的计算机系统资源情况,预测的系统运行情况,结合以往的经验,给出一个经验设置,大体上来说,这种经验设置离满足优化需求的目标不是很远,但还是有一定的距离。
在完成了初始优化设置后,经过一段时间的业务运行,已可开始收集实际运行环境的性能数据。此时,就可以对各种Oracle性能指标、各种关心的数据查询或事务执行进行性能评估,然后进行进一步的微优化。
Oracle优化,不是一个一蹴而就的工作,也不是一个一劳永逸的工作,需要定期维护,定期观察,在发现性能瓶颈时及时进行调整。Oracle总是存在性能瓶颈的,不使用、不操作的数据库总是最快的,在解决当前瓶颈后,总是会有另一个瓶颈出现,所以在优化前,我们需要确定一个优化目标,我们的目标是满足我们的应用性能要求就可以了。
Oracle优化,涉及的范围太广泛,包含的有主机性能、内存使用性能、网络传输性能、SQL语句执行性能等等。从我们面向网管来说,满足事务执行速度性能主要表现在:
1)批量重复的SQL语句执行性能(主要是通过Procedure计算完成数据合并和数据汇总的性能和批量数据采集入库的性能);
2)一些单次、不常用的操作的语句执行性能(主要是GUI的非规律操作)。
根据这两个特点,我们可把优化方法归纳到3个重要方向:
1)内存等参数配置的优化。内存优化,是性能受益最快的地方。
2)减少物理读写的优化。内存逻辑I/O操作的时间,远远小于物理I/O的操作时间。
3)批量重复操作的SQL语句及大表操作的优化。减少SQL执行次数,减少大表操作次数。
下面主要针对得益最大的这三个方向的优化进行阐述。
1.内存等参数配置的优化
对于大多数需求应用来说,最直接、最快速得到性能优化收益的,肯定是在内存分配环节的优化。给每个Oracle内存块分配合理的大小,可以有效的使用数据库。通过观察各种数据库活动在内存里的命中率,执行情况,我们能很快的掌握数据库的主要瓶颈。我们可以从下面对一条SQL语句的执行步骤进行分析还获得道。
一个SQL语句,从发布到执行,会按顺序经历如下几个步骤:
1)Oracle把该SQL的字符转换成它们的ASCII等效数字码。
2)该ASCII数字码被传送给一个散列算法,生成一个散列值。
3)用户server process查看该散列值是否在shared pool内存块中存在。
若存在:
4)使用shared pool中缓存的版本来执行。
若不存在:
4)检查该语句的语义正确性。
5)执行对象解析(这期间对照数据字典,检查被引用的对象的名称和结构的正确性)。
6)检查数据字典,收集该操作所引用的所有对象的相关统计数据。
7)准备执行计划,从可用的执行计划中选择一个执行计划。(包括对stored outline和materialized view的相关使用的决定)
8)检查数据字典,确定所引用对象的安全性。
9)生成一个编译代码(P-CODE)。
10)执行。
这里,通过内存的合理分配,参数的合理设置,我们主要解决:
1)减少执行到第五步的可能,节约SQL语句解析的时间。第五步以后的执行过程,是一个很消耗资源的操作过程。
2)通过内存配置,尽可能让SQL语句所做的操作和操作的数据都在内存里完成。大家都知道,从内存读取数据的速度,要远远快于从物理硬盘上读数据,一次内存排序要比硬盘排序快很多倍。
3)根据数据库内存活动,减少每个内存块活动的响应时间,充分利用每个内存块,减少内存latch争用发生的次数。
2.减少物理读写的优化
无论如何配置Oracle数据库,我们的网管系统,每小时周期性的都会有新数据被处理,就会发生物理读写,这是避免不了的。
减少物理读写的优化,一般所用的方法有:
1) 增加内存data buffer的大小,尽可能让数据库操作的数据都能在内存里找到,不需要进行物理读写操作。
2) 通过使用索引,避免不必要的全表扫描。
3) 大表物理分区,Oracle具有很好的分区识别功能,减少数据扫描范围。
上述3个方法,是从整体上改善数据库物理I/O性能最明显的3个方法。能非常快速的减少数据库在物理I/O,最直接的反应是数据库事务执行时间能能以数量级为单位减少。
其他的一些减少物理读写的优化方法,比如使用materialized view,Cluster等方法;还有一些分散I/O的方法,比如 Oracle日志文件不与数据文件放在一个物理硬盘,数据热点文件物理I/O分开等等方法,就目前我们的网管系统而言,能得到的效果不是很明显,在网管系统中,为了不增加数据库维护的复杂性,不推荐使用。
3.批量重复操作的SQL语句及大表操作的优化
批量重复执行的SQL语句,一般出现在每个周期时间内的数据批量入库的insert语句,和数据合并、汇总的周期性select、delete、insert操作。
我们需要注意以下几点:
1) 减少不必要的SQL语句执行和SQL语句的执行次数。
每条SQL语句执行,都会消费系统资源,都有执行时间。减少不必要的SQL语句执行和减少SQL语句的执行次数,自然能减少业务执行时间。需要根据业务流程,重新设计数据处理的代码。此方法主要适用于procedure执行的数据合并、汇总。
2) 这些SQL语句,由于每个SQL语句都要执行很多次,应该尽量让该SQL的散列值在shared pool内存块中存在。也就是使用动态SQL,避免SQL硬解析。
可通过Oracle参数的设置,和动态SQL语句的应用,通过绑定变量的方式,减少SQL语句的解析次数。
3) 减少大表的操作,确保在一次事务中,同类操作只对大表执行一次。主要在数据合并和数据汇总的pprocedure和数据采集时出现。
- procedure:在我们的应用中,cell数据和traffic数据,以及link,linkset,trunkgroup等数据,都属于大数据量业务,对他们的每次操作,执行的时间都不短(即使做了表分区,减少数据扫描范围,操作的响应时间还是大于一个同类大小的普通表)。在多次执行的一个procedure中,通过使用临时表的方式,把多次大表操作,改造成一次大表操作,能极大的缩短业务执行时间。
- 数据采集:如果是普通的每条insert语句完成一条数据入库,为了保证数据的唯一性,每次入库的时候,都会判断该条数据是否存在数据库,造成对表不必要的多次操作。对于批量数据入库,我们推荐使用sqlldr的方法进行数据拷贝,能极大的提高数据入库时间。
Oracle数据库优化方案
1.内存等Oracle系统参数配置
Oracle 的parameter参数,分动态参数和静态参数,静态参数需要重新启动数据库才能生效,动态参数不需要重新启动数据库即可生效。
Oracle可以使用spfile的特性,使用alter system set 参数名=参数值 scope=both[spfile];的方法进行修改。也可以直接修改pfile。
以下给出了网管Oracle 数据库重点关注的parameter的初始优化设置。
n 最大可使用的内存SGA总和
静态参数sga_max_size=物理内存的大小减1.5G
n Shared pool
动态参数SHARED_POOL_SIZE= 600 ~ 800 M
静态参数SHARED_POOL_RESERVED_SIZE= 300 M
动态参数OPEN_CURSORS= 400 ~ 600
静态参数CURSOR_SPACE_FOR_TIME= TRUE
静态参数SESSION_CACHED_CURSORS= 60 ~ 100
动态参数CURSOR_SHARING= SIMILAR
n Data buffer
动态参数DB_CACHE_ADVICE= READY
动态参数DB_CACHE_SIZE
动态参数DB_KEEP_CACHE_SIZE
动态参数DB_RECYCLE_CACHE_SIZE
(SGA_MAX_SIZE大小,除了分给所有非DATA BUFFER的SIZE,都分配给DATA BUFFER)
n Sga other memory
动态参数LARGE_POOL_SIZE= 50 M
静态参数JAVA_POOL_SIZE= 100 M
动态参数LOG_BUFFER= 3 M
n Other memory
动态参数SORT_AREA_SIZE= 3 M
静态参数SORT_AREA_RETAINED_SIZE= 0.5 M
静态参数PGA_AGGREGATE_TARGET= 800 M
动态参数WORKAREA_SIZE_POLICY= AUTO
n 磁盘I/O配置
静态参数SQL_TRACE= FALSE
动态参数TIMED_STATISTICS= TRUE
动态参数DB_FILE_MULTIBLOCK_READ_COUNT= 16
静态参数DBWR_IO_SLAVES= 0
静态参数DB_WRITER_PROCESSES= 3
静态参数UNDO_MANAGEMENT= AUTO
动态参数UNDO_RETENTION= 7200
2.使用索引
我们初步定义,表数据超过1000行的表,我们都要求使用索引。(不区分事务操作的数据在表数据中所占的比例)
索引所包含的字段不超过4个。
检查SQL语句是否使用了索引,我们使用execute plan来看,获得explain的方法,我们通过SQL*PLUS工具,使用如下命令进行查看:
set autotrace on
set autotrace traceonly explain
set timing on
或通过SQL*PLUS trace,然后查看user_dump_dest下的跟踪文件,使用tkprof工具格式化后阅览。
alter session set events '10046 trace name context forever,level 12';
alter session set events '10046 trace name context off';
SELECT p.spid,s.username FROM v$session s,v$process p WHERE s.audsid=USERENV('sessionid') AND s.paddr = p.addr;
使用方法示例:
sqlplus perf/perf
SQL*Plus: Release 9.2.0.6.0 - Production on 星期二 7月 19 21:25:19 2011
Copyright (c) 1982, 2005, Oracle Corporation. All rights reserved.
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.6.0 - Production
SQL> set timing on
SQL> set autotrace on
SQL> select count(*) from perf_sdcch_nn where start_time = (select max(start_time) from perf_sdcch_nn);
COUNT(*)
----------
638
Elapsed: 00:00:00.80
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=8)
1 0 SORT (AGGREGATE)
2 1 INDEX (RANGE SCAN) OF 'IDX02_PERF_SDCCH_NN' (NON-UNIQUE)
(Cost=2 Card=1495 Bytes=11960)
3 2 SORT (AGGREGATE)
4 3 INDEX (FULL SCAN (MIN/MAX)) OF 'IDX02_PERF_SDCCH_NN'
(NON-UNIQUE) (Cost=1 Card=3852090 Bytes=30816720)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
15 consistent gets
0 physical reads
0 redo size
492 bytes sent via SQL*Net to client
656 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
SQL>
从上面的示例我们可以看到,该SQL语句执行花了0.8秒,select语句和inline view,都使用了名为'IDX02_PERF_SDCCH_NN 的索引。物理读为0,redo log size为0,没有生成REDO日志。
3.表分区
在网管数据库里,比较突出的大表有小区表和告警表。
n 性能表,使用范围分区。
以时间点start_time为范围分区字段。
n 告警表,使用range-hash的混合分区和范围分区。
范围分区以时间点starttime为分区字段,混合分区增加ALARMNUMBER为字段的hash子分区。
同时,创建本地分区索引。
范围分区示范:
drop table part_mid_cell_traf;
create table part_mid_cell_traf;
(
NE_ID CHAR(16) NOT NULL,
....................
TCHSEIZE FLOAT(126)
)
partition by range(start_time)
(
partition mid_cell_traf_110719 values less than (to_date('2011-07-19 00:00:00','yyyy-mm-dd hh24:mi:ss')) tablespace perf_partition42,
partition mid_cell_traf_110731 values less than (to_date('2011-07-31 00:00:00','yyyy-mm-dd hh24:mi:ss')) tablespace perf_partition43,
partition mid_cell_traf_error values less then (maxvalue) tablespace perf_partition44
)
enable row movement;
CREATE INDEX local_mid_cell_traf ON part_mid_cell_traf (ne_id,cell_id,start_time,stop_time)
LOCAL
(
partition l_ind1_mid_cell_traf_110719 tablespace perf_partition42,
partition l_ind1_mid_cell_traf_110731 tablespace perf_partition43,
partition l_ind1_mid_cell_traf_error tablespace perf_partition44
);
Range-hash混合分区示范:
create table part_ALARMTEXTDATA;
(
ALARMNUMBER VARCHAR2(16) NOT NULL,
......
SERIAL NUMBER(38) NOT NULL
)
partition by range (STARTTIME)
subpartition by hash(ALARMNUMBER)
subpartition template
(
subpartition atd01 tablespace alarm_partition01,
subpartition atd02 tablespace alarm_partition02,
subpartition atd03 tablespace alarm_partition03,
)
(
partition ALARMTEXTDATA_110719 values less than (to_date('2011-07-19 00:00:00','yyyy-mm-dd hh24:mi:ss')),
partition ALARMTEXTDATA_110731 values less than (to_date('2011-07-31 00:00:00','yyyy-mm-dd hh24:mi:ss')),
partition ALARMTEXTDATA_error values less then (maxvalue)
)
enable row movement;
CREATE INDEX local_ALARMTEXTDATA ON ALARMTEXTDATA (STARTTIME,ALARMNUMBER,SERIAL)
LOCAL
(
partition l_ind1_atd_110710 tablespace alarm_partition02,
partition l_ind1_atd_110719 tablespace alarm_partition03,
partition l_ind1_atd_110731 tablespace alarm_partition04,
);
4.Procedure优化
1)取消地市一级的Procedure,只保留其上层调用Procedure,并保持参数输入方法,调用方法不变。
2)确保大表数据查询操作只有1次,确保大表数据删除只有一次。
3)确保单条SQL语句执行已优化。
4)减少SQL执行次数。
5.其他改造
1) 修改表存储参数,提前预先分配extents。
2) 修改表空间存储参数(采集表空间所用块设置为大块,比如32k一个块;修改ptcfree,pctused,pctincrease等)。
3) 避免使用唯一索引和非空约束。
4) 创建合理的索引。
5) 各模块SQL语句优化,比如使用提示固定索引等。
6) 确认每一条历史数据删除语句已优化和删除方法。
7) 临时表的使用。
6.维护作业计划
表分析(包含确定具体的表的分析方法,分区表分析方法,索引分析方法)。
空间回收维护(包括确定HWM,回收多余分配给表的块,合并数据块碎片等)。
索引维护(包括定期重建索引,索引使用情况监视等)。
历史数据删除检查(检查保存的数据是否符合要求,检查历史数据删除方法是否正确-比如批量删除提交的方法等)。
全库性能分析和问题报告及优化(比如使用statspack进行性能趋势分析,检查有问题的SQL或事务,确定当前系统等待的top 5事件等等)。
表数据keep,default及reclye(比如把一些常用的配置表固定在内存里等)。
数据库参数核查(防止数据库参数被修改,定期对系统配置参数进行比较)。
日志文件分析(定期检查Oracle生成的日志文件,定期备份、删除)。
硬盘空间维护(定期对Oracle 对象使用的空间情况进行监视)。
Oracle数据库优化前后比较
1.批量重复的SQL语句执行性能
根据网元数量,各地的执行的完成时间有所区别。
n 用于数据合并和汇总的Procedure的计算性能
通过statspack的周期性采集数据,我们可以使用以下语句,计算我们想统计的Procedure的执行情况:
SELECT TO_CHAR(SN.SNAP_TIME, 'YYYY-MM-DD HH24:MI:SS') AS SNAP_TIME,
S.DISK_READS,
S.BUFFER_GETS,
S.ELAPSED_TIME / 1000000 AS ELAPSEDTIME
FROM (SELECT HASH_VALUE, SQL_TEXT, ADDRESS, LAST_SNAP_ID
FROM STATS$SQLTEXT
WHERE PIECE = 0
AND SQL_TEXT LIKE '%&SQLTEXT_KEY%') T,
(SELECT ADDRESS,
HASH_VALUE,
SNAP_ID,
SQL_TEXT,
DISK_READS,
EXECUTIONS,
BUFFER_GETS,
ROWS_PROCESSED,
ELAPSED_TIME
FROM STATS$SQL_SUMMARY) S,
STATS$SNAPSHOT SN
WHERE S.HASH_VALUE = T.HASH_VALUE
AND S.ADDRESS = T.ADDRESS
AND S.SNAP_ID = T.LAST_SNAP_ID
AND SN.SNAP_ID = S.SNAP_ID;
比如,我们以perfstat用户执行该SQL,输入“to_comp”,可以观察到数据库里保存的有的to_comp存储过程的执行时间,我们发现,其执行时间,从优化前的几千秒,最后稳定在优化后的几十秒。
注:to_comp是整体调用执行一次所有网元的数据合并和汇总的procedure。
n 用于小区分析数据的Procedure的计算性能
使用上面的方法,我们一样可以知道,小区分析的procedure执行,从优化前的约几千秒,最后稳定在优化后的几十秒。
n 批量数据采集入库性能
使用bcp,能从以前约15分钟,减少到约4分钟。
2.一些单次、不常用的操作的语句执行性能
GUI上的性能数据查询,告警数据查询,响应时间都极快,几乎不再出现长时间等待响应的情况。
参考
statspack
sql*plus
PL/SQL的执行计划