ARTICLE DETAIL

资讯详情

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

月结那天数据库 CPU 拉满:KWR、KSH 一路追到统计信息

月结那天数据库 CPU 拉满:KWR、KSH 一路追到统计信息 事儿是月结那天爆的我们这套财务系统年初从 Oracle 迁的金仓数据库迁完头几个月风平浪静我都快把迁移那档子事忘了。直到三月月结那天下午财务部的王姐电话直接打到我这儿–月结报表跑不出来了点了查询转了快十分钟还在转前两个月可都是三五秒就出结果的。王姐语气挺急说后面还压着一串结账流程这条出不来全卡住。我当时正在茶水间接水水还没接满电话一来杯子差点没拿稳。回到工位一查数据库服务器 CPU 直接拉满业务系统的连接池也开始排队。月结是卡着财务结账节点的耽误一天后面审计都跟着乱压力一下就上来了。运维的小陈隔着工位探头问我是不是库挂了我说没挂是慢但他那表情明显不信。痛点劣化来得没头没脑最让人抓狂的是这劣化毫无征兆。同一张报表同样的数据量级二月份还三五秒三月份就十分钟往上。代码没动表结构没动索引也没人碰过。这种昨天好好的今天就不行的故障比那些一上来就慢的难缠得多–一上来就慢的你照着慢的地方改就行这种你得先搞清楚到底哪儿变了光知道慢没用。我先排除了几个常见嫌疑不是迁移残留迁完都跑俩月了要残留早残留了不是磁盘满了空间富余得很也不是业务量突增月结每个月都这样量没变化。剩下的可能性就往执行计划变了上头想–数据分布变了统计信息没跟上优化器选了条更差的路。但这只是猜测得拿证据。方案拿金仓的诊断工具体系化排查瞎猜没用得让工具说话。金仓这一套诊断工具我平时用得不多这回被迫完整走了一遍分工大致是这样KSH看实时和近期的活动会话定位现在谁在卡、卡在等什么KWR打快照、出报告定位这段时间哪条 SQL 最耗资源KDDM拿 KWR 的数据自动分析给出诊断建议EXPLAIN ANALYZE拉具体 SQL 的执行计划看走没走索引、每步过滤多少ANALYZE收集统计信息给优化器喂准数。这套下来从哪儿慢到为啥慢再到怎么治基本能形成闭环。下面就是那天下午我一步步走的过程顺带把踩的坑也记上。整个排查路径我先画了张图后头每一步就是图里的展开是是否月结报表慢 / CPU 拉满① KSH / v$session看活动会话 等待事件锁定耗资源 SQL② KWR 打快照 报告Top SQL / KDDM 建议③ EXPLAIN ANALYZE发现全表扫描④ 查索引 user_ind_columns查统计 user_tables.last_analyzed统计信息陈旧?ANALYZE 收集统计计划恢复: 走索引 Hash Join偶尔还飘?参数化计划不稳加 Hint 锁定索引定时 ANALYZE 机制故障消除第一步KSH 看会话先锁定现场先进数据库看现在谁在忙。我用 ksql 查活动会话重点看等待事件-- 活跃会话 它们在等什么SELECTsid,serial#, username, status,event,wait_class,seconds_in_wait,SUBSTR(sql_text,1,80)ASsql_previewFROMv$sessionWHEREstatusACTIVEANDusernameFIN_APPORDERBYseconds_in_waitDESC;一查清一色在等 IO 读十几条会话挤在那儿preview 里反复出现同一段报表 SQL。现场就锁定了–就是这条月结查询把 IO 吃干的。顺手也看了一眼有没有锁阻塞怕是哪个长事务攥着锁不撒手把后面全堵了-- 锁阻塞链有没有谁挡住了谁SELECTblocker.sidASblocker_sid,blocker.usernameASblocker_user,blocked.sidASblocked_sid,blocked.usernameASblocked_user,obj.object_name,lo.locked_modeFROMv$lockl_blockJOINv$sessionblockerONblocker.sidl_block.sidJOINv$lockl_waitONl_wait.idl_block.idANDl_wait.sidl_block.sidANDl_wait.request0JOINv$sessionblockedONblocked.sidl_wait.sidLEFTJOINv$locked_object loONlo.session_idblocker.sidLEFTJOINuser_objects objONobj.object_idlo.object_id;结果空集没堵。那就排除了锁的嫌疑纯是 SQL 自己慢。这种实时定位KSH 的活动会话采样也能看历史趋势不过当时火烧眉毛我先看的是 v$session 这个实时视图够用了。第二步KWR 打快照让数据说话光知道是哪条 SQL 还不够得看它在这段时间里到底吃了多少资源。我手动打了两个 KWR 快照把劣化这段时间框进来-- 劣化开始时打一个快照SELECTsys_kwr.create_snapshot();-- ... 跑了大概十分钟让报表再执行几次 ...-- 劣化时段结束时再打一个SELECTsys_kwr.create_snapshot();金仓的 KWR 跟我以前用 Oracle AWR 的思路一样靠两个快照之间的差值算这段时间的负载。平时它也会按间隔自动打快照但自动间隔太长定位这种突发故障不够精细手动打才掐得准。这俩快照的 ID 得记好后头生成报告要用。快照打完生成一份文本报告开始和结束快照的 ID 填进去-- 生成两个快照之间的性能报告文本版好贴好搜SELECTsys_kwr.report_text(123,124);报告一出来Top SQL 那一栏里月结报表那条 SQL 的 elapsed time 一骑绝尘buffer gets 也高得离谱。罪魁祸首实锤了。这一步顺带也跑了 KDDM它自动分析这俩快照给出的第一条建议就是统计信息陈旧建议收集还附带提了一嘴某张表上的索引使用率偏低让我复核。我当时还半信半疑觉得自动建议不能全信后头证明它这条说到点子上了。不过它提的索引那条我没采纳复核完发现那个索引本来就该是冷的KDDM 这块得带着判断看不能照单全收。第三步EXPLAIN ANALYZE扒执行计划锁定 SQL拉执行计划看它到底怎么跑的EXPLAINANALYZESELECTsum(d.amount)AStotalFROMfin_detail dJOINfin_account aONa.acct_idd.acct_idWHEREa.period2024-03ANDd.trans_typeIN(R,P);计划一出来我心里就有数了fin_detail那张大表两千多万行赫然一个全表扫描跟fin_account走的 Nested Loop每行回去扫一遍大表。这要是走索引根本不至于这么慢。估算行数跟实际也差出一截进一步印证了统计信息不准的猜想。第四步以为没索引结果是统计信息的锅我第一反应是索引没建查了一下-- 看 fin_detail 上有没有合适的索引SELECTindex_name,column_name,column_positionFROMuser_ind_columnsWHEREtable_nameFIN_DETAILORDERBYindex_name,column_position;索引在的trans_type和acct_id上都有。那为啥不走我怀疑是trans_type这列值太集中、选择性太差优化器觉得走索引不划算。顺手算了下它的值分布-- trans_type 的值分布看选择性到底行不行SELECTtrans_type,COUNT(*)AScnt,ROUND(COUNT(*)*100.0/SUM(COUNT(*))OVER(),1)ASpctFROMfin_detailGROUPBYtrans_typeORDERBYcntDESC;一看‘R’ 类占了 40% 多选择性确实不算高但也没差到完全不该走索引的地步。再一查最后分析时间破案了-- 看表上次收集统计信息是啥时候SELECTtable_name,num_rows,last_analyzedFROMuser_tablesWHEREtable_nameIN(FIN_DETAIL,FIN_ACCOUNT);last_analyzed停在两个月前–正好是迁移完那阵。可这俩月数据翻了一倍多trans_type的分布也变了‘R’ 类从占 10% 涨到了 40%。优化器手里捏的还是俩月前的旧统计以为 ‘R’ 类还是少数算出来的成本不准自然选了条错路。根子就是统计信息没跟上数据变化优化器被带偏了。赶紧收一遍-- 收集统计信息给优化器喂准数ANALYZEfin_detail;ANALYZEfin_account;收完再跑一遍 EXPLAIN计划立马变了fin_detail走上了trans_type的索引Nested Loop 换成了 Hash Join耗时从分钟级掉回秒级。那一下踏实了主要矛盾找对了。排查工具分工我整理了一张表免得下次再抓瞎工具干啥用啥时候上KSH看活动会话、等待事件先用它锁定现在谁在卡KWR打快照出报告看负载和 Top SQL框一段时间定位最耗资源的 SQLKDDM自动分析快照给建议KWR 之后跑参考它的提示EXPLAIN ANALYZE看单条 SQL 执行计划锁定 SQL 后看走没走索引ANALYZE收集统计信息计划选错、怀疑统计陈旧时第五步计划偶尔还飘参数化埋的雷本以为 ANALYZE 完就收工了结果第二天王姐又来找说偶尔还是慢一下。我一查又是那条报表 SQL执行计划在走索引和全表扫之间反复横跳。这是参数化埋的雷。报表按期间查不同期间的数据量差别很大–有的期间几十万行有的几百万。SQL 用绑定变量传期间值优化器第一次解析时按那个值生成了计划后面换了个数据量悬殊的期间计划没跟着变就可能在大量数据的期间上跑出全表扫的老路。改法是让 SQL 对数据量敏感的查询别一股脑用同一套计划。我给这条 SQL 在数据量大的期间加了个 Hint明确告诉优化器走索引别自己猜-- 数据量大的期间明确走索引别让优化器自己赌SELECT/* index(d idx_fin_detail_type) */sum(d.amount)AStotalFROMfin_detail dJOINfin_account aONa.acct_idd.acct_idWHEREa.period2024-03ANDd.trans_typeIN(R,P);Hint 这东西我平时不爱用怕以后数据变了又不合适等于把优化器的活儿自己揽了。但这条报表逻辑固定、期间分布稳定加上比不加稳权衡下来还是加了。加了之后再没飘过。另外我也开了慢 SQL 日志超阈值的自动记录省得下次再靠人肉发现偶尔慢一下。顺手把统计信息的坑填了单次 ANALYZE 只是救急根上的问题是统计信息没定期收集。我跟运维的小陈商量把统计信息收集排进了定时任务业务低峰期每天跑一遍关键大表-- 关键大表每天低峰收集别再让优化器拿过期数算账ANALYZEfin_detail;ANALYZEfin_account;ANALYZEfin_voucher;大表我特意调高了采样比例默认采样对两千万行的表偏粗算出来的行数估计容易有偏差。另外设了个阈值表的数据变动超过一定比例就自动触发收集不用等人发现慢了再补。这套机制立起来之后后面几个月再没出现过这种昨天好好的今天炸的劣化小陈也再没探过头问我库是不是挂了。优化效果从十分钟掉回三秒月结报表及几个关联接口优化前后对比接口劣化时调优后变化月结报表612s3.1s走索引Hash Join期间明细汇总95s2.4s统计信息刷新科目余额查询18s1.2s同上凭证流水导出40s5.8s加 Hint 稳住计划月结报表那条最夸张从十分钟掉回三秒主要是统计信息一收、计划一正立马就回来了。这反倒说明金仓本身的执行引擎没问题之前纯粹是被陈旧的统计信息拖累让优化器做了错误决策。收个尾真要说这次故障最难的不是改 SQLSQL 加个 Hint、收个统计信息操作上都不复杂。难的是定位–劣化来得没头没脑代码没动表没动光盯着 SQL 看是看不出来的得靠 KWR、KSH 这套工具把这段时间到底发生了什么还原出来。我以前嫌这些工具麻烦有事直接 EXPLAIN这次被教育了突发劣化先 KSH 看现场、再 KWR 框时段、最后 EXPLAIN 看计划这个顺序比上来就 EXPLAIN 高效得多。被这回坑完我立了几条规矩贴工位上了关键大表统计信息定时收集别等慢了再补上线后头一个月盯紧执行计划迁移完数据分布变了最容易出幺蛾子数据量悬殊的参数化查询该加 Hint 就加别全指望优化器自己赌KDDM 的建议带着判断看别照单全收。再补一句掏心窝的–别因为代码没动就排除数据库侧的问题数据在长、统计在旧执行计划随时可能翻脸。那天月结赶在下班前跑完了王姐在群里发了句好了谢谢啊。我在工位上瘫了会儿茶早凉透了苦得发涩。俩小时没白熬吧大概。要是这篇里这套排查路子能帮你在金仓上少抓会儿瞎我敲这几页字就没白敲。
返回列表