拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Oracle性能分析2:用TaoToken统一Key解读trace文件中的SQL执行瓶颈

Oracle性能分析2:用TaoToken统一Key解读trace文件中的SQL执行瓶颈 1. 从一段 trace 片段说起为什么你盯着它看了半小时还是没结论Oracle 的 trace 文件是性能分析里最原始、也最诚实的数据源。它不像 AWR 报告那样经过采样和聚合也不像 ASH 那样只保留活跃会话的切片它把一次会话里每个游标的解析、执行、等待、抓取、执行计划全部按时间顺序记下来。问题是它太诚实了——一个中等繁忙的会话跑十分钟trace 文件就能到几百 MB里面全是PARSING IN CURSOR、PARSE、EXEC、FETCH、WAIT、STAT这些块肉眼扫一遍基本等于没看。我试过直接grep WAIT然后按ela排序结果发现排在最前面的等待事件是SQL*Net message from client耗时几百秒但那是客户端在发呆跟数据库没关系。也试过只看FETCH的e值结果漏掉了PARSE阶段的高耗时硬解析。trace 文件解读的核心难点不是字段不认识而是没有把散落在不同块里的信息按 SQL 维度重新聚合。这篇要解决的就是这件事把原始 trace 文件经过 tkprof 格式化之后定位到真正拖慢系统的 SQL 和等待事件产出一份可以直接拿去改索引、改 SQL、改绑定变量的优化清单。同时因为整个分析过程会涉及多个工具调用——tkprof、SQL 查询、脚本解析、甚至让模型帮忙读执行计划——我会用 TaoToken 的统一 Key 把这些调用凭证管起来避免在多个终端和脚本之间反复切换配置。适合谁看已经在做 Oracle 性能分析、手上有 trace 文件但不知道怎么下手的 DBA 或后端开发以及想把 trace 分析流程脚本化、但又不想在每个工具里单独维护一套 API 凭证的人。2. 前置准备TaoToken 统一 Key 与 trace 分析工具链2.1 为什么 trace 分析场景需要统一 Keytrace 分析不是单一动作。典型流程是先在数据库服务器上跑tkprof生成格式化报告然后用 SQL 查v$sql补上下文再用脚本解析报告里的执行计划最后可能还要把某条 SQL 的执行计划丢给模型做一次语义解读。每一步可能用不同的工具、不同的终端、不同的脚本如果每个工具都单独配一套 API Key管理成本会很高而且容易在脚本里硬编码凭证。TaoToken 的做法是提供一个统一的 API 入口你只需要在配置文件里维护一份 Key所有走 OpenAI 兼容协议的工具都能复用。对于 trace 分析这种“多工具、短会话、频繁切换”的场景统一 Key 的价值在于你不需要在每个脚本里重复写认证逻辑换 Key 的时候也只改一个地方。2.2 获取 Key 与配置文件骨架先到控制台创建一个 API Key然后按下面的骨架写config.toml。这个文件可以放在项目根目录也可以放在~/.config/taotoken/config.toml取决于你的工具怎么读。# config.toml - TaoToken 统一凭证配置骨架 # 适用于 trace 分析流程中的多工具调用 [default] # 统一 API 入口所有兼容 OpenAI 协议的工具都指向这里 base_url https://taotoken.net/api # 从控制台创建的 Key建议用环境变量注入不要硬编码 api_key ${TAOTOKEN_API_KEY} # 默认模型trace 解读场景建议用长上下文模型 model gpt-4o [trace_analysis] # trace 分析专用配置可以覆盖 default # 执行计划解读需要较强的推理能力 model claude-3-5-sonnet # 单次请求超时trace 报告可能很长 timeout 120 # 最大输出 token执行计划解读可能返回较长文本 max_tokens 4096 [tkprof] # tkprof 可执行文件路径按实际环境改 binary /u01/app/oracle/product/19c/dbhome_1/bin/tkprof # 格式化报告输出目录 output_dir ./trace_reports [sqlplus] # 连接串用于补查 v$sql 等视图 connect sys/oraclelocalhost:1521/orcl as sysdba把 Key 写进环境变量export TAOTOKEN_API_KEY你的Key如果你用的是 Python 脚本做解析读取配置的代码大概长这样import os import tomllib with open(config.toml, rb) as f: config tomllib.load(f) api_key os.path.expandvars(config[default][api_key]) base_url config[default][base_url] model config[trace_analysis][model]这样一套配置tkprof 调用、SQL 补查、模型解读三个环节都能复用同一个 Key不需要在每个脚本里单独处理认证。3. 可复制配置tkprof 格式化与 trace 解析脚本3.1 tkprof 命令与关键参数原始 trace 文件不能直接读先用 tkprof 格式化。假设 trace 文件是orcl_ora_12345.trctkprof orcl_ora_12345.trc trace_report.txt \ sysno \ sortexeela,fchela,prsela \ aggregateyes \ insert./insert_statements.sql \ record./trace_records.sql参数说明参数作用trace 分析场景建议sysno不输出递归 SQL必开否则满屏都是数据字典查询sortexeela,fchela,prsela按执行耗时、抓取耗时、解析耗时排序直接定位高耗时 SQLaggregateyes相同 SQL 聚合统计减少重复条目便于看总量insert生成 insert 语句把统计写入表便于后续 SQL 查询record生成带绑定变量的 SQL 记录复现问题时用sort参数是重点。exeela是执行阶段的总耗时fchela是抓取阶段的总耗时prsela是解析阶段的总耗时。三个一起排基本能覆盖“慢在解析、慢在执行、慢在取数”三种情况。3.2 从 tkprof 报告里定位高耗时 SQLtkprof 报告里每条 SQL 的头部长这样call count cpu elapsed disk query current rows ------- ------ -------- ---------- ---------- ---------- ---------- ---------- Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 0 0 Fetch 2 0.01 0.02 4 8 0 1 ------- ------ -------- ---------- ---------- ---------- ---------- ---------- total 4 0.01 0.02 4 8 0 1看elapsed列。如果Fetch的elapsed远大于Execute说明时间花在取数上可能是索引没走对或者返回行数太多。如果Parse的elapsed高说明硬解析严重要考虑绑定变量或者游标共享。下面这段 Python 脚本把 tkprof 报告解析成结构化数据按elapsed排序输出import re from dataclasses import dataclass, field dataclass class SqlStat: sql_id: str sql_text: str parse_elapsed: float 0.0 exec_elapsed: float 0.0 fetch_elapsed: float 0.0 disk_reads: int 0 buffer_gets: int 0 rows: int 0 waits: list field(default_factorylist) property def total_elapsed(self): return self.parse_elapsed self.exec_elapsed self.fetch_elapsed def parse_tkprof(report_path): stats [] current None with open(report_path, r, errorsignore) as f: lines f.readlines() i 0 while i len(lines): line lines[i] # 匹配 SQL 文本块 if line.startswith(SQL ID:): if current: stats.append(current) current SqlStat(sql_idline.split(:)[1].strip()) # 匹配 call 统计行 elif current and re.match(r^(Parse|Execute|Fetch)\s, line): parts line.split() call_type parts[0] elapsed float(parts[3]) disk int(parts[4]) query int(parts[5]) rows int(parts[7]) if call_type Parse: current.parse_elapsed elapsed elif call_type Execute: current.exec_elapsed elapsed elif call_type Fetch: current.fetch_elapsed elapsed current.disk_reads disk current.buffer_gets query current.rows rows # 匹配等待事件行 elif current and WAIT in line and nam in line: current.waits.append(line.strip()) i 1 if current: stats.append(current) return sorted(stats, keylambda s: s.total_elapsed, reverseTrue) if __name__ __main__: results parse_tkprof(./trace_reports/trace_report.txt) for s in results[:10]: print(fSQL_ID{s.sql_id} total{s.total_elapsed:.2f}s fparse{s.parse_elapsed:.2f} exec{s.exec_elapsed:.2f} ffetch{s.fetch_elapsed:.2f} disk{s.disk_reads} fquery{s.buffer_gets} rows{s.rows})跑出来的输出直接按总耗时降序排列前十条就是你要重点看的 SQL。3.3 把等待事件和执行计划关联起来tkprof 报告里每条 SQL 下面会跟等待事件和执行计划。等待事件的关键字段是nam事件名、ela耗时单位微秒、p1/p2/p3参数。比如WAIT #1: namdb file scattered read ela 5000 p14 p21435 p325这表示在游标 1 上等待了 5000 微秒5 毫秒做的是多块读文件 4起始块 1435读了 25 个块。如果是db file sequential readp3通常是 1表示单块读多半是索引扫描。执行计划部分长这样STAT #4 id1 cnt1 pid0 pos1 obj74 opTABLE ACCESS BY INDEX ROWID IDL_CHAR$ (cr4 pr0 pw0 time20 us) STAT #4 id2 cnt1 pid1 pos1 obj115 opINDEX RANGE SCAN I_IDL_CHAR1 (cr3 pr0 pw0 time12 us)id是执行计划节点编号pid是父节点cnt是行数cr是一致性读pr是物理读time是该节点耗时。把time加起来和elapsed对比能看出时间花在哪个节点上。下面这段脚本把等待事件按事件名聚合输出每个事件的次数和总耗时from collections import defaultdict def aggregate_waits(stats): wait_summary defaultdict(lambda: {count: 0, total_ela: 0}) for s in stats: for w in s.waits: m re.search(rnam([^]) ela\s*(\d), w) if m: name m.group(1) ela int(m.group(2)) wait_summary[name][count] 1 wait_summary[name][total_ela] ela return sorted(wait_summary.items(), keylambda x: x[1][total_ela], reverseTrue) if __name__ __main__: results parse_tkprof(./trace_reports/trace_report.txt) for name, info in aggregate_waits(results)[:10]: print(f{name}: count{info[count]} ftotal_ela{info[total_ela]/1000:.2f}ms)输出里如果db file scattered read排第一说明全表扫描多如果db file sequential read排第一说明索引扫描多但可能回表次数高如果enq: TX - row lock contention出现说明有锁竞争。4. 验证请求用统一 Key 调模型解读执行计划4.1 构造请求把上面解析出来的高耗时 SQL 和执行计划拼成一段文本通过 TaoToken 的统一入口发给模型让它给出优化建议。请求体是标准的 OpenAI 兼容格式import os import requests api_key os.environ[TAOTOKEN_API_KEY] base_url https://taotoken.net/api prompt 下面是一条 Oracle SQL 的执行计划和等待事件统计请分析瓶颈并给出优化建议 SQL 文本 select /* index(idl_char$ i_idl_char1) */ piece#,length,piece from idl_char$ where obj#:1 and part:2 and version:3 order by piece# 执行计划 STAT #4 id1 cnt1 pid0 pos1 obj74 opTABLE ACCESS BY INDEX ROWID IDL_CHAR$ (cr4 pr0 pw0 time20 us) STAT #4 id2 cnt1 pid1 pos1 obj115 opINDEX RANGE SCAN I_IDL_CHAR1 (cr3 pr0 pw0 time12 us) 等待事件 WAIT #4: namdb file sequential read ela 4000 p14 p21224 p31 请指出1) 主要瓶颈在哪个阶段2) 索引是否合理3) 是否需要调整 SQL 或绑定变量。 resp requests.post( f{base_url}/v1/chat/completions, headers{ Authorization: fBearer {api_key}, Content-Type: application/json, }, json{ model: claude-3-5-sonnet, messages: [{role: user, content: prompt}], max_tokens: 2048, temperature: 0.2, }, timeout120, ) print(resp.json()[choices][0][message][content])4.2 成功结果长什么样模型返回的内容应该包含对执行计划节点的解读、对等待事件的分析、以及具体的优化动作。比如它会指出INDEX RANGE SCAN的cr3说明索引扫描本身不贵但TABLE ACCESS BY INDEX ROWID的cr4说明回表读了 4 个块如果这条 SQL 执行频率很高回表就是主要开销。建议可能是确认obj#、part、version三个条件的过滤性如果version区分度低考虑调整索引列顺序或者加组合索引。拿到这个建议之后你回到数据库里用explain plan验证新索引的效果再决定是否创建。整个流程从 trace 文件到可执行优化清单中间不需要手动整理数据脚本和模型调用共用同一份config.toml里的 Key。5. 本篇常见错排查5.1 tkprof 报错 unable to open file多半是 trace 文件路径不对或者权限不够。trace 文件通常在$ORACLE_BASE/diag/rdbms/$ORACLE_SID/$ORACLE_SID/trace/下面文件名类似orcl_ora_12345.trc。用ls -l确认文件存在并且当前用户有读权限。如果是从别的服务器拷过来的注意文件属主可能变了。5.2 解析脚本读不到 SQL 文本tkprof 报告里 SQL 文本可能跨多行而且前面有缩进。上面的脚本只匹配了SQL ID:行没有把后续的 SQL 文本行拼起来。实际使用时需要在SQL ID:之后继续读行直到遇到call统计行或者空行把中间的内容拼成完整 SQL。另外如果 tkprof 用了aggregateyes相同 SQL 会被合并SQL ID可能只出现一次但统计是多次执行的累加。5.3 等待事件耗时单位搞混ela的单位是微秒不是毫秒。ela 5000是 5 毫秒不是 5 秒。上面脚本里除以 1000 得到的是毫秒。如果你看到ela 143那是 0.143 毫秒基本可以忽略。只有ela超过 1000010 毫秒的等待才值得关注。5.4 模型返回内容被截断如果执行计划很长max_tokens设小了会导致返回内容不完整。trace 分析场景建议至少设 2048复杂执行计划设 4096。另外temperature建议设 0.2 以下让模型输出更稳定减少胡编乱造。5.5 统一 Key 在脚本里读不到如果用${TAOTOKEN_API_KEY}这种环境变量占位符确保脚本运行前已经export了。在 cron 或者 systemd 里跑脚本时环境变量不会自动继承需要在脚本开头显式 source 一个 env 文件或者在 service 文件里用Environment指定。6. 把 trace 分析流程固定下来trace 文件解读这件事单次做不难难的是每次都要重新搭一遍流程。我的做法是把上面这些步骤固化成一个目录结构trace_analysis/ ├── config.toml ├── parse_tkprof.py ├── aggregate_waits.py ├── ask_model.py ├── trace_reports/ │ └── trace_report.txt └── output/ └── optimization_list.md每次拿到新的 trace 文件先跑 tkprof 生成报告再跑解析脚本输出高耗时 SQL 和等待事件聚合最后把 Top 5 的 SQL 和执行计划拼成 prompt 发给模型把返回的优化建议追加到optimization_list.md。整个过程里config.toml里的 Key 只需要维护一份tkprof 调用、SQL 补查、模型解读三个环节共用。如果你还没有 Key可以到 TaoToken 控制台 创建一个然后按上面的config.toml骨架填进去。trace 分析场景对模型的长上下文能力要求比较高建议在 模型对话 里先试一条执行计划确认返回质量符合预期再批量跑。如果你打算把这个流程做成长期跑的脚本或者 Agent可以看看 Coding Plan 的额度方案避免每次手动换 Key。接入细节和参数说明在 接入文档 里Key 的创建和管理在 API Keys 页面。
返回列表