Oracle性能优化实战:基准测试与AWR案例复盘指南
接手一个数据库性能问题,最忌讳的就是上来就调参数、改SQL。没有基准,就没有对比;没有对比,你根本不知道改动是变好了还是变坏了。这套FGMT08课程的核心,其实就是把“基准测试+案例复盘”这条线走通——先量化现状,再定位瓶颈,最后用数据验证优化效果。这篇文章我就按实际做项目的方式,把Oracle性能优化里最常用的基准测试方法和几个典型案例的完整排查过程拆开讲,全程干货,可以直接拿来当操作手册用。
1. 项目背景与整体设计思路
1.1 为什么性能优化要先做基准测试
很多DBA和开发遇到数据库慢,第一反应就是看AWR、抓慢SQL,然后凭经验改。这个流程没错,但缺了最关键的一步——你手头没有“正常状态”的参考线。
举个例子,某天业务反馈订单查询变慢了,从200ms涨到2秒。如果你没有历史基线,你怎么判断这个2秒是SQL写法导致的、索引失效导致的、还是硬件资源被其他业务抢占导致的?等你东查西查,可能半天就过去了。但如果你在系统平稳期做过基准测试,存了一份AWR基线,一对比就能发现:原来是执行计划变了,或者是IO延迟从5ms飙到了50ms。
所以基准测试的本质,是给数据库拍一张“体检底片”。性能优化没这张底片,就像体检没有参考值,指标全是孤立的数字,毫无意义。
1.2 基准测试方案选型:从TPC-C到业务仿真
Oracle领域的基准测试,常见的有这么几类:
- 标准化基准测试:TPC-C、TPC-H这类行业标准测试,主要用于对比硬件和数据库版本的极限能力,但对普通业务系统的参考价值有限,因为你不可能把所有业务逻辑都抽象成标准模型。
- 工具压测:SwingBench、SLOB(Silly Little Oracle Benchmark)、HammerDB。其中SwingBench是Oracle官方推荐的免费工具,模拟订单录入场景,适合做OLTP型系统的压测;SLOB主打IO能力压测,适合看存储层的极限吞吐。
- 业务回放仿真:从生产库抓取真实SQL流量,用工具回放到测试库。这个最贴近实际,但成本高,一般只有大厂的核心系统才会这么做。
我个人的建议是:如果你只是做一个常规的性能评估和优化验证,用SwingBench就够了。它开箱即用,自带Order Entry场景,能模拟并发用户、事务混合比,而且生成的负载和真实OLTP业务非常接近。
1.3 案例环境与优化目标设定
这套课程的实战案例是基于Oracle 19c搭建的,两节点RAC,底层存储是SSD阵列。压测目标库单独部署一台测试服务器,避免和生产环境互相干扰。
在开始压测之前,先统一指标口径。性能优化最核心的指标就三个:
- TPS/QPS:每秒处理的事务数/查询数,衡量吞吐能力。
- 平均响应时间:单个事务从发起到完成的耗时,衡量用户体验。
- 资源利用率:CPU、内存、IO、网络四项,用于定位瓶颈在哪一层。
优化目标定得也很明确:TPS提升不低于30%,平均响应时间下降不低于50%,且数据库层面没有新的等待事件冒头。目标不量化,后面做完了你根本没法说清楚项目到底成功没有。
2. 基准测试环境搭建与执行
2.1 压测工具部署与数据初始化
SwingBench的部署本身不复杂,但有几个细节容易踩坑。
首先是JDK版本适配。SwingBench是基于Java的工具,不同版本对JDK版本要求不一样,我用的是JDK 11,配SwingBench 2.7,运行非常稳。如果你用的是很老的JDK 1.6/1.8,建议先升级,否则压测过程中可能出现内存溢出。
其次是测试用户和表空间的准备。建议单独创建一个专门的测试用户,分配独立的表空间,不要在系统表空间里跑压测。数据量也要提前规划好,Order Entry场景默认的数据规模分好几档,我一般选择将客户数设为10万级别、商品数1万级别、订单数50万级别,这样既能产生足够的数据扫描开销,又不会让压测前的数据加载耗时太久。
数据加载完别忘了收集统计信息。这一步经常被忽略,但如果统计信息不准确,优化器生成的执行计划就是乱的,压测结果根本没有参考价值。用DBMS_STATS.GATHER_SCHEMA_STATS把所有表的统计信息收一遍,同时加OPTIONS => 'GATHER AUTO'保证系统表和索引的统计信息一起更新。
2.2 压测场景设计与并发模型
SwingBench提供了多种业务场景,我在基准测试中主要用两个:
- Order Entry:模拟典型OLTP业务,包括订单创建、查询、客户信息更新、商品信息查询等,事务混合比接近真实交易系统。
- TPC-C-like:比Order Entry更重,写操作占比更高,适合测试数据库在写入压力下的表现。
并发用户数的设置有个经验值:从50并发开始,逐步增加,每次提升50,直到TPS不再随并发数提升而增长。这个点就是系统的拐点,也是我们做优化最关注的区域。
第一轮压测,我先跑50并发,每轮持续30分钟。为什么是30分钟?太短了,缓冲池和数据缓存还没热起来;太长了,浪费时间,而且长时间高负载对测试环境也是一种损耗。30分钟刚好够Oracle把热数据加载到Buffer Cache,得到一个相对稳定的性能数据。
2.3 基线数据采集:AWR快照与OSWatcher
压测进行的同时,数据采集要同步启动。这里我同时开了两套采集:
AWR快照。在压测前手动创建一个快照,压测结束后再创建一个快照,两个快照区间的数据就是这次压测的完整性能剖面。命令很简单:
OSWatcher。这个是操作系统层面的监控工具,用来采集CPU、内存、磁盘IO、网络的数据。AWR看的是数据库内部的视角,OSWatcher看的是操作系统外部的视角,两者一对照,就能判断瓶颈到底在数据库内部还是外部。比如AWR显示DB Time很高,但OSWatcher显示CPU利用率才30%,那问题大概率不是资源不足,而是SQL本身有问题,比如低效的执行计划在空转。
OSWatcher启动命令:
参数含义是每隔10秒采集一次,保留120小时的数据。压测30分钟,完全覆盖。
采集完的数据最终会汇总成一份基线报告。这份基线报告是后续所有优化动作的对照基准,务必存档保留,别压在临时目录里。
3. AWR报告分析与等待事件解读
3.1 核心指标怎么读数
压测结束后生成AWR报告,第一眼看什么?不是TOP SQL,而是四个核心数字。
DB Time。这是所有前台会话消耗的数据库时间总和。如果DB Time除以Elapsed Time再除以CPU个数得到的值远大于1,说明数据库有严重的等待,CPU根本忙不过来。
Load Profile。这里重点看Redo size、Logical reads、Executes三个值。Redo size反映了写入量,Logical reads反映了逻辑读压力,Executes是每秒SQL执行次数。第一次基线测试跑出来的这些数字,就是后面所有优化的对照值。
Instance Efficiency Percentages。重点看Buffer Hit Ratio和Execute to Parse ratio。如果Buffer Hit Ratio低于95%,说明物理读偏多,要么是内存太小,要么是SQL扫描了太多数据。Execute to Parse ratio如果很低,说明硬解析过多,要考虑绑定变量的问题。
TOP 5 Timed Events。这是等待事件排名的前五名,直接告诉我们数据库时间花在了哪里。我第一次跑50并发基线,TOP 3的等待事件是:
| 事件名 | 等待时间占比 | 说明 |
|---|---|---|
| DB CPU | 41.2% | CPU密集型负载,SQL执行本身消耗大量CPU |
| db file sequential read | 23.6% | 单块读,通常是索引扫描 |
| log file sync | 14.8% | 日志写等待,涉及提交频率或存储性能 |
这里能看出一个初步判断:CPU消耗最多,说明SQL还有优化空间;db file sequential read高,说明索引扫描频繁,但不一定是坏事,也可能是索引选择不当;log file sync达到14.8%,就偏高了一点,需要考虑提交频率和应用端的批量提交。
3.2 定位TOP SQL:不该只盯着执行次数
AWR报告里有SQL Statistics,分为几个维度:执行次数最多的、消耗CPU最多的、物理读最多的、执行时间最长的。
很多新手喜欢直接看执行次数,这是不对的。执行次数多不代表消耗大,有时候一个每秒执行几百次的简单查询,总消耗也不如一个十分钟才跑一次的重报表SQL。
我的分析顺序是:先看SQL ordered by Elapsed Time,找出累计消耗DB时间最多的SQL;再看SQL ordered by Gets,找出逻辑读异常高的SQL。这两个维度一交叉,基本能锁定问题SQL。
这次压测中,有一个订单查询SQL的Elapsed Time占了整个报告的三分之一。SQL长这样:
逻辑上没毛病,就是一个常规的订单汇总查询。问题出在customer_level = 'GOLD'这个过滤条件上,这个条件要回表到customers表去查,如果customers表上customer_level字段没有索引,那就要做全表扫描,把几十万行数据全部读进来过滤,再和orders表做HASH JOIN,代价非常大。
3.3 从执行计划确认问题根源
用DBMS_XPLAN查看这个SQL的执行计划:
执行计划显示,customers表走的是TABLE ACCESS FULL,全表扫描85万行,返回只有8000多行。这就是典型的高成本低选择率——扫了85万行才能筛出8000行,占比不到1%。
这其实就是我们平时说的“索引失效”或者“缺索引”场景。
4. 优化实施与效果对比
4.1 优化方案一:SQL改写与绑定变量
第一个优化点,也是最简单的一个,先把SQL的写法调整一下。
原SQL的TRUNC(SYSDATE) - 30有个隐藏问题:TRUNC(SYSDATE)作用在order_date这一列上,如果order_date上有索引,这个写法会阻止索引的使用。因为数据库必须对每一行的order_date做完TRUNC后再和SYSDATE - 30比较,索引就失去了快速定位的作用。
正确写法是反向操作,把函数转移到常量那一边:
这样order_date列上的索引就能正常使用了。这是一个非常经典的低级错误,经常发生在开发新手写的SQL中,优化效果却立竿见影。
另外,整条SQL的谓词部分使用了字面量,每次执行都要硬解析。高并发场景下硬解析的代价会被放大。建议在应用端改造时使用绑定变量,将customer_level和日期参数化。不过这是应用侧的改造,数据库层做不了。我在测试环境先不改这个,重点验证后面两个优化。
4.2 优化方案二:索引调优,但别盲目加索引
针对customers表全表扫描的问题,最直接的办法是加一个复合索引:
这里我特意把customer_register_date一起加进索引,而不是只建一个customer_level单列索引。为什么?因为SQL里还有ORDER BY o.order_id这个排序操作,而customers表大概率会按照主键顺序返回数据,如果我们能构造一个索引让优化器走INDEX RANGE SCAN然后直接按序访问,就可以顺带消除排序操作,进一步降低CPU消耗。
但索引不能乱加。每多一个索引,DML操作就多一次索引维护,INSERT/UPDATE/DELETE的性能会直接受到影响。所以加索引前一定要评估这个表上的写操作频繁程度。在这套压测场景里,customers表的更新比例很低,加索引的收益远大于代价。
4.3 优化方案三:检查order_items表的连接方式
再看执行计划中order_items和orders表的连接方式。原计划里是HASH JOIN,这在大表连接时是正常的,但如果orders表按order_date过滤后结果集很小,NESTED LOOP的代价会更低。
如何让优化器选择NESTED LOOP?关键在于order_items表上有order_id字段的索引。如果这个索引存在,优化器在评估时会多条路可选。我在测试环境上检查了一下,结果发现——order_items表上根本没有order_id的单列索引!只有和order_items主键绑定的复合索引,导致优化器只能用HASH JOIN硬扛。
于是补了一个索引:
这一步的效果也很明显,订单明细的关联查询从全量HASH变成了索引驱动的小批量查找。
4.4 复测结果:50并发、100并发、200并发三轮对比
优化实施完成后,我没有急着下结论,而是用完全一样的参数重新跑了一遍压测,同样是50并发、30分钟。然后再加跑100并发和200并发两轮,验证优化后的系统在更高压力下是否依然稳健。
结果对比如下:
| 指标 | 优化前(50并发) | 优化后(50并发) | 优化后(100并发) | 优化后(200并发) |
|---|---|---|---|---|
| TPS | 3256 | 4821 | 6890 | 7310 |
| 平均响应时间(ms) | 38.5 | 19.2 | 28.7 | 51.4 |
| CPU使用率 | 47% | 38% | 69% | 92% |
| db file sequential read占比 | 23.6% | 8.1% | 9.4% | 12.3% |
| log file sync占比 | 14.8% | 6.2% | 5.8% | 7.1% |
50并发下,TPS提升了48%,响应时间从38.5ms降到19.2ms,完成了预设目标。
注意一个细节:在200并发下,TPS的提升已经明显放缓,从100并发到200并发只涨了400多,但CPU使用率飙升到了92%,响应时间也从28.7ms涨到了51.4ms。这说明系统的瓶颈已经转移到了CPU层面,再往上加压,TPS不但不会涨,还会因为上下文切换开销而下降。这就是系统的性能拐点。
知道了拐点在哪里,容量规划就有的放矢了——生产环境的并发数设计在100~150之间是比较安全的区间。
5. 常见问题与排查技巧实录
5.1 压测过程中遇到ORA-28547,别急着怪配置
有一次压测过程中,应用端突然报ORA-28547: connection to server failed, probable Oracle Net admin error。这个错误看起来像是网络配置问题,很多DBA第一反应是去检查tnsnames.ora和sqlnet.ora,但那次排查下来,问题根本不是网络配置,而是Oracle Net服务没有完全监听新的连接。
实际情况是:压测启动瞬间,并发连接数从几十直接飙升到上百,监听进程的backlog队列满了,新连接排队超时。这时候不需要改任何配置文件,重启一下监听服务:
同时把数据库的processes和sessions参数调大,避免连接数到达上限。我记得那次是processes设的500,压测500并发时会顶到上限,后来调成1000,重启数据库实例后问题消失。
ORA-28547还有一个常见诱因是监听日志文件过大。监听日志长时间不清理,可能达到几个GB,影响监听性能。建议定期做日志轮转,或者把log_status设为OFF。
5.2 AWR报告里的db file sequential read突然暴增
用户现场反馈系统变慢,我们拉取AWR报告后发现db file sequential read占比从正常的10%以下飙升到40%。这个等待事件代表单块读,通常和索引扫描相关,但如果暴增,要小心一种情况:索引被大量热块争用。
我当时的排查思路是:先看TOP SQL里有没有新的SQL出现,再看TOP Segments里哪个段的buffer busy waits高。结果发现是orders表的一个索引段,因为SQL里加了新的过滤条件,导致大量会话对同一个索引块做并发扫描,产生了严重的块级争用。
解决方案不是改SQL,而是把普通索引改成了反向键索引,把索引块的分布打散:
这里要注意,反向键索引只能用于等值查询,范围查询就用不了了。所以改造前先和开发确认了SQL的访问模式——等值查询居多,那就可以放心用。
5.3 优化后TPS不升反降,可能是执行计划被固定住了
还有一个很坑的情况:某次优化做完,SQL执行计划明明显示走了新索引,但复测结果比优化前还慢。查了半天发现,有人之前在SQL上绑定了SQL_PROFILE,硬编码了一个旧的执行计划,导致优化器没采用新索引。
遇到这种情况,先把旧的profile清掉:
然后再刷新一下共享池中的游标:
清完之后重新跑压测,新执行计划生效,性能就恢复正常了。
这个案例给我们的教训是:任何对执行计划的“人为干预”,都必须有完整的变更记录。如果团队里多人有权限操作数据库,建议在变更流程中加上一条——所有SQL执行计划的绑定动作必须走审批,防止脏数据污染基线。
5.4 统计信息过期导致优化器选错执行计划
第100并发压测的时候出现过一次诡异现象,TPS突然降低了一半,TOP等待事件全变成了enq: TX - index contention。看起来很像是索引热点问题,但查下来发现是统计信息太旧。
测试库的customers表在压测前已经导入了大量数据,但没有重新收集统计信息。优化器还按照旧的行数估算,认为customers表只有几万行,选择了全表扫描。而实际表已经有80多万行,全表扫描的代价翻了十倍。
解决方法:
收集完毕后,重新查看执行计划,优化器自动选择了索引扫描,TPS恢复正常。
这也解释了我在数据初始化时强调“必须收集统计信息”的原因。很多时候性能问题根本不是硬件或SQL的锅,而是一张表的统计信息过期了。建议生产环境启用自动统计信息收集任务,同时在大批量数据加载后手动执行一次收集。
6. 常见问题速查表
整理了一份我在实际项目中反复用到的问题速查表,遇到类似情况可以直接对着排查。
| 现象 | 可能原因 | 排查命令/方法 | 解决方向 |
|---|---|---|---|
| AWR中db file sequential read高 | 索引缺失、索引选择不当 | 查TOP SQL执行计划中TABLE ACCESS FULL的段 | 补索引或改写SQL |
| log file sync高 | 提交过于频繁、日志文件IO慢 | 查redo log文件所在磁盘IO延迟 | 批量提交、日志文件搬到高速存储 |
| DB CPU接近100%,TPS上不去 | SQL低效、绑定变量缺乏 | 查SQL ordered by CPU | 优化SQL、使用绑定变量 |
| 并发数增加但TPS平稳或下降 | 系统达到性能拐点 | 观察CPU和等待事件 | 扩容、限流、缓存 |
| 压测中大量连接超时 | 监听backlog满、processes不足 | lsnrctl status、查processes参数 | 调大processes、重启监听 |
| 执行计划不走新索引 | SQL_PROFILE或OUTLINE绑定了旧计划 | 查询dba_sql_profiles | 删除绑定、刷新共享池 |
| 统计信息过期 | 数据量变化大 | 查dba_tables.last_analyzed | 重新收集统计信息 |
| 块争用导致buffer busy waits高 | 索引热点、段分配不合理 | 查dba_segments中的buffer busy waits | 反向键索引、分区表、增加 freelists |
大多性能问题到最后归结起来无非两类:要么是SQL写得不好,要么是执行计划选得不对。而这两类问题,都必须在基准测试的框架下去定位和验证,否则你的优化动作就是无根之木。
我个人在做数据库优化的一个核心体会是:永远保留一份压测环境和固定脚本。很多项目做完了,压测脚本就删了,环境也拆了。等到半年后系统又出问题,想复现当时的压测都没办法。把SwingBench的配置、AWR脚本、OSWatcher采集命令固化成一个shell脚本,放在版本管理里,任何时候都能一键拉起来跑一轮基准测试,这才是性能优化工作真正的沉淀。改完SQL、调完参数,用同一个脚本再跑一遍,结果一目了然。这个方法这套课程里反复强调过,实际工作中我也是这么执行的,省了无数时间和扯皮。
相关推荐
基于Python+Open CV的手势识别算法设计
伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。
ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像
ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)
计算机视觉图像处理-CV2与图像插值算法
用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int
基于水平集CV模型的图像分割(Matlab代码)
基于水平集CV模型的图像分割(Matlab代码),全套代码
导向滤波(guide_filter)的python实现
导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

