前阵子帮一家客户做YashanDB上线前的全链路压测,业务方上来就问“这数据库到底行不行”,我说先别急着下结论,咱们把吞吐量、响应时间、并发能力、CPU、内存、磁盘和锁冲突这7个维度全摸一遍,再谈优化。YashanDB作为兼容Oracle语法、主打企业级高可用的国产数据库,性能表现不是靠一两个测试点就能拍板的,资源优化策略更得建在真实指标上。这篇文章想分享的就是这套我自己在压测和运维中反复用过的评估框架,从头到尾怎么测、怎么看、怎么调,以及那些只有踩过坑才知道的细节。
1. 为什么是这7个指标:评估思路与选型逻辑
1.1 从一次性能风波说起
有次一个核心交易系统切换到YashanDB后,白天跑得好好的,一到整点批量任务就卡死。开发第一反应是“数据库太弱”,我上去查了一圈,发现CPU没满、内存没用满、磁盘I/O也不高,但活动会话全部堵在锁等待上。这就是典型的只盯着资源利用率做判断,结果被锁冲突这个隐性指标背刺的案例。所以评估一个数据库,不能只看某个指标“高不高”,而是要看一组指标之间能不能相互印证。
后来我把这套评估思路固定成了7个维度:吞吐量(QPS/TPS)、响应延迟、并发能力、CPU利用率、内存利用率、磁盘I/O、锁冲突率。每一个维度都不是孤立存在的——吞吐量上不去可能是CPU瓶颈,也可能是锁冲突拖慢了事务提交;响应时间变长可能源于磁盘I/O抖动,也可能是执行计划走了坏索引。只有把7个指标放到同一张时间轴上对照,才能定位真正的瓶颈。
1.2 7大指标的定位与分工
这7个指标可以分成三类:一类是业务感知指标,吞吐量和响应延迟直接决定用户体感;一类是资源承载指标,CPU、内存、磁盘I/O、并发能力反映系统能扛多大量;还有一类是内部健康指标,锁冲突率代表并发事务之间的内耗程度。
| 指标 | 观察对象 | 反映的问题类型 | 一句话解读 |
|---|---|---|---|
| QPS/TPS | 每秒完成的请求/事务数 | 业务吞吐上限 | 能不能接住这么多流量 |
| 响应延迟 | 平均、P95、P99耗时 | 用户体验 | 快不算快,稳定才算快 |
| 并发能力 | 活跃会话数、连接池状态 | 系统承载规模 | 人多了还能不能稳住 |
| CPU利用率 | %CPU、内核态/用户态占比 | 计算资源瓶颈 | 是算不过来还是没活用 |
| 内存利用率 | 缓冲命中率、SGA/PGA使用 | 数据访问效率 | 热点数据是否进了缓存 |
| 磁盘I/O | IOPS、延迟、吞吐量 | 存储层慢路径 | 数据落盘是不是拖后腿 |
| 锁冲突率 | 锁等待次数、平均等待时间 | 事务内耗 | 并发之间有没有互相踩脚 |
这个分类直接影响优化策略的优先级。我在实际项目里的习惯是:先把业务感知指标压到合理范围,再看资源承载指标有没有水位过高,最后查锁冲突这类内部健康指标。如果上来就调内存参数,结果发现瓶颈在锁等待,那就是南辕北辙。
2. 吞吐量:压测第一件事看它
2.1 QPS/TPS怎么测才准
测吞吐量最忌讳的就是“一把梭”,开个压测工具直接灌满,然后看数据库死没死。正确做法是先定业务模型,再分梯度加压。比如模拟一个电商交易系统,读接口占70%、写接口占30%,事务比例按实际业务来定,不能全用select 1这种无脑查询。YashanDB兼容Oracle语法,所以压测工具的选择很宽泛:sysbench、JMeter、BenchmarkSQL都能用,我自己常用sysbench的oltp_read_write和oltp_insert模式做基础测试,再用业务压测脚本做验证。
测量窗口也有讲究。单次压测至少要跑5分钟以上,取稳定段的数据,不要看启动前30秒的“虚高”值。因为数据库有缓存预热、连接池初始化、执行计划编译这些前期过程,前几十秒的QPS往往会比稳定期高出不少。我见过有人拿1分钟压测数据写报告,结果上线后实际吞吐只有报告里的70%,就是吃了这个亏。
2.2 读多写少与写多读少的场景差异
YashanDB对读写混合负载的处理,和单纯读压测是两码事。读多写少时,QPS会很好看,但TPS和提交延迟才是关键。因为每个写事务都要经过日志写入、事务提交、数据落盘这几道工序,写比例一上来,吞吐量立刻就会掉一个台阶。
压测时我会专门跑两组:一组是oltp_read_only,一组是oltp_read_write,对比两组数据就能算出写事务对系统资源的额外消耗。有次测试结果里,读压测QPS能达到13万,读写混合后TPS只有1.8万,表面看吞吐量跌得厉害,但单事务耗时只从0.4毫秒涨到2.1毫秒,说明系统本身没瓶颈,主要是写路径本来就比读路径重。如果这时候盲目加CPU,不如去优化redo日志的刷盘策略。
2.3 实操记录:一次YashanDB压测示例
当时我用的是8核32G的虚拟机,两节点RAC模式。先按照默认配置跑,sysbench的oltp_read_write测得TPS大概1.2万,P95延迟8.5毫秒。整体看起来还行,但CPU平均已经跑到78%,接近警戒线。后来把测试表的索引和数据都调整了一下,把一些冗余索引删掉,TPS提到1.55万,同时CPU降到了67%。这说明吞吐量指标本身不能只看数字,还要和资源消耗绑定看“单位资源的产出”。
这里也有个要注意的点:压测时如果TPS曲线出现锯齿状波动,往往不是数据库的问题,而是压测客户端线程数不均衡或者网络有抖动。建议先压测客户端,再用多台压测机分散压力,避免把客户端瓶颈算到数据库头上。
3. 响应延迟:用户体验的真相
3.1 平均延迟会骗人,分位数才是硬道理
很多团队汇报性能喜欢说“平均响应2毫秒”,但用户那边明明感觉卡顿。原因很简单,平均延迟被少数极快请求拉低了,真正影响体验的是那些慢请求。我在YashanDB上做延迟评估时,必看P95和P99,也就是95%和99%的请求都在多少毫秒以内。P99如果超过100毫秒,哪怕平均只有5毫秒,线上也会有不少用户投诉。
举一个实测数据:一次压测中平均延迟3.2毫秒,P95也是5.6毫秒,很漂亮,但P99直接跳到230毫秒。排查后发现有少量会话在做全表扫描,这些慢SQL拖垮了P99。如果不看分位数,这个问题在报告阶段根本不会被发现。
3.2 延迟测试的采样与并发梯度
测延迟不能只跑一个并发数。我的习惯是从10个并发开始,依次增加20、50、100、200、500,每个梯度跑1分钟。低并发下延迟差异不大,高并发下才能看出系统的拐点在哪里。YashanDB在并发从100涨到200时,P99如果出现断崖式上升,说明临界点就在这附近。
还要注意采样方式。压测工具往往会自动丢弃超时请求,导致报告里的延迟比真实情况乐观。我建议把超时请求单独统计,和成功请求的延迟分开看。如果超时率超过0.1%,即使平均延迟很低,这个系统也不能直接承接生产流量。
3.3 缓慢查询与网络往返
响应延迟不只是数据库的事。有一次客户反馈YashanDB接口慢,查了数据库AWR和等待事件,一切正常。最后用tcpdump一抓包,发现应用服务器和数据库服务器之间有40%的丢包,TCP重传把请求拉长了。所以评估延迟时,一定要把网络RTT也纳入视野。
在压测环境里,我一般会在数据库服务器本地跑一次同样的压测,再跨网络跑一次,两个结果的差值基本就是网络开销。如果网络开销超过总延迟的30%,说明应用和数据库的部署位置需要调整,或者有跨交换机绕路的可能性。YashanDB的分布式部署模式下,节点之间的网络延迟对全局事务的影响会被放大,这种情况更要重视。
4. 并发能力:连接数与活跃会话的微妙关系
4.1 并发连接不等于并发执行
很多人以为数据库能撑1万个连接就代表很能并发,这是误区。连接是会话的外壳,真正干活的是活跃会话——正在执行SQL、等待I/O、持有锁的那些会话。YashanDB的连接进程本身占用内存,每个空闲连接也会消耗几兆资源,连接数一多CPU调度也会变慢。
我在压测中会同时监控两个数据:当前连接数和活跃会话数。如果当前连接数5000,活跃会话只有30,说明压力根本没打进去;如果活跃会话数等于CPU核心数的好几倍,且等待事件以CPU运行为主,说明系统正在过度上下文切换。
4.2 连接池与最大会话参数
YashanDB层面有会话数上限参数,类似Oracle的processes和sessions。我一般建议把processes设置成实际峰值连接数的1.2~1.5倍,而不是拍脑袋写个大几百。但更关键的是应用连接池的配置,很多中间件的连接池,最大连接数默认值动辄200,而数据库只有8核,一旦多个应用连上来,活跃会话瞬间超过可运行的线程数。
之前调过一个案例:应用A连接池50个线程,应用B连接池80个线程,两个连到同一个YashanDB实例,高峰时活跃会话冲到100多,CPU线程数才16,大量进程在排队。后来把两个连接池的上限分别压到30和40,通过增加应用实例横向扩展来弥补,整体吞吐反而提升了20%。
4.3 并发场景下的雪崩效应
并发能力有个可怕的特质:过了临界点后,性能不是线性下降,而是断崖式雪崩。原因是当活跃会话超过一定数量,锁等待排队、缓冲池争用、日志缓冲竞争会同时恶化,形成正反馈。我在一次压测中亲眼看到,并发从300升到350后,TPS从1.5万直接跌到6000,QPS反而上升——请求堆积在数据库里,系统在处理垃圾流量。
要想避免雪崩,必须在数据库前面做好入口限流。我常用的是在应用侧配合连接池的等待队列长度做控制,同时数据库侧设置资源管理计划,用YashanDB的资源隔离功能把不同业务部门的CPU份额分开。这些配置在平时看不出用处,但一旦遇到大促或者突刺流量,就是保命的手段。
5. 资源指标:CPU、内存、磁盘与锁冲突
5.1 CPU利用率的“良性”和“恶性”
CPU跑得高不一定代表坏事。如果是SQL计算密集、数据过滤在内存里完成,这种高CPU是良性负载;但如果是大量上下文切换、spinlock自旋等待,CPU高企却伴随着很低的TPS,这就是恶性负载。我一般会用top -H看一下进程内线程状态,再结合YashanDB的等待事件视图区分是CPU run queue还是latch free。
有一次压测,CPU利用率100%,TPS却很低。排查发现是连接数开得过大,每个连接都在忙轮询,导致大量CPU时间花在切换线程上。把连接数降低以后,CPU利用率降到60%,TPS反而涨了30%。所以看到CPU高先别急着加机器,先搞清楚CPU到底在做什么。
5.2 内存命中率的三个层级
YashanDB的缓存体系和其他企业级数据库类似,我习惯分成三个层级来看:数据缓冲层(类似Buffer Cache)命中率、SQL结果集缓存命中率、以及闩锁和元数据缓存命中率。第一层决定数据访问IO次数,第二层决定SQL响应速度,第三层决定高并发下内部资源争用程度。
数据缓冲命中率低于90%就说明内存不够大或者热点数据分散,但也不是越高越好,如果到99.5%以上,且还有大量空闲内存,可以考虑缩小Buffer Cache给PGA或者日志缓冲多留一点空间。结果集缓存对重复查询特别有效,适合报表类业务,但要注意缓存失效带来的额外开销。
内存指标还有一个容易忽略的:内存增长的稳定性。如果SGA/PGA持续攀升不回落,多半存在内存泄漏或SQL绑定变量没写好。我在YashanDB上遇到过一条SQL每次执行生成新执行计划,导致共享池碎片不断增长,最后只能重启实例才恢复。这类内存问题不是调整参数能解决的,必须从SQL和会话管理入手。
5.3 磁盘I/O:顺序与随机、延迟与吞吐
磁盘I/O是数据库性能表现最容易“藏雷”的地方。机械硬盘、SATA SSD、NVMe SSD的延迟能差一个数量级。YashanDB的redo日志写路径对延迟特别敏感,因为每个事务提交都要等日志落盘。如果redo所在的磁盘随机写延迟超过10毫秒,TPS基本很难突破1万级别。
我评估磁盘I/O时,会先用fio测裸设备的随机读写和顺序读写能力,再对比数据库实际产生的I/O类型。数据文件通常需要随机读能力,redo日志和归档日志需要顺序写能力。所以最好把redo日志单独放在一块高性能磁盘或者SSD上,避免和数据文件抢I/O通道。
有一次客户把所有文件都放在一块SATA SSD上,压测时发现数据文件读取速度正常,但TPS始终上不去。最后定位到redo日志写入延迟有15毫秒,和数据文件读操作产生了I/O争用。把redo文件迁移到独立的NVMe盘后,TPS直接从8000到了1.8万。优化存储布局往往比调任何参数都见效。
5.4 锁冲突与事务阻塞:并发隐性问题
锁冲突率是我最看重的内部健康指标。YashanDB支持行级锁和MVCC,并发读不会阻塞写,但并发写同一行或者批量更新大范围数据时,锁等待就会显现。实例上有锁等待的SQL,基本都会在等待事件视图里记录enq: TX - row lock contention这类信息。
我判断锁冲突是否严重主要看两个数:平均锁等待时间(毫秒)和每秒锁等待次数。如果每秒锁等待次数上千,且平均等待时间超过50毫秒,说明应用设计上存在热点行竞争。比如用户余额表只有一行记录,所有转账都更新同一行,这种设计换了任何数据库都会卡死。
处理锁冲突不能只靠调数据库参数,更要从业务逻辑上拆解。把热点账户拆成多个明细行、所有事务更新前先做一次排序避免死锁、把大事务拆小,这些措施的效果比增加事务超时时间要实在得多。
6. 基于7大指标的资源优化策略
6.1 内存类参数调优:SGA、PGA与日志缓冲
YashanDB的内存参数和Oracle高度相似,所以很多调优思路可以直接迁移。我一般先看实例的memory_target分配了多少给SGA、多少给PGA。OLTP场景下,SGA里Buffer Cache的比例可以大一些,因为热点数据访问主要靠它;PGA要保证排序、哈希连接的空间,如果PGA不足Oracle会使用临时表空间,造成严重的temp I/O。
调优顺序建议是:先确保Buffer Cache命中率达到95%以上,再看共享池(Shared Pool)是否频繁出现无法分配空间、等待library cache的等待事件。日志缓冲(Log Buffer)大小决定redo写入批量,如果瞬间写入大事务,默认几MB的Log Buffer可能会导致日志文件同步等待变多,我一般会调到32MB或64MB起步,再根据单批提交量微调。
注意总内存不要全部分配完,要给操作系统和文件缓存留至少20%。否则数据库内存一吃满,系统就要靠swap保命,性能直接垮掉。我有一次把YashanDB内存调到物理内存的85%,结果系统开始频繁使用swap,所有SQL都慢了一倍,降回来才好。
6.2 磁盘规划与redo日志优化
磁盘层优化的核心原则是把热路径和冷路径分开。redo日志、数据文件、临时文件、归档日志最好分布在不同的物理磁盘或者至少不同的LUN上。控制文件和数据文件可以放一起,但redo日志千万不要和数据文件共享同一块低速盘。
针对redo日志还可以调整组数和单个文件大小。每组redo文件建议至少两个成员,丢一个还能维持运转;单个redo文件大小不宜太小,否则切换频繁会带来同步开销。我一般根据业务峰值每分钟产生的日志量,估算出一个切换时间控制在15~30分钟内的文件大小。
归档日志要放到另一块有足够余量的磁盘。如果归档空间满了,数据库可能会等待归档完成再继续写日志,这种现象叫“hang等待”。压测时最容易暴露这种问题,因为日志量大,归档跟不上,整个系统就被卡死了。
6.3 SQL与索引优化带来的资源释放
参数和存储都调优到位后,真正的性能红利往往来自SQL和索引优化。YashanDB的优化器思路和Oracle接近,所以explain plan出来的执行计划,如果出现全表扫描、大表哈希连接、排序溢出到临时表空间,都要重点排查。
我处理过一个案例:一条统计SQL,跑了5秒多,占用了大量CPU和临时表空间。加了一个复合索引后,执行计划从全表扫描变成索引范围扫描,耗时降到120毫秒。这个过程中数据库的整体CPU利用率下降了15%,因为把这个“资源大户”优化掉,其他查询就有了余量。
索引也不是越多越好。每次写操作都要维护索引,索引过多会拖慢DML,甚至产生额外的redo日志。我的习惯是建立一个索引基线,根据慢查询日志和访问模式逐步增减,而不是把所有where字段都建上索引。
6.4 连接池与应用侧限流
应用侧对资源的影响经常被忽略。连接池最大连接数设置过大,看似给应用留了余量,实际却可能导致数据库活跃会话超卖。YashanDB的进程模型下,每个连接都要占内存,连接风暴时甚至会触发操作系统层的文件句柄瓶颈。
建议连接池最小值设成平均并发的1.5倍,最大值设成数据库最大活跃会话的80%左右。还要在应用里加上获取连接的超时时间,避免连接池满时请求无限等待。更前置一点,可以通过接入层做全局限流,比如对单个账号的TPS做控速,保护数据库不被异常流量冲垮。
这些策略在7大指标上反映出来,就是锁冲突率下降、活跃会话趋于平稳、CPU不再出现多核打满而单核空闲的现象。资源优化不是单点调参,而是从应用到数据库的通盘设计。
7. 实战中常见的问题与排查技巧
7.1 压测时CPU跑不满但TPS上不去
出现这种情况,先别怀疑数据库有毛病。可能的原因有三个:压测客户端线程数太少;网络带宽和延迟成为瓶颈;数据库在等待某种串行资源。我的排查步骤很简单:先看活跃会话有没有在等redo log flush、lock wait、buffer busy这类等待事件。
如果等待事件很分散,且网络抓包显示TCP窗口收缩,就要怀疑网络缓冲区或者带宽。如果等待事件集中在锁上面,就要按下面的方法找阻塞源。CPU跑不满而TPS上不去,本质上是系统有“蓄水池”在节流,必须找到那个蓄水池。
7.2 缓存命中率高但响应仍慢
缓存命中率99%以上,按理说数据应该都在内存里,为什么还会慢?这种情况大概率是执行计划本身有问题,比如SQL做了大量CPU密集型计算,或者在内存中做了很大的排序和哈希匹配。也就是说,数据没走磁盘,但CPU把时间烧完了。
我遇到过一次:一条SQL命中缓存很高,但每次执行要消耗几十毫秒CPU,因为它在循环里调用了自定义函数处理几万行数据。后来把函数逻辑改成用SQL集合运算实现,单条SQL耗时骤降。所以内存命中率高不等于性能好,要看SQL在内存里做了什么。
7.3 锁等待飙升:如何快速定位阻塞源头
当发现锁等待事件暴涨,我第一反应是查有哪些未提交的长事务。YashanDB里可以通过系统视图查活动会话及其当前SQL、事务开始时间。核心思路是找到一个老事务,它一直没有提交,后面所有更新同一行数据的会话都堵在它后面。
定位到阻塞者后,先不要贸然kill事务,要确认是应用异常未提交还是业务确实需要。如果确实是僵尸事务,再在应用层杀掉对应连接。我还遇到过因为应用全局事务没能提交,导致整个业务停滞的情况,这种问题只能靠完善应用事务管理来根治。
7.4 资源优化策略的优先级建议
按照投入产出比,我习惯这样排序:存储布局优化(如redo独立盘)优先于内存参数调优;SQL和索引优化优先于加大资源配额;连接池和应用侧限流优先于数据库层强制资源隔离。原因是前两者直接减少系统需要处理的物理负载,后两者只是给系统增加了限制。
在7大指标的辅助下,每次优化后都能看到对应指标的变化。存储优化通常直接反映在磁盘I/O延迟和TPS;SQL优化反映在CPU利用率和响应时间;连接池优化反映在活跃会话和锁冲突率。指标之间的联动让优化不再靠感觉,每次调整都有数据支撑。
这套方法我在YashanDB上反复用过多次,也从早期只看CPU和内存的教训里吃够了苦头。评估性能别只盯着某一个数字,要用7个指标互相印证。而且无论参数怎么调、存储怎么分,最终还是要回归到业务SQL和事务设计上。数据库性能不是靠某一个超级参数堆出来,而是靠每一个环节都踩在合理的节奏上。