
半夜一点手机响了。线上订单查询接口大面积超时业务群已经炸了。我登录服务器CPU不高内存还有富余磁盘也没满可页面就是卡得像幻灯片。这是一个跑在YashanDB上的业务系统数据库本身没毛病但默认参数和业务负载不匹配SQL也没人专门梳理过。那段时间我把内存、SQL、分区、存储、并发从前到后折腾了一遍最后沉淀出5个提升YashanDB运行效率的优化策略。这篇内容没有太多理论废话全是能落地的操作和真实踩坑记录。无论你是DBA、后端开发还是运维同学只要数据库跑在YashanDB上这些策略都值得直接拿去试一试尤其是刚完成迁移、还在用默认配置上生产的团队看完应该能少踩一轮坑。1. 动手之前先把瓶颈找清楚1.1 调优不是调参大赛测量永远先于调整我先讲一个自己身上发生的反面教材。刚接手第一套数据库时我上来就把数据缓冲区调大了一倍觉得内存参数调大总能快一点。结果第二天业务反而更卡原因很简单机器总共 64G 内存数据库多吃 10G操作系统分给文件缓存的就少了系统开始频繁 swap整个数据库被拖得喘不过气。那次之后我给自己立了个规矩任何参数改动之前先看基线改完再看基线绝不拍脑袋。在 YashanDB 上做优化第一步不是改参数而是先回答一个问题时间到底花在哪里。我习惯先查两类数据一类是活跃会话和等待事件看看整个库的时间都消耗在哪个环节另一类是 TOP SQL看看哪几条 SQL 正在吃掉绝大多数资源。这些信息可以从系统动态性能视图里拿具体视图名不同版本可能略有差异但方向和节奏是固定的测量、定位、假设、验证。这跟看病是一个道理医生不会一上来就开药先做检查找到病灶再对症下药。没有数据的调优本质上都是赌。1.2 YashanDB的SQL执行链路与优化切入点YashanDB 的 SQL 执行链路和主流关系型数据库差别不大一条 SQL 大致要经过词法语法解析、逻辑优化、物理优化、执行引擎、存储访问这几个阶段。每个阶段都可能成为瓶颈但对应到优化手段是不同的。把这些环节和策略对应起来就形成了一张很清晰的优化地图SQL执行环节常见瓶颈对应的优化策略解析硬解析多、共享池压力大使用绑定变量、结果集缓存执行计划生成统计信息旧、缺少索引索引设计、统计信息采集、fixed plan执行与数据访问缓冲命中率低、物理I/O多内存参数调优、分区裁剪并发控制会话数过高、锁等待连接池配置、事务规范物理存储文件布局差、磁盘争抢表空间规划、日志与数据分离这张表也是这篇博文的总纲。五个策略并不是彼此独立的比如分区做得好I/O 自然变少内存压力也会下降SQL 优化得好缓存命中率会提升连接池压力也会缓解。所以后面每个策略我都会尽量讲清楚它作用在链路哪个位置方便你自己判断当前环境最该先动哪一环。2. 策略一内存与缓冲区优化给热数据建贵宾室2.1 内存区域怎么分调多大才合理数据库的内存主要是给几个区域用的数据缓冲区缓存数据块、日志缓冲区缓存重做日志、SQL 工作区排序和哈希、共享池缓存SQL文本和执行计划。所谓内存优化本质就是让高频访问的数据尽量留在内存里少去磁盘搬数同时又要给操作系统留够底裤。配置比例上我自己的惯例是纯数据库服务器数据库整体内存占物理内存的 70% 到 80% 左右如果这台机器还部署了应用或者中间件比例要降到 50% 到 60%。这个区间不是拍脑袋定的因为操作系统还要处理文件系统缓存、网络协议栈、进程调度内存留得太少系统一开始 swap性能不是降低一点而是直接雪崩。YashanDB 的具体内存参数名称不同版本可能不同比如数据缓冲区、日志缓冲区这类你可以通过参数视图查到当前值然后再决定怎么改。我建议做内存调整时一次只改一个区域改完观察一个业务高峰时段确认有效再动下一个。最忌讳的就是把一堆参数的值同时翻倍出现问题时你根本不知道是谁导致的结果。我遇到过同行把所有内存类参数都往大调结果数据库进程频繁触发内存分配失败反而比之前更不稳定。2.2 命中率检查与调整实操判断内存够不够用最直接的指标是数据块命中率也就是逻辑读中有多大比例直接在内存里命中。理想状态在 99% 以上如果低于 95%就要认真找原因。但这里有个很容易犯的错误命中率低就急着加内存。我见过一个系统一张上亿行的历史明细表经常被全表扫描数据缓冲区命中率只有 80%DBA 把缓冲区从 8G 加到 16G命中率确实上去了但物理读并没有明显减少因为那几张大表的扫描范围太大多出来的缓存区只能存下一个边角。最后是加索引、做分区把扫描范围降下来命中率才真正稳定住。正确的操作节奏应该是这样记录当前基线高峰期的命中率、平均响应时间、P99 延迟。用慢 SQL 日志或 TOP SQL 确认到底哪些查询在消耗数据缓冲区。如果是热数据小但访问频次高扩大缓冲区或使用缓存机制效果立竿见影。如果是大表全表扫描先去解决SQL和分区而不是先加内存。调整后用同一时段数据对比基线确认效果再固化到参数文件。把内存缓冲区理解成收银台后面备的零钱如果每个订单都要临时去金库取钱柜台再大也不可能快。真正该做的是让高频小额的数据常备在手边低频大额的走仓库直取。3. 策略二SQL与索引优化花最少成本拿最大收益3.1 先找出真正吃性能的TOP SQL性能优化里性价比最高的动作永远是优化那几条最烂的 SQL。很多时候一条慢 SQL 就吃掉了整个库 60% 以上的资源。定位 TOP SQL 的场景我通常用动态性能视图按 CPU 时间、执行次数、逻辑读排序大致思路如下-- 按累计耗时找 TOP SQL实际视图名以 YashanDB 版本为准 SELECT sql_id, sql_text, elapsed_time, executions, cpu_time FROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 10 ROWS ONLY;有一次客户的报表查询每天跑一次一次跑 20 多分钟还总在业务低峰期把 I/O 打满。我把执行计划拉出来一看两张表连接时驱动表选反了优化器拿小表做被驱动表导致大表被反复扫描。我加了一个 hint 让优化器调整连接顺序整个查询降到 3 秒。一条 SQL 的改动节省的时间够做一整轮参数调优还有富余。我自己有个习惯每季度做一次 TOP SQL 盘点把逻辑读最大的前 5 条拉出来过一遍执行计划这个动作成本极低收益却很稳定。3.2 索引设计三条铁律索引设计我总结了三条最核心的经验。第一等值查询的列放最前面复合索引里各列位置按选择性高低排选择性越高的越靠前。第二避免在索引列上做函数运算或隐式类型转换这是开发最容易踩的坑。第三能用覆盖索引就别回表但索引数量要克制。隐式转换这个坑很典型。比如订单表的手机号字段是 varchar2但接口层传进来的参数被框架转成了 numberSQL 落到数据库后优化器可能对索引列做隐式转换索引就失效了。我排查过很多慢查询第一眼看起来都走了索引仔细一看条件里对索引列做了 to_char 或者算术运算等于强制让索引失效。还有时间字段查当天数据写成 to_char(create_time) 2025-01-01 这种形式也会让数据库放弃索引。正确写法是用范围条件 create_time date 2025-01-01 and create_time date 2025-01-02。给一个具体的索引设计示例。订单表 orders 有 customer_id、order_time、order_status、amount 几个字段业务上最常见的查询是按客户查最近一段时间的订单列表。这时候一个复合索引 (customer_id, order_time DESC) 能同时满足等值过滤和排序连 filesort 都省了。如果只建 customer_id 单列索引数据库还要额外做一次排序才能返回结果。反过来说如果某个字段组合几乎没有查询用到索引建立后每次 insert 和 update 都要额外维护纯亏。注意加索引之前先确认这个查询真的高频。我见过一个库里有三十几个索引绝大多数建完之后从没被优化器选中过写放大倒是贡献了不少。3.3 统计信息与执行计划管理优化器做判断全靠统计信息。统计信息过旧执行计划就会走偏。最常见的情况是表从 10 万行涨到 1000 万行统计信息还停留在早期状态优化器以为这张表还是小表选了一个在小数据量下合理的连接顺序结果实际执行时嵌套循环反复扫描大表慢到怀疑人生。反过来也有坑。统计信息显示某张表有 500 万行实际只有几千行优化器觉得全表扫描更划算结果执行计划里出现大范围扫描速度反而比不上走索引。所以大表数据量发生数量级变化之后一定要重采统计信息。新上线的大表建完索引之后就顺手采集一次不要等自动任务去碰运气。查看执行计划我习惯关注三个点有没有全表扫描、连接方式是不是合理、有没有被隐式转换坑掉的索引条件。YashanDB 上可以用 EXPLAIN PLAN 或者带执行计划的客户端工具查看具体输出格式各版本有差异但判断逻辑通用。如果统计信息暂时没法刷新又急着上线用 hint 或固定执行计划先兜底也是一种合理手段。4. 策略三分区与数据生命周期让大表不再臃肿4.1 分区表什么时候建、怎么建分区我一般建议在三个条件下考虑单表数据量达到千万级甚至更多有明显的时间或业务维度列以及业务有定期清理历史数据的需求。这三点占了两点分区基本就是值得的。订单交易表按月份范围分区是经典设计CREATE TABLE orders ( order_id NUMBER, customer_id NUMBER, order_time DATE, order_status VARCHAR2(20) ) PARTITION BY RANGE (order_time) ( PARTITION p2024_01 VALUES LESS THAN (DATE 2024-02-01), PARTITION p2024_02 VALUES LESS THAN (DATE 2024-03-01), PARTITION p2024_03 VALUES LESS THAN (DATE 2024-04-01) );分区带来的收益不只是查询变快还有管理上的便利。删除一个月的历史数据以前是 DELETE 跑一个小时锁表、撑日志、占 I/O分区之后一个 DROP PARTITION 秒级完成。备份和恢复也可以做到分区粒度某个分区坏了不影响整表。这些运维层面的价值在数据量大到一定程度时会比查询性能本身更宝贵。用档案室类比最贴切按年份分柜子找某年的档案不用翻所有柜子过期档案整个柜子拖走就行。4.2 分区裁剪与冷热数据分离分区表建好了前提是 SQL 要能触发分区裁剪。裁剪的含义是优化器通过过滤条件确定只需要扫描哪些分区而不是把全部分区翻一遍。反面案例我遇到过太多次。订单表按 order_time 做了分区开发写查询时只带了 customer_id 条件没带时间范围结果执行计划扫描了全部分区性能甚至比普通表还差。正确写法是带上分区键的范围条件让裁剪生效SELECT * FROM orders WHERE customer_id 1001 AND order_time DATE 2024-01-01 AND order_time DATE 2024-03-01;需要特别提醒的是对分区键做函数处理会让裁剪失效。比如写成 to_char(order_time) 2024-01-05优化器没法判断这个表达式落在哪些分区只能全扫。这种问题在 SQL Review 里并不显眼但排查起来相当费劲。数据生命周期管理还有一个进阶玩法是冷热分离。活跃的最近三个月数据留在 SSD 上的主表空间历史月份的分区移动到低成本存储或只读表空间甚至定期从主表分离出去归档。这样活跃查询扫描的数据量大幅减少归档数据还保留着需要查历史时也能走独立访问通道。做这一步时要注意业务侧对历史数据查询的依赖程度别头脑一热把常用报表的数据也归档走了后面还要折腾回来。5. 策略四存储与I/O优化否则前面全白做5.1 文件布局与硬件选择这是最容易在初期被忽略的一层。SQL 写得再好、内存调得再大物理 I/O 跟不上整体性能上限就在那里。数据库的核心文件大致分三类数据文件、重做日志文件、控制文件。我检查新环境时第一件事就是确认这三类文件不在同一个物理盘上。曾经有一个系统重做日志和数据文件放在同一块机械盘上业务高峰时 commit 延迟很高一条事务提交要等日志写完才能响应。应用侧改了半年代码都没解决最后把重做日志迁到独立的 SSD 盘上TPS 直接上了一个台阶。原因是日志写入是同步的串行操作数据文件的随机读也在抢同一块盘的磁头两边互相干扰。分开之后各走各的路问题自然消失。硬件选型上第一优先 SSD如果预算允许直接上 NVMe。云环境则要注意磁盘规格的 IOPS 上限很多云盘看起来容量很大并发一高就被限流数据库反而比物理机更慢。另外一定要预留足够的备份和快照空间磁盘写满之后数据库会进入非常难恢复的卡死状态这种事故我见过不止一次。5.2 如何定位和解决I/O瓶颈定位 I/O 瓶颈系统层面看 iostat 的 %util 和 await 指标数据库层面看物理读次数以及跟文件读写相关的等待事件。如果找到瓶颈的依据处理手段通常是组合拳加大数据缓冲区减少物理读分区缩小扫描范围批量任务错峰执行以及调整 checkpoint 频率避免集中刷盘。批量导入的尖峰问题是个高频场景。以前有个夜间任务每天准点把 I/O 打满连带第二天早高峰都受影响。分析之后发现是几百万条数据在一个事务里一次性更新日志和脏数据集中在同一时刻刷盘。改成每 1000 条一个批次 commit并关闭多余的运行日志I/O 尖峰被削平早高峰恢复正常。存储优化的目标从来不是让每个指标都最高而是把 I/O 压力曲线拉得足够平让路况始终顺畅。6. 策略五并发与连接管理让高峰期的数据库不喘粗气6.1 连接数不是越多越好我见过不少团队有个错觉数据库连不上就把连接数往上调。连接数提升确实能容纳更多会话但每个连接都要占用内存和内核资源连接过多会让操作系统忙于线程上下文切换CPU 花在调度上的时间比干正事还多系统整体反而变慢。有一次排查性能问题数据库 CPU 才 30%应用延迟却很高。查了系统指标发现 context switch 数目异常高再看连接池配置最大连接数被设到了 1000实际活跃会话只有二三十。把连接池最大连接数调回 80 之后系统立刻恢复正常。这个案例很典型问题根本不在数据库而在连接管理策略。一般单实例数据库会话数控制在几十到一两百之间是比较稳妥的区间具体数值用压测验证而不是凭感觉定一个很大的数防止报错。服务端最大会话数和客户端连接池上限必须对齐两边不能各调各的。客户端连接池设置比服务端还大高峰期就会报连接超限服务端调大而客户端不变意义也不大。查一下当前连接数和活跃会话数通常最高活跃并发乘以三到五倍就是连接池上限的合理估算。注意调低连接池之前先确认应用侧确实在复用连接。如果应用每次请求都新建连接连接池参数调得再合理也白搭。6.2 锁等待与事务优化连接数正常系统还慢下一步要怀疑锁等待。数据库的锁机制本来是为了保护数据一致性但长事务会把锁占用时间拖得很长后面的会话全部排队。最经典的坑是开发在事务里调用了外部接口比如更新订单状态后调用支付回调这个接口响应 3 秒事务就挂着 3 秒期间这个订单相关的所有操作全部阻塞。定位锁阻塞不复杂查会话视图里的阻塞关系找到 blocking_session顺着线索找到源头会话。确认是异常会话后可以 kill 掉释放锁。但下手之前一定要和业务方确认别把正在办理核心流程的事务误杀了。规范层面能做的事情更多事务尽量做短批量更新按主键分段执行多个会话更新数据时保持一致的加锁顺序降低死锁概率应用侧用完游标及时关闭、连接及时归还。锁优化和连接池优化一样靠的不是某一次大招而是把日常规范做扎实。7. 实操中遇到最多的4个坑与排查清单7.1 参数调整后没生效这个坑出现的频率远超想象。改完参数查询 v$parameter 一看值没变。大多数情况是两种原因参数属于静态参数需要重启实例才能生效或者改了配置文件但实际运行的实例加载的不是这份文件。还有少部分是存在多实例环境改错了节点。正确流程应该是改之前先查询当前值和参数来源执行修改语句时尽量用同时作用于内存和参数文件的 scope 模式改完再查询一次确认。静态参数就老老实实排一个重启窗口不搞侥幸心理。7.2 统计信息太旧执行计划眼盲这是个很有欺骗性的问题。表面上看 SQL 走了全表扫描索引也存在优化器就是不用。我接手的某个环境里一张用户表实际已经 500 万行统计信息却停留在只有几百行的年代优化器认为全表扫描只要几百个数据块当然不走索引。手动采集一次统计信息之后执行计划立刻恢复正常。从那以后我要求凡是单表数据量在一个月内出现数量级变化的项目里必须配置统计信息刷新任务新上线的大表建完索引后立刻采集一次。自动统计信息任务是否默认开启不同版本策略不同但一定要确认它在真实运行不能想当然。7.3 分区表没走分区裁剪分区裁剪失效九成是 SQL 写法问题。一种是对分区键做了函数处理另一种是分区键条件缺失。排查方法很简单看执行计划里扫描的分区范围如果显示扫描了所有分区再去核对 SQL 条件。把条件改成分区键的原始范围比较形式裁剪就能恢复。这个问题的难点不在解决而在发现。SQL Review 时通常只关注 where 条件的等值匹配很少有人专门验证分区裁剪是否生效我的习惯是在新 SQL 上线前随手跑一遍 EXPLAIN确认执行计划和设计预期一致。7.4 性能问题排查速查清单最后把整套方法压缩成一张排查清单方便你下次遇到问题时按顺序走步骤做什么想得到什么结论1. 看整体负载CPU、内存、I/O、会话数曲线确认问题是什么时候开始的2. 查等待事件活跃会话和等待事件分布定位瓶颈在 CPU、I/O 还是锁3. 找 TOP SQL按耗时和逻辑读排序确定要优化的具体目标4. 看执行计划EXPLAIN 确认访问方式判断是否全表扫描、索引失效5. 核对统计信息比较统计行数和实际行数排除优化器信息失真6. 对比参数基线当前参数与历史配置差异排除配置漂移和误改7. 小流量验证改动后跑真实负载确认效果并决定是否全量推广这套流程的核心价值是把所有优化动作建立在数据之上而不是直觉之上。数据库优化最大的成本往往不是改东西而是反复试错的时间。照着这张表走至少能保证你不会在错误的环节浪费太多时间。最后再分享一点自己的体会。优化 YashanDB 别指望一步到位我更习惯把它当成持续迭代的过程。每次改动之前我会把当前基线记录下来高峰期的 CPU、平均响应时间、TOP SQL 都存一份。改完之后再做一次对比效果清楚出问题也知道回退到哪里。还有个实用小技巧把所有关键参数的修改整理成一个变更脚本记录日期、原值、新值和变更原因一旦线上出问题可以快速恢复。不同版本的 YashanDB 在参数名和视图名上会有细节差异但优化的底层逻辑是一致的。如果某个策略在你的环境里不生效先别急着怀疑思路从版本差异和数据分布上找原因大概率能找到答案。