资讯动态

YashanDB性能优化:五个技巧解决解析、写入和扫描瓶颈

发布时间:2026/10/20 15:07:10 来源:尧图企业网站定制
1. 动优化之前先搞清“卡在哪一层”别让技巧失效最近在客户现场调一套资金对账系统业务方给的反馈很直接每天晚上批量跑到某个环节就变慢日志里全是“等待资源”“执行超时”整个月结要拖到凌晨三四点。我接手后的第一反应不是急着改 SQL而是先把慢的路径摸清楚。很多人在数据库上花大力气加索引、拆分区结果性能没起来原因往往就是优化对象搞错了。要提升 YashanDB 这类通用关系数据库的处理效率先得给“效率”做一个分层。我习惯把性能问题拆成三类来对号入座并发型效率问题系统本身 SQL 很简单但几十上百个会话同时执行时整体吞吐量上不去往往表现为 CPU 高、闩锁和锁等待明显。这类问题的高频原因是数据库在做太多重复解析或者无谓的争用。数据量型效率问题单条 SQL 不慢但要处理的数据范围太大比如全表扫描几千万行、大范围关联跑一次要很久。这在批处理和报表场景里非常常见。写入型效率问题业务逻辑不复杂但数据积压严重比如循环逐条插入、反复的日志刷盘、事务粒度太碎导致写入吞吐远低于磁盘理论能力。怎么定位是哪一层可以先看两个东西慢查询日志和数据库的等待事件。慢日志能告诉你哪条 SQL 占用的累计时间最长等待事件能告诉你它到底在等什么——等 CPU、等磁盘 I/O还是在等锁、等 GC。我曾经在 YashanDB 上排查过一个慢接口执行计划看半天没毛病后来查等待事件才发现瓶颈是会话间游标缓存抖动而不是 SQL 本身。这种情况就算你把表结构重设计一遍也没用。定位之后再对症下药。下面的五个技巧是我在实际项目中反复用过、验证过有效的手段分别覆盖解析、写入、扫描、执行计划和预计算这几个维度。每个技巧我都会讲清楚适用的场景、为什么能提升效率、以及落地时容易踩的坑。2. 技巧一绑定变量与语句复用压掉并发场景的硬解析开销这是 YashanDB 上最被低估的一个优化点特别是业务系统里使用 ORM 或者动态拼 SQL 的地方问题非常突出。2.1 为什么一条 SQL 只执行一次却要反复“硬解析”数据库处理一条 SQL 的时候并不只是“读数据”那么简单。第一次收到一条新 SQL它要做词法分析、语法校验、生成执行计划这整个过程叫做硬解析。硬解析的代价在 OLTP 场景里可能是执行本身的好几倍。如果同一个 SQL 只是查询条件里的值不同而文本结构完全相同数据库就可以把执行计划缓存下来下次直接复用省掉大多数解析步骤这就是软解析。问题在于很多人写的代码喜欢这样拼 SQLString sql select amount from trans_order where order_no orderNo ;这种写法在一个高并发的订单查询接口里就会酿成灾难。假设一秒钟来了一千个不同订单号数据库收到一千条 SQL 文本不同的语句每一条都要硬解析。共享池里塞满了一千个几乎一样的执行计划副本内存白白浪费解析过程中的闩锁竞争还会把 CPU 彻底打满。你回头看慢日志可能每一条 SQL 本身都只跑了 10 毫秒但系统整体就是慢。2.2 用绑定变量的方式改写能省多少开销把上面的代码改成 PreparedStatement 绑定变量PreparedStatement ps conn.prepareStatement( select amount from trans_order where order_no ? ); for (int i 0; i orderList.size(); i) { ps.setString(1, orderList.get(i)); try (ResultSet rs ps.executeQuery()) { // 处理结果 } }这里的变化是SQL 的文本结构始终只有一条第一次执行生成执行计划之后所有订单号都直接复用。我做过一个对比测试同样是一万个订单号的批量查询完全不绑定的版本在并发测试中吞吐量只有绑定版本的六成左右而且 CPU 占用高出将近三成。换到 YashanDB 上绑定变量也意味着游标缓存可以稳定命中数据库不需要反复构造执行计划对象内存和 CPU 的压力同步下降。有人可能会问是不是所有 SQL 都适合绑定变量不是。比如数据分布极度不均A 值能查到一万行B 值只能查到一行绑定变量会导致优化器无法针对具体值选择最优执行计划。这种“绑定变量窥视不准”的极端场景在日常业务里并不多通常只在报表类或者参数化差异特别大的查询中才需要特殊处理。对于绝大多数在线交易系统绑定变量的收益远远大于风险。2.3 配套检查游标缓存和共享池设置代码改完之后还要检查 YashanDB 的游标缓存和共享池相关参数是不是够用。很多项目默认参数在处理低并发时没事一旦业务起来游标缓存太小就会发生频繁淘汰导致本来能命中的软解析变成硬解析优化效果缩水。我一般关注两点一是共享池大小是否随着业务数据量同步调整过二是是否存在高版本计数过高的问题。高版本计数简单说就是同一条 SQL 因为环境差异产生了太多执行计划版本这会让复用失效。遇到这种情况优先检查权限、会话参数、优化器模式这些是否统一而不是一味去清缓存。3. 技巧二批量提交与整表集合操作摆脱逐行操作的性能陷阱第二个技巧针对的是写入场景。数据处理中大量时间花在数据导入、批量更新、批量删除上而很多人的第一版实现就是写一个循环一条条 UPDATE一条条 INSERT。3.1 逐条提交到底慢在哪用 JDBC 往 YashanDB 里插入一万条数据如果不开批量就像一个人去银行柜台一张一张存钱每次都要排队、填单、等柜员处理、拿到回执然后继续下一笔。数据库端的表现就是事务频繁提交、重做日志反复刷写、网络来回开销巨大。遇到批量刷新类的任务比如把外部文件里的几百万条数据清洗进库逐条插入基本是跑不完的。举个实际例子。我优化过一个数据同步作业初次实现是循环单条 INSERT每次自动提交五百万行数据跑了将近一个半小时。改成每批五百条、事务提交一次的批量写入后耗时降到十几分钟效率提升极其显著。3.2 批量写入的正确姿势在 Java 里配合 YashanDB 的 JDBC 驱动标准写法是使用 addBatch 和 executeBatchConnection conn dataSource.getConnection(); conn.setAutoCommit(false); PreparedStatement ps conn.prepareStatement( insert into settle_detail(trade_no, amount, state) values (?, ?, ?) ); for (int i 0; i data.size(); i) { ps.setString(1, data.get(i).getTradeNo()); ps.setBigDecimal(2, data.get(i).getAmount()); ps.setString(3, data.get(i).getState()); ps.addBatch(); if (i % 500 499) { ps.executeBatch(); conn.commit(); } } ps.executeBatch(); conn.commit();有三个细节我在项目里反复强调批量粒度不是越大越好。单批太大事务持有锁的时间变长而且一旦中途出错回滚代价很高。五百到一千比较平衡。关闭自动提交。很多人忘了这一步导致 executeBatch 之后每个批次还是自动提交批量的效果大打折扣。避免在循环里夹杂查询操作。批量写入的循环体里尽量不要做 SELECT否则事务之间的交互会让锁持有时间不可控。3.3 用一条 MERGE 替代 R 循环里的判断更新数据同步中另一个常见的差劲写法是先 SELECT 判断记录是否存在存在就 UPDATE不存在就 INSERT。这个模式在数据量大时不仅慢还会引入并发下的重复键风险。正确做法是使用 MERGE 语句把判断逻辑交给数据库自己去处理。MERGE INTO target_table t USING source_table s ON (t.id s.id) WHEN MATCHED THEN UPDATE SET t.amount s.amount WHEN NOT MATCHED THEN INSERT (id, amount) VALUES (s.id, s.amount);这种集合操作的效率远高于逐行判断因为数据库在执行计划层面就可以按块处理而不是一次处理一行。我给这个技巧的定位是凡是能用一条 SQL 表达的数据变更就不要用程序循环去表达。这不是偷懒而是充分利用数据库本身的计算能力。4. 技巧三分区键设计与并行执行海量数据扫描的破局点当单表数据量从几百万涨到几千万甚至上亿的时候你迟早会遇到“表太大怎么查都慢”的阶段。这时候最有效的两个手段是分区和并行。4.1 分区不是把表拆掉而是让数据库知道“只看该看的部分”分区表看起来还是一张表逻辑上没有变化但物理存储上数据被切成了多个独立的分区。以订单表为例按月份做范围分区CREATE TABLE yashan_order ( order_no NUMBER, create_time DATE NOT NULL, user_id NUMBER, total_amount NUMBER(12,2) ) PARTITION BY RANGE (create_time) ( PARTITION p_202501 VALUES LESS THAN (TO_DATE(2025-02-01,YYYY-MM-DD)), PARTITION p_202502 VALUES LESS THAN (TO_DATE(2025-03-01,YYYY-MM-DD)), PARTITION p_202503 VALUES LESS THAN (TO_DATE(2025-04-01,YYYY-MM-DD)) );之后执行查询时只要带上 create_time 的过滤条件优化器就能通过分区裁剪只扫描对应月份的分区其他几个月的数据碰都不碰。我优化过一个批量报表表里十五个月的数据报表只统计上一个月。没分区前需要全表扫一次跑六分钟加上分区后扫描量直接减少到原来的几十分之一查询秒级返回。分区还能给数据维护带来额外红利。以前删除某个时间段的历史数据要 DELETE 几百万行日志量大、回滚段压力大。分区之后直接ALTER TABLE yashan_order TRUNCATE PARTITION p_202501;这种操作不产生逐行删除的重做日志瞬间完成。4.2 分区键选不好分区反而拖后腿分区不是随便找个列就行。核心原则是查询条件里必须稳定出现这个列否则分区裁剪发挥不了作用。很多人选分区键时只看“这个列有没有索引”却没有想过有多少查询会拿它当过滤条件。如果一个表有 90% 的查询都按 create_time 过滤那按 create_time 分区就是对的如果业务全是按 user_id 查最新一张订单那按 user_id 做哈希分区可能更合理。分区粒度也需要克制。按月分区和按天分区之间扫描性能差距很小但分区数量太多了管理成本和元数据查询开销都会上来。我一般建议在生产环境报表类大表按月流水类海量表按周或者按天具体看数据增长速度和保留周期。4.3 并行执行让多个 CPU 一起干活但别滥用分区解决的是扫描范围问题并行解决的是单个任务慢的问题。YashanDB 的并行执行可以把一条全表扫描分成多个子任务同时交给多个 CPU 处理相当于把一条流水线拆成四条流水线只要机器有空闲资源整体耗时就能大幅缩短。SELECT /* PARALLEL(t, 4) */ user_id, SUM(total_amount) FROM yashan_order t WHERE create_time TO_DATE(2025-01-01,YYYY-MM-DD) AND create_time TO_DATE(2025-03-01,YYYY-MM-DD) GROUP BY user_id;这属于分析型查询用并行很合理。但注意OLTP 的高并发短查询绝对不能开并行。假设你原本有一百个会话在跑小查询开了并行之后每个查询又拆成几个并行子任务几百个并行进程会把 CPU 和内存全部吃光最后反而拖垮整个系统。我见过一个团队给一个高频接口的表加了并行提示上线后数据库直接卡死回滚后才恢复正常。并行度的设置一般不超过机器 CPU 核心数的一半。判断标准是跑批量任务的时段有没有其他重要业务在跑如果有宁可并行度低一点也要保住核心交易链路的资源。5. 技巧四统计信息与索引策略让优化器做出正确的选择很多性能问题不是数据库处理慢而是优化器做出了糟糕的执行计划。YashanDB 的优化器依赖统计信息来估算结果集大小统计信息不准再好的索引也白搭。5.1 索引加了没走很可能是统计信息在“误导”有一次排查一个查询表上明明建了 (user_id, create_time) 的复合索引条件就是这两个列可执行计划显示的是全表扫描。原因是这张表导入大量数据之后没有及时更新统计信息优化器认为表里只有几千行直接扫描更划算。后来执行了统计信息采集执行计划立刻切成索引扫描查询耗时从八百毫秒降到四十毫秒。在大表上统计信息的重要性甚至超过索引本身。索引只是给执行计划提供一条路走不走这条路要由估算决定。统计数据里包含的行数、列的唯一值数量、数据分布直方图等都会直接影响这个判断。常见的统计信息维护动作EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, YASHAN_ORDER);在生产环境我建议在每次大规模数据装载完成之后、第二天业务高峰期之前执行一次统计信息采集。如果表数据每天变化幅度不大可以每周做一次没必要天天跑毕竟采集本身也有开销。5.2 索引策略少建精确建别把索引当万金油初学者容易陷入“给每列都建索引”的误区。索引能加快查询但会拖慢写入因为每一行数据变了索引也要跟着维护。一个表如果有五六个索引单条 INSERT 的代价就很高。我常用的决策框架是点查条件where user_id ?建单列索引。组合查询where state ? and create_time ?建联合索引并把区分度高的列放前面。排序或者分组字段如果经常group by user_id在 user_id 上建索引能避免临时表排序。频繁更新的列谨慎建索引索引维护成本可能超过收益。几乎不用于过滤的列不要建索引纯属浪费空间和写入性能。联合索引的列顺序也值得说一句。用 (state, create_time) 还是 (create_time, state)取决于业务里哪个字段过滤后剩下的行数更少。通常把等值条件放前面范围条件放后面这样索引利用率最高。5.3 函数条件导致索引失效用函数索引绕开SQL 里对索引列做函数转换是另一个高频问题。最常见的写法是SELECT * FROM yashan_order WHERE TO_CHAR(create_time, YYYY-MM-DD) 2025-02-01;因为 create_time 被 TO_CHAR 处理过索引无法直接定位优化器只能把整列数据取出来算一遍这叫做索引失效。如果不改 SQL可以建函数索引CREATE INDEX idx_yd_order_dt ON yashan_order(TO_CHAR(create_time, YYYY-MM-DD));不过更干净的方案是直接改成范围条件SELECT * FROM yashan_order WHERE create_time TO_DATE(2025-02-01, YYYY-MM-DD) AND create_time TO_DATE(2025-02-02, YYYY-MM-DD);这样既保留索引的有效性也让优化器有更多选择空间。函数索引能解决问题但会增加维护成本能用改 SQL 的方式解决就优先改 SQL。6. 技巧五物化视图与查询重写把反复计算的活提前干完最后一个技巧面向一个非常典型的场景同一套统计逻辑每次查询都要重新扫描全量数据算半天结果其实和昨天差不了多少。这种问题用物化视图解决最合适。6.1 物化视图拿空间换时间的预计算方案普通视图只是一个查询定义本身不存数据每次访问都实时跑一遍。物化视图则不同它是把查询结果真正保存在数据库里的一张物理表数据在后台按照设定的策略刷新。以后查询直接读已经算好的汇总结果而不需要回到源头做聚合了。举个我真实处理过的例子。某个经营分析系统有一个订单汇总查询每天业务要做几十次每次都是按省份、按渠道统计当天的订单金额和订单数。明细表超过八千万行单次查询要二十多秒。天天查天天扫资源浪费非常严重。我建了物化视图CREATE MATERIALIZED VIEW mv_order_summary REFRESH COMPLETE ON DEMAND AS SELECT province, channel_code, SUM(total_amount) sum_amount, COUNT(*) cnt FROM yashan_order WHERE create_time TRUNC(SYSDATE) GROUP BY province, channel_code;再把原本复杂的统计 SQL 改成直接查这个物化视图查询耗时从二十多秒变成几十毫秒。这些汇总结果相当于提前算好放在那儿业务想怎么查都行。6.2 刷新策略增量刷新还是全量刷新要想清楚物化视图不是建完就完刷新策略才是真正的设计决策点。ON COMMIT源表事务提交时同步刷新适合对实时性要求高、写入频率不高的场景。它的代价是每次事务提交都会多出刷新开销大事务下很容易拖慢主流程。ON DEMAND由用户手动或定时任务触发刷新适合汇总报表这类对时间不敏感的场景。我一般建议用这种方式把刷新放在了凌晨低峰期。REFRESH FAST增量刷新只处理变化的数据效率高但要求源表有对应的物化视图日志建起来复杂一些。REFRESH COMPLETE全量刷新相当于把物化视图的查询重新执行一遍逻辑简单但数据量大时刷得很慢。生产环境我的建议是宁可慢一点用 COMPLETE ON DEMAND也别轻易上 ON COMMIT。物化视图一旦影响主表的事务延迟会引发一堆连锁问题实在需要“准实时”效果可以每五分钟刷新一次刷新频率可控可调比 ON COMMIT 更容易维护。6.3 查询重写让优化器自动命中物化视图YashanDB 支持查询重写的能力意思是优化器遇到一条和物化视图等价的 SQL 时可以自动改写去查物化视图而不需要业务代码显式感知物化视图的存在。这个特性很优雅但有个前提物化视图的查询要和用户 SQL 的结构匹配。差的太远重写就不会发生。我在项目里的做法是先分析最耗资源的 Top 查询确定通用的聚合口径然后针对这个口径建物化视图最后用原 SQL 做对比测试验证是否命中。不要贪多一个物化视图能覆盖多个查询就去覆盖。物化视图越多刷新任务越重存储开销也越大最后数据库变成了一台只维护汇总表的机器得不偿失。物化视图也需要索引。因为它本质是一张物理表查询它的 WHERE 条件和 JOIN 条件照样可以建索引。如果刷新频率高还要确保刷新任务不会和业务查询争抢资源通常安排在同一时段会互相影响。还有一个容易忽略的点物化视图的日志空间会随时间增长记得定期清理否则会出现磁盘被性能诊断日志撑满的情况。7. 落到自己的库上建议按这个顺序消化这五个技巧五个技巧讲完了写出来感觉很短落地的时候其实是一条条踩过来的。我建议你下次再遇到 YashanDB 上的数据处理慢问题不要冲上去就改 SQL。先把慢日志和等待事件翻出来确认瓶颈在解析、写入还是扫描并发接口慢优先查绑定变量数据导入慢优先批量化扫描大表慢思考分区和并行怎么组合执行计划歪了从统计信息找原因高频聚合慢考虑物化视图。每个技巧都能单独产生效果但组合起来威力更大。另外说一句我在现场经常讲的话优化是留给需要保留的业务的脏数据、多余的循环、为了应付检查写出来的临时表这些该清理的清理掉该重构的重构掉。数据库再强也顶不住程序把简单的事情反复做一万遍。把引擎该做的事交还给引擎把系统里无意义的重复开销去掉这才是提升数据处理效率的根本思路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑