ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YashanDB性能优化实战:5种方法从索引到并发全面调优

YashanDB性能优化实战:5种方法从索引到并发全面调优 做数据库性能优化这件事很多时候不是从某一条SQL开始的而是从把数据库当成一个整体系统去审视开始的。YashanDB是这几年在企业级市场里关注度颇高的数据库产品尤其是在迁移场景里兼容Oracle生态这一点让很多团队降低了切换成本。但不少DBA和开发接手之后第一反应还是老一套加内存、加机器、开并行结果跑起来该慢还是慢。这篇文章我把在YashanDB上实际做性能优化的经验整理成5种方法从索引、SQL改写、内存参数、表结构设计到并发连接管理一条一条讲清楚每个方法都给出可以直接抄的实操步骤。适合刚接手YashanDB的同学也适合那些已经在用但每次优化都靠猜的同事照着步骤走基本能把性能问题的大头解决掉。1. 方法一索引优化让YashanDB的查询路径短一半1.1 先把慢SQL捞出来再决定怎么建索引很多人一上来就问“这个表该建什么索引”其实这个问题问早了。正确的顺序应该是先找到慢SQL再看执行计划最后才决定索引怎么建。YashanDB里可以通过系统视图查历史SQL类似于Oracle的v$SQL按执行次数乘以平均耗时排序很快就能定位到那些真正吃资源的语句。SELECT SQL_ID, SUBSTR(SQL_TEXT, 1, 80) AS SQL_TEXT, EXECUTIONS, ELAPSED_TIME / DECODE(EXECUTIONS, 0, 1, EXECUTIONS) AS AVG_MS, DISK_READS, BUFFER_GETS FROM V$SQL WHERE EXECUTIONS 0 ORDER BY AVG_MS DESC FETCH FIRST 20 ROWS ONLY;这个查询里我一般重点看两列AVG_MS单次执行耗时和BUFFER_GETS逻辑读次数。很多开发只看耗时不看逻辑读其实逻辑读高往往意味着走了大量全表扫描或者索引选择不当优化空间比一条纯粹慢但只读几条数据的SQL大得多。把Top 20慢SQL拉出来之后再逐个看执行计划判断是缺索引还是SQL写歪了这时候建索引才有依据。实操里我建议把慢SQL清单保存成一张性能基线表每次优化前后做对比不然改完参数或者加完索引到底有没有效果全靠“感觉”那很容易走偏。1.2 组合索引最左前缀、覆盖索引和函数索引怎么选建索引不是越多越好关键是匹配查询模式。YashanDB的B树索引和大多数数据库类似最左前缀原则同样适用。假设我们有一张订单表业务上最常见的过滤条件是两个客户ID和时间范围那组合索引应该这样建CREATE INDEX IDX_ORDER_CUST_TIME ON T_ORDER(CUST_ID, ORDER_TIME);组合索引的列顺序有讲究。我的经验是等值条件列放前面范围条件列放后面。因为等值条件可以精确定位范围条件只能确定区间。把等值列放前面走索引时可以快速缩小扫描范围范围列只负责在命中的区间内再做过滤。覆盖索引则是另一种思路就是把查询要返回的字段也放进索引里让索引本身就“覆盖”了查询需求连回表都省了。YashanDB里经常有类似的查询比如统计某客户的订单总额SELECT CUST_ID, SUM(ORDER_AMOUNT) FROM T_ORDER WHERE ORDER_STATUS PAID GROUP BY CUST_ID;如果索引只建在CUST_ID上查询还要回表去取ORDER_AMOUNT和ORDER_STATUS建一个组合索引(ORDER_STATUS, CUST_ID, ORDER_AMOUNT)就能让查询只用索引完成扫描的索引块更少速度自然快。代价是写入时需要维护更多索引所以覆盖索引只建议加在真正高频的查询上。函数索引也是YashanDB支持的一个好工具。比如经常需要按月统计做报表普通索引在字段上做函数转换后就失效了这时候可以直接建函数索引CREATE INDEX IDX_ORDER_MONTH ON T_ORDER(TO_CHAR(ORDER_TIME, YYYY-MM));但函数索引有一点必须提醒它会阻止字段上隐式转换的纠正。很多团队一边建了函数索引一边SQL里还在用不同格式做条件匹配这样优化器要么识别不了要么走了全扫描。函数索引和普通索引不要在同一列上重复建浪费空间还增加维护成本。1.3 索引维护与失效的常见坑索引建好了不代表一劳永逸。我看到的最常见故障是业务运行了几个月之后某个查询突然从快变成慢。查来查去发现是统计信息过期优化器以为索引选择性很高实际上数据分布早就变了。所以索引维护的第一件事是定期收集统计信息YashanDB里可以调用类似DBMS_STATS的接口EXEC DBMS_STATS.GATHER_INDEX_STATS(APP, IDX_ORDER_CUST_TIME);还有一类问题是索引列上的隐式转换。比如某个字段定义是VARCHAR2但SQL里传入的是数字类型YashanDB可能会做隐式转换导致索引失效。排查时直接在WHERE条件里写数字可能看不出来因为数据库在解析时已经做了转换。最稳的办法是查看执行计划看有没有TABLE ACCESS FULL有的话优先检查字段类型和传入参数是否一致。索引碎片也是维护项之一。频繁的UPDATE和DELETE会让索引块变得稀疏导致扫描的块数量上升。YashanDB里可以重建索引或者合并索引碎片我一般不是定期做而是结合AWR报告里的索引扫描指标来定当索引逻辑读明显高于正常水平时才动手避免无谓的资源消耗。2. 方法二SQL改写与执行计划调优最被低估的免费优化2.1 先用EXPLAIN看清YashanDB到底怎么跑索引优化是治标SQL改写才是治本。很多性能问题不是数据库不行而是SQL写法让优化器“无从下口”。在YashanDB里最常用的命令是EXPLAIN它可以告诉你一条SQL执行时的访问路径、连接方法和代价估算。EXPLAIN FOR SELECT o.ORDER_ID, c.CUST_NAME FROM T_ORDER o JOIN T_CUSTOMER c ON o.CUST_ID c.CUST_ID WHERE o.ORDER_TIME DATE 2025-01-01;执行计划出来之后重点看三个地方一是有没有TABLE ACCESS FULL也就是全表扫描二是两个表之间的连接方式是NESTED LOOP还是HASH JOIN三是每个步骤的COST值。很多新手只看COST大小其实COST是估算值更值得关注的是实际行数和估算行数是否差得离谱。如果估算100行实际拉了100万行那统计信息十有八九有问题这时候该做的是收集统计信息而不是硬加索引。YashanDB的EXPLAIN还可以配合实际执行来验证比如EXPLAIN ANALYZE会返回真实执行时间和读取行数这个比干看估算计划要靠谱得多。我改完SQL之后习惯同一时刻把旧SQL和新SQL都跑一遍用实际耗时做对比而不是只看计划的理论成本。2.2 NOT IN改NOT EXISTS、OR改UNION ALL这类实用改写这类改写看似简单实战里收益却非常稳定。NOT IN子查询是性能杀手因为只要子查询结果里有NULL整个查询结果就可能不对优化器会被迫做更保守的处理。改成NOT EXISTS之后执行计划通常更清晰-- 较慢写法 SELECT ID FROM T_A WHERE ID NOT IN (SELECT ID FROM T_B); -- 优化写法 SELECT ID FROM T_A WHERE NOT EXISTS (SELECT 1 FROM T_B WHERE T_B.ID T_A.ID);还有一类是把OR改写成UNION ALL。OR条件会让优化器难以确定走哪个索引很多时候只能合并扫描甚至全表扫描。如果两个条件各自都有独立索引改成UNION ALL之后两边都能充分用上索引-- 原本每个条件都能用索引但OR把索引废掉了 SELECT * FROM T_ORDER WHERE STATUS A OR STATUS B; -- 改成UNION ALL两边分别走索引 SELECT * FROM T_ORDER WHERE STATUS A UNION ALL SELECT * FROM T_ORDER WHERE STATUS B;不过这里要提醒一句UNION ALL不会去重如果原来OR语义里可能出现重复行需要确认业务是否允许重复。就我自己的经验来说这种改写逻辑简单、效果可预期是最适合先动手的一类SQL优化。2.3 统计信息为什么重要手动收集的时机与方法执行计划再厉害也是建立在统计信息之上的。YashanDB在数据量发生变化后优化器手里的牌如果不准出来的执行计划自然可能歪掉。最常见的问题就是小表变巨表但统计信息还停留在小表时代结果优化器选错了连接方式。YashanDB里收集统计信息的接口和Oracle很接近EXEC DBMS_STATS.GATHER_TABLE_STATS(APP, T_ORDER, CASCADE TRUE);什么时机收集我总结了三条。第一批量加载数据之后比如ETL任务跑完立刻收集。第二表数据量变化超过10%到15%特别是DELETE和TRUNCATE之后。第三执行计划的Cost和实际表现出现明显偏差时优先想到统计信息而非索引问题。绑定变量也是SQL优化里容易忽略的一块。如果业务在代码里用字符串拼接动态SQL每条语句的SQL文本都不一样YashanDB每次都要做硬解析CPU消耗非常大。改成绑定变量之后SQL文本固定数据库只需解析一次后续走软解析甚至游标复用整体压力小很多。这一点对高并发OLTP系统尤其重要。3. 方法三内存与缓冲区参数调优别让YashanDB饿着跑3.1 关键参数数据缓冲、排序区和哈希区怎么调如果说索引和SQL解决的是“怎么少干活”内存参数解决的就是“活来了干得快不快”。YashanDB里最影响性能的几块内存和数据缓冲、排序区、哈希区直接相关。因为具体参数名在不同小版本里可能有差异动手前请先查官方手册里的参数列表核对一遍。数据缓冲区是数据库的“热饭锅”。查询要的数据如果在缓冲区里直接从内存返回速度是磁盘的几十倍。缓冲区太小业务一上来就频繁把数据从磁盘读进内存表现就是DISK_READS高、响应时间飘。我见过不少系统分配给数据缓冲区的内存比例偏低结果大量SQL的逻辑读全部落到磁盘上。排序区和哈希区则是给ORDER BY、GROUP BY、HASH JOIN这类操作用的。如果这两个区域偏小数据库会把中间结果写到临时表空间也就是所谓的磁盘排序性能下降特别明显。判断方法很简单执行计划里如果出现SORT ORDER BY后面跟着TEMP SPACE字样说明排序溢出了可以考虑调大排序区。内存调优有一个通用原则总内存不能全塞给数据库。操作系统、连接会话、JVM如果是Java中间件都要留一份。我给YashanDB分配内存的建议是单机64GB内存在只跑数据库的专用机器上数据缓冲区先给20到30GB排序区单会话给16MB到64MB哈希区类似。但千万别照抄先看看当前命中率指标再定。3.2 调整参数的节奏与观察指标调整参数最忌讳一步到位然后重启最好用动态参数调完立刻看效果。YashanDB里手动设置参数的命令风格类似标准SQLALTER SYSTEM SET DATA_BUFFER_SIZE 24G;改完之后不要马上宣布“优化完成”要盯两个核心指标Buffer Cache命中率和磁盘排序比例。命中率长期低于90%说明数据缓冲确实偏小可以逐步加如果已经95%以上还在慢问题多半不在缓冲区别硬调。磁盘排序比例我是看AWR或动态视图如果排序次数不少且都落在磁盘上调大排序区通常见效很快。这里必须强调一个我踩过的坑不要把多个内存参数一次性都调大。因为数据库内存总量是有限制的你调大了数据缓冲区排序区就被挤小了结果缓存命中上去了排序又开始写磁盘整了半天等于原地踏步。我习惯“一次只动一个参数”并且记录调整前后的基线数据这样才能准确判断每个参数的真实贡献。还有一个细节值得注意某些参数修改后需要重启数据库实例才生效对于线上系统一定要提前做变更窗口评估别在业务高峰期执行这种操作。最好是先在测试环境把参数组合验证一遍记录性能数据再谨慎应用到生产环境。4. 方法四表结构设计与存储布局从源头少干活4.1 字段类型选择与定长/变长的取舍有时候慢查询的根源不在索引和SQL而在表结构本身。一个设计糟糕的表任凭怎么优化都会吃力。拿字段类型来说很多业务系统喜欢“偷懒”明明可以用数值类型的地方偏偏用字符串存比如手机号、订单号。字符串在比较时是按字典序逐字符匹配的而且占用空间大扫描时读的块更多SQL写得再好也很难快起来。YashanDB里我强烈建议遵守几条字段设计原则。能用INTEGER就不存VARCHAR2能用DATE或者TIMESTAMP就不存STRING能用CHAR的短定长字段就别用VARCHAR2。定长字段的好处是每行数据的位置可以精确计算扫描时更规整而且不会有行长变短导致的页分裂当然YashanDB的存储粒度更大这个差异没有MySQL那么敏感但依然值得遵循。大字段是性能优化的重灾区。TEXT、CLOB这类字段如果经常跟业务主表放在一起查询哪怕SQL里没select大字段数据库读数据块时也会把这些大对象带进内存白白消耗缓冲区。遇到这种情况我的做法是把大字段拆到独立的副表通过主键关联业务需要时才去读副表主表的扫描和缓存效率立刻提升。4.2 分区表什么时候该分按什么分分区表是一个“改了表结构SQL基本不动”就能见效的方案。热数据全表有1000万行和热数据只有100万行性能完全是两个级别。YashanDB支持范围分区、列表分区这些常见形式用得最广的还是按时间做范围分区尤其是订单、日志这类持续增长的数据。CREATE TABLE T_ORDER ( ORDER_ID NUMBER, CUST_ID NUMBER, ORDER_TIME DATE ) PARTITION BY RANGE (ORDER_TIME) ( PARTITION P_2024_Q1 VALUES LESS THAN (DATE 2024-04-01), PARTITION P_2024_Q2 VALUES LESS THAN (DATE 2024-07-01), PARTITION P_2024_Q3 VALUES LESS THAN (DATE 2024-10-01), PARTITION P_2024_Q4 VALUES LESS THAN (DATE 2025-01-01) );按时间分区之后SQL查询如果带上时间范围优化器能直接做分区裁剪只扫描相关分区全表扫描的次数大幅下降。分区的另一个红利是运维老数据直接按分区drop或者归档不会对在线业务产生大量DELETE压力这个优势在数据量大的系统里特别明显。不过分区也不是无脑上。如果一张表的数据量连几百万行都不到分区带来的收益很有限反而增加了表管理和DDL的复杂度。我的经验是单表数据量超过500万行或者有明显的按时间访问特征才考虑分区设计。分区数量也不要太多不然分区管理本身也会成为新的负担。4.3 适度冗余与历史数据归档策略关系型数据库范式设计讲究减少冗余但在性能优化里适度冗余是常用手段。比如报表查询经常需要把订单表和客户表join起来取客户名称如果每次都实时关联查询慢并且并发一高就锁竞争激烈。对这类场景可以在订单表上冗余一个客户名称字段写的时候多存一份读的时候就不用join了。代价是要保证冗余字段的一致性一般通过应用层同步更新或者触发器维护。历史数据归档则是从“量”的维度解决性能问题。一张表有3年数据但业务只高频查询近3个月的那前33个月的数据其实都在拖慢索引扫描和缓存命中。YashanDB配合分区表可以把老分区直接迁移到归档表空间或者导出备份在线表只保留热数据。这样表的物理体积小了索引高度降了同样的SQL自然就快了。这一步是很多团队忽略的“终极优化”——不是跑得更快而是让数据库干更少的活。5. 方法五连接管理与并发控制把“等锁”时间降下来5.1 连接池参数到底该怎么配性能优化做到后面会发现有些系统的SQL和索引都没问题但整体吞吐就是上不去。这时候要看看连接层。很多应用用的连接池是默认配置默认值往往是“合理但不优秀”的在YashanDB高并发场景下连接池配错了数据库CPU不高但应用响应就是慢大概率是连接等待。连接池大小不是越大越好。每个连接都要占内存和会话资源连接太多会让数据库忙于上下文切换反而拖垮性能。理论上有一种起步算法是池大小等于CPU核心数乘以2加1。比如机器是16核连接池可以配33左右。但这只是起步值I/O密集场景可以适当往上调纯CPU计算密集场景反而要往下压。连接池里有两个参数需要重点关注minimumIdle和maximumPoolSize。minimumIdle代表池里常驻的闲连接太小的话突发流量来了要现场建连会毛刺太大的话闲连接白白占资源。maximumPoolSize决定峰值并发能力配得过高会拖垮数据库。我的习惯是minimumIdle控制在5到10maximumPoolSize根据压测结果动态修正不要迷信计算公式。还有一个被忽视的点连接池的获取连接超时时间。默认值常常是30秒这意味着连接池满了之后新请求会一直等用户感知就是“卡住”。合理设置超时时间比如3到5秒超时直接失败快速反馈让监控能及时告警比请求一直挂着好得多。排障时看到应用线程大量BLOCKED很多时候不是数据库问题就是连接池耗尽。5.2 事务边界与锁等待处理并发还有一个隐藏坑是事务边界太长。开发写代码时往往意识不到一个事务里可能包含了十几次SQL还包括远程调用或者业务计算结果事务一直不提交行锁越积越多。YashanDB里锁等待一多其他会话全部排着队数据库看着CPU不高但吞吐起不来。控制事务边界的原则很简单事务只包含必要的数据库操作远程调用、消息发送、文件读写这类网络I/O和数据库无关的操作不要放在事务里。单事务内SQL数量尽量少能一条搞定的不要拆成十条能三条完成的不要拖到二十条。事务提交的频率也要考量OLTP系统里小事务短平快比一个长事务骑着几十行数据锁半天要健康得多。真出现锁等待时用系统视图排查是第一步。YashanDB里可以查当前锁的持有和等待关系SELECT BLOCKING_SESSION, WAIT_CLASS, WAIT_TIME FROM V$SESSION WHERE STATE WAITING AND WAIT_CLASS Application;查到阻塞源头之后先判断是业务逻辑问题还是真的要调并发。很多时候是偶然的批量任务和在线小事务撞在一起处理方法不是调参数而是把批量任务挪到低峰期或者把批量任务拆成批内小事务分批提交。直接杀掉阻塞会话虽然立竿见影但用户事务会被回滚影响业务完整性这招要谨慎。5.3 隔离级别与乐观锁的合理选择YashanDB默认的隔离级别通常能覆盖绝大多数场景但有些开发为了图省事把隔离级别调到SERIALIZABLE后果是并发冲突变得极其频繁锁等待直线上升。串行化隔离适合数据强一致、冲突本来就少的场景如果业务冲突率高串行化会让系统吞吐惨不忍睹。对于高并发更新场景我更推荐在应用层用乐观锁也就是在表里加一个VERSION字段更新时带上版本条件UPDATE T_ORDER SET STATUS PAID, VERSION VERSION 1 WHERE ORDER_ID 1001 AND VERSION 0;如果更新影响行数为0说明数据已被其他人改过应用自己决定重试或者提示用户。这种方案没有行锁等待不会阻塞其他事务在热点行更新场景里比悲观锁效率高很多。要注意的是任何锁策略都离不开业务语义的配合纯技术手段再厉害也弥补不了业务逻辑对数据竞争的过度依赖。6. 常见问题排查与避坑实录6.1 明明有索引YashanDB却偏要走全表扫描这是我在答疑群里被问得最多的一个问题。索引建了EXPLAIN一看就是不走原因基本逃不出三类。第一类是隐式类型转换比如字段是VARCHAR2SQL里却用数字比较索引列上套了函数自然失效第二类是联合索引的列顺序不对查询条件没有从最左列开始索引就用不上第三类是统计信息严重过期优化器认为全表扫描比走索引更快选择了一个“自认为最优”的糟糕计划。排查顺序就按这三类来一条条对照。6.2 数据库重启后性能“一夜回到解放前”有一些优化过的系统跑得好好的重启之后又变慢了。这不是玄学而是参数没持久化。前面提到ALTER SYSTEM SET命令如果没加SPFILE选项或者没做成持久化配置参数只在当前实例内存里生效重启就全部丢失。另外内存缓冲在重启后会经历一个冷启动阶段数据要慢慢从磁盘读进来缓存命中率一开始很低这属于正常现象不必紧张。6.3 五种方法的使用优先级建议实践里我不建议一次性把五种方法全铺开反而容易搞不清是谁起了作用。我总结了一张优先级表格按投入产出比排列新手可以按这个顺序走优先级优化方法见效速度风险等级适用场景1SQL改写与执行计划调优快低几乎所有项目2索引优化快中有明确慢查询、缺索引3内存参数调优中等中缓存命中率低、频繁磁盘排序4表结构设计与分区慢中高表数据量大、持续增长5连接与并发控制中等中高高并发OLTP、锁等待严重实验下来SQL改写和索引优化的成本最低、见效最快应该是第一步内存参数调优建立在指标分析基础上别靠感觉表结构涉及业务改动需要变更窗口更适合作为中期规划连接管理则是在并发量起来之后必须做的功课。我个人在实际操作中体会最深的一点是性能优化不是“一次性的动作”而是一套持续观测、小步快跑的循环。每做一步优化都要留下基线和结果数据下次再遇到问题才能有据可查。最后再分享一个小经验——优化完之后把系统跑一两天再去动下一个参数很多问题在峰值过后自己就暴露了你千万别急着把所有方法一起全上稳住节奏比手忙脚乱更重要。
返回列表