☰
Oracle SQL优化利器SQLT:安装配置与执行计划分析实战
2026/10/9 14:41:31 网站建设 项目流程

简介:面向Oracle数据库管理员与性能调优工程师,这份压缩包汇集了适配10g、11g、12c、18c、19c多个版本的SQL调优顾问测试工具集合,可用于分析慢SQL、生成执行计划概要文件并对查询进行针对性优化,尤其适合需要跨环境迁移性能优化建议的运维团队。整个压缩包共205个文件,体积仅927KB,其中160个SQL脚本承载调优和概要文件迁移的核心逻辑,19个包体(PKB)与19个包规范(PKS)构成完整的工具代码结构,另有5个文本说明辅助使用,2个HTML报告展示变更记录与操作指引,结构紧凑又不失完整性。包内核心脚本支持将已有的执行计划概要文件从开发或测试环境导出,再导入到生产环境,帮助数据库管理员快速复用经过验证的优化方案,避免重复调优;同时附带的说明文档还能用于核对不同版本间的差异和部署步骤。目前已有419人学习下载,对于需要系统掌握SQL调优顾问工具、提升数据库稳定性与响应速度的从业者来说,这份资源可以显著缩短脚本收集和概要文件配置的时间,提供一条可直接落地的调优路径。

1. 一个 Oracle SQL 优化工具包的自述:sqlt_10g_11g_12c_18c_19c 是什么、能做什么、适合谁

工单系统里躺着一条慢 SQL,开发催得急,生产库不能随便动,这是 DBA 最熟悉也最头疼的场景。看到sqlt_10g_11g_12c_18c_19c_5th_June_2020.zip这个文件,它就是为这种场景准备的工具包:SQLT(SQLTXPLAIN),一套以脚本形态发布的 SQL 诊断采集器。文件名里的 10g、11g、12c、18c、19c 表示它覆盖了从 Oracle 10g 到 19c 的几乎所有主流版本,日期是打包时间,与某个特定数据库版本无关。它做的事情概括起来就一句话:在目标库上运行一个入口脚本,自动采集执行计划、优化器统计信息、对象元数据、绑定变量和会话事件,打包成一个便于传递和回溯的 zip 报告。适合手里有慢 SQL 但缺现场证据的 DBA,也适合要跟开发团队复盘计划变化的应用负责人。

2. 把 sqlt_10g_11g_12c_18c_19c 装进测试库:解压、目录规划与最小授权

2.1 解压与目录布局:先搞清楚 zip 里到底装了些什么

用 unzip 解压后,你会看到一个主目录sqlt,下面按用途拆成了若干个脚本与子目录。常见布局是:主入口脚本sqltxtract.sql放在根目录,sqlt子目录放核心诊断脚本,utl子目录放工具函数,doc子目录放使用说明。不同历史版本对目录做了微调,但入口脚本的位置基本稳定,这也是为什么很多 DBA 拿到包后习惯先找sqltxtract.sql而不是逐文件读 README。

mkdir -p /u01/sqlt && cd /u01/sqlt unzip sqlt_10g_11g_12c_18c_19c_5th_June_2020.zip find . -maxdepth 2 -type f | head -40

解压动作看似简单,有两件事值得现在做对。第一件,把 zip 里所有脚本的行尾符从 CRLF 转成 LF,很多压缩包在 Windows 环境打包,直接在 Linux 上跑会出现第一个字符前带着\r的报错。第二件,确认sqltxtract.sql有执行权限并指定用哪个用户的 SQL*Plus 登录。我习惯单独建一个目录,不放任何业务脚本,避免 SQLT 运行时自动搜索当前目录下的其他文件,干扰它对同名脚本的解析。

# 行尾符转换,避免 \r 导致的 ORA-00900 / unknown command find . -name "*.sql" -o -name "*.pls" | xargs sed -i 's/\r$//' chmod +x sqltxtract.sql

这里为什么要强调行尾符?SQLPlus 逐行读取脚本时,遇到 CRLF 会把\r当作命令的一部分,常见报错是ORA-00900: invalid SQL statement。这在 11g 之后特别常见,因为压缩包里的脚本往往从多个渠道转手。跑一次 sed 的代价几乎为零,能省掉后面一连串玄学问题。如果你所在团队规定脚本不允许 sed 批量改写,也可以解压时用unzip -a参数自动转换文本文件,或者在 SQLPlus 里set sqlblanklines on,但最省心的路径还是直接转行尾符。

目录规划上,我的标准做法是三级结构:/u01/sqlt放工具本体,/u01/sqlt/output放每次采集生成的报告,/u01/sqlt/log放 SQL*Plus 的 spool 日志。这样做的原因是 SQLT 默认会把报告生成在当前目录,如果每次都从工具根目录跑,日志和报告混在一起,后续按日期归档时非常痛苦。提前用 mkdir 建好目录,并在执行入口脚本前切到输出目录,能让多次采集的可维护性高出一个量级。

2.2 最小授权与初始化:SQLT 运行前必须满足的前提

SQLT 不是以普通用户身份随便跑就能出全量报告的,它要读取v$动态性能视图、访问dba_*数据字典、在 SYS 模式下创建辅助结构,因此通常建议用一个具备 DBA 角色的账号运行。生产环境如果拿不到 DBA 角色,就需要单独给账号授权,常见做法是授予SELECT ANY DICTIONARY、SELECT_CATALOG_ROLE,再在 SYS 下执行一次sqltcreate.sql来建立辅助表。

-- 以 sysdba 连接,创建 SQLT 辅助结构 CONNECT / AS SYSDBA @sqlt/utl/sqltcreate.sql

这段脚本会在当前数据库里创建 SQLT 内部使用的表、视图和包,后续sqltxtract.sql运行时依赖这些对象做数据暂存。注意执行顺序:先创建辅助结构,再跑入口脚本。有些同事第一次跑直接执行sqltxtract.sql,结果报ORA-00942: table or view does not exist,原因就是跳过了这一步。辅助结构默认建在 SYSTEM 表空间,如果 SYSTEM 空间紧张,可以在执行sqltcreate.sql前临时指定 TOOLS 表空间,或者在脚本内查找表空间设置的相关注释按需修改。

授权方面的坑集中在 12c 及以上版本。在 CDB 形态下,如果你在 PDB 里建了辅助结构,需要用 PDB 内的账号执行sqltxtract.sql,而不是用 CDB 的 SYS 去跑。很多 DBA 从 11g 时代养成的习惯是想都不想用 SYS 直接登录,到 19c 的 PDB 里会发现报告里缺失大量对象元数据。原因是 SYS 在 CDB 根容器中的字典视图与 PDB 内业务对象的元数据视图并不完全一致。我的做法是:先conn user/password@pdbservice,确认current_schema指向目标业务库,再执行入口脚本。

-- 授权脚本示例,在 PDB 内以管理员账号执行 GRANT SELECT ANY DICTIONARY TO sqlt_user; GRANT SELECT_CATALOG_ROLE TO sqlt_user; GRANT EXECUTE ON DBMS_XPLAN TO sqlt_user;

除了字典权限,DBMS_XPLAN的执行权限也值得单独确认。SQLT 在采集执行计划时要调用DBMS_XPLAN.DISPLAY系列函数,如果运行账号缺少该包的 EXECUTE 权限,报告里的执行计划部分会变成 ORA-00942 或不完整的提示信息。上述三条授权语句基本覆盖了日常 SQLT 运行的最小权限集,更细粒度的授权项可以在sqltcreate.sql的注释里找答案,它会把每一步需要什么权限写得比较清楚。

2.3 用 sqltxtract.sql 跑出第一份报告:命令与输出

万事俱备后,跑第一份报告的命令非常短。SQLT 的入口脚本是交互式的,它会依次问目标 SQL 的 SQL_ID、要采集的执行计划类型、是否收集统计信息等。如果你希望非交互批量执行,可以在命令行里直接传参,常见做法是用 here-doc 把参数喂给 SQL*Plus。

sqlplus /nolog <<EOF connect sqlt_user/password@orcl @sqltxtract.sql 5g9x2k1p0y3z 2 yes EOF

这里的三个传参含义分别是:第一个参数是目标 SQL 的 SQL_ID,第二个参数是执行计划采集级别(常见取值 1 为基础计划、2 为带统计信息的计划、3 为全量诊断信息),第三个参数控制是否额外收集优化器统计信息。执行完毕后,SQLT 会在当前目录或你指定的输出目录下生成一个 zip 文件,文件名通常带着数据库名和时间戳,里面是主报告 HTML、执行计划文本、AWR/ASH 摘录以及所有涉及对象的建表语句。

第一次跑通之后,建议把输出目录固定下来,不要每次都在不同目录生成报告。我一般会在执行前把当前目录切到/u01/sqlt/output,然后给脚本传一个明确的输出目录参数。这样后面对比多次采集结果时,不用满服务器找 zip。另外,交互模式下如果手滑把 SQL_ID 输错,脚本不会马上报错,而是会生成一份空报告,看起来像执行计划缺失,这时要先确认 SQL_ID 在v$SQL里存在,再决定是否重新采集。

-- 确认 SQL_ID 是否还在共享池中 SELECT sql_id, sql_text, executions, elapsed_time FROM v$sql WHERE sql_id = '5g9x2k1p0y3z';

这条语句应该成为每次采集前的肌肉记忆。如果查不到记录,说明目标 SQL 的游标已经被淘汰出共享池,SQLT 即使运行成功也拿不到实时计划。遇到这种情况,可以让业务再把这条 SQL 跑一次,然后立刻采集;或者改用 AWR 中的历史 SQL_ID 采集模式,从dba_hist_sqltext中拿文本。SQLT 的交互菜单里有一个选项是让用户选择数据来源,实时游标还是 AWR 历史,选错了直接决定报告里有没有执行计划。这一节看似基础,但方向上错了,后面所有分析都白做。

3. SQLT 的核心工作方式:为什么一份 zip 能横跨 10g 到 19c

3.1 版本兼容是怎么做到的:脚本分层与元数据探测

SQLT 能同时兼容 10g 到 19c,不是靠一份脚本跑到底,而是靠分层探测。入口脚本先读取v$version或v$instance得到当前数据库大版本,再根据版本号选择对应的内部脚本分支。这样 10g 里不存在的v$视图就不会被引用,19c 里新增的数据字典列也不会导致旧分支报错。底层逻辑类似针对不同 SQL 方言编写多个执行分支,只不过这个"方言"是 Oracle 自身不同大版本之间的字典差异。

对使用者来说,这个机制带来的实际影响是:不要自己去改脚本里的版本判断逻辑。有些同事发现某个视图在当前版本不存在后,直接注释掉判断分支,往往会在后续其他视图上继续报错。正确的做法是确保包完整解压,不要只拷贝其中几个脚本过来用。SQLT 的目录结构本身就是分层设计的一部分,把脚本拆出来单独跑等于切断了版本判断的路,后患很多。

-- 查看数据库版本,确认 SQLT 分支是否会覆盖当前环境 SELECT banner FROM v$version WHERE ROWNUM = 1;

版本探测的一个现实意义是:12c 与 18c/19c 虽然都属于 12c 及以上大版本,但v$视图和数据字典列仍有差异。例如 PDB 相关信息在 12c 早期与 19c 的展示方式就不同。SQLT 的版本分支通常会按10g、11g、12.1、12.2+这几个粒度来划分,遇到 18c 和 19c 时,它会根据具体的版本号走向兼容分支。如果你在 19c 上跑出的报告内容不完整,先确认你拿到的 SQLT 包是不是较新的打包日期,而不是急着改脚本。

3.2 报告里到底有什么:从执行计划到统计信息的数据链路

一份标准 SQLT 报告的核心内容可以按数据链路拆开理解。顶层是目标 SQL 文本,往下是执行计划,执行计划每行的代价估算依赖表、索引、列的统计信息,统计信息之上还有绑定变量与字面量的对比,最外圈是会话级等待事件和 AWR/ASH 数据。SQLT 把这五层数据全部打包,并在 HTML 报告里按层展示,方便你沿着某一行执行计划的代价一路下钻到该表最近一次统计信息收集时间。

报告板块包含内容排查作用
SQL 文本与绑定变量完整 SQL、变量名与值确认排查对象是否正确
执行计划当前计划、历史计划、计划对比定位访问路径变化
统计信息表、索引、列统计信息与新鲜度判断统计信息是否过期
对象元数据涉及的表的 DDL、索引定义分析索引设计是否合理
会话与等待ASH 采样、等待事件分布判断是 CPU 还是 I/O 瓶颈

我倾向把 SQLT 报告看成一张"SQL 健康检查 CT 片":它不是直接告诉你答案,而是把优化器做决策时看到的所有输入都摆出来。比如一个嵌套循环在报告中显示驱动表行数估算偏差 100 倍,你一眼就能看出统计信息过期是主因。相比手动写查询去逐个视图捞数据,SQLT 的价值是把这条链路的采集脚本化了,输出格式固定,前后对比方便。

这里要特别说明的是执行计划对比功能。SQLT 不止采集当前执行计划,它还会尝试从 AWR 中抓取同一 SQL 的历史计划,并把多个计划按时间线排列。这个功能在处理"昨天快今天慢"的问题时极为有用,因为它把优化器选错计划的"案发时间"直接标记出来了。我在实践中遇到很多开发同事自己手动跑 EXPLAIN PLAN 拿到的计划和生产环境真实计划不一致,SQLT 直接从v$SQL_PLAN里取游标对应的计划,减少了这种偏差。

3.3 和 AWR 的区别:什么时候该用 SQLT 而不是看 AWR

AWR 报告解决的是"这个数据库在时间段内的整体负载画像"问题,而 SQLT 解决的是"这一条 SQL 为什么这么走计划"的问题。两者不是替代关系。AWR 告诉你 CPU 高、等待在 db file sequential read,SQLT 告诉你这条 SQL 走全表扫描是因为列的直方图缺失。实际工作中,我通常先用 AWR 定位到 TOP SQL,再用 SQLT 对其中某一条做单点深挖。

一个容易踩的认知误区是:以为 AWR 里的 SQL 信息足够回答优化问题。AWR 里的执行计划通常是经过采样的,可能落后于当前真实计划;而 SQLT 可以从游标缓存中取出当前正在使用的执行计划,并额外生成多个历史计划做对比。所以当开发拿着一句"昨天快今天慢"的 SQL 来问时,第一反应应该是让 SQLT 去取今天的真实计划和绑定变量,而不是先导 AWR。AWR 只能说明当时环境负载的变化,SQLT 能说明执行计划为什么不稳定。

两者的另一个差异在于变量值。SQLT 在开启绑定变量捕获后,会记录最后一次执行的真实变量值,这是 AWR 做不到的。有了真实变量值,你可以直接做一次带参数的 EXPLAIN PLAN,验证是否存在变量值选择性偏差。AWR 只能看到 SQL_ID 与平均资源消耗,无法精确到某一次执行的变量组合。所以当问题与绑定变量窥探相关时,SQLT 几乎是唯一选择。简单判断标准:如果需要回答"哪条 SQL 慢"用 AWR,如果需要回答"这条 SQL 为什么走错计划"用 SQLT,两者配合使用效果最好。

4. SQLT 配置与三个必调参数:从默认值到生产级设置

4.1 sqlt 参数文件:那些决定诊断深度的开关

SQLT 的配置集中在它的参数文件里,常见位置是sqlt/utl/sqltparams.sql或运行时通过命令行传参。参数文件里定义了采样行数、计划级别、是否收集直方图、是否导出绑定变量、是否抓取 ASH 数据等开关。默认值偏向保守,目的是在 10g 的老库上也能跑完,但在 19c 上往往采得不够深。打开参数配置之前,先明确你要解决的问题类型,再决定动哪些参数。

打开参数文件后,你会看到一系列 define 变量,每个变量都有一段注释说明取值范围。这个文件本身就是一份很好的入门文档,比外边找的二手攻略准确得多。常见变量包括控制计划采集级别的sqlt_xplan_level、控制采样行数的sqlt_sampling_rows、控制是否收集统计信息的sqlt_collect_stats、控制绑定变量值捕获的sqlt_capture_bind_values等。每个变量在不同版本里命名可能略有差异,以实际包内注释为准。

4.2 三个必调参数:采样行数、执行计划级别、绑定变量捕获

第一个必调参数是采样行数。默认值通常是一个整数,表示 SQLT 对涉及的表做行数采样时最多读取多少行。生产环境的大表动辄上千万行,默认值可能只采样几千行,导致估算偏差。我一般会把采样行数调到 100000 以上,但要评估执行时间,特别是统计信息长时间未更新的场景。这个参数只影响采样规模,不影响 SQLT 对游标本身的分析,所以可以放心调大,代价只是采集时间变长。

第二个必调参数是执行计划级别。SQLT 支持从 1 到 3 的采集级别:1 为最小计划,只取执行计划主干;2 为带 E-Rows、A-Rows、Cost 等统计信息的计划;3 为全面诊断,额外收集相关对象的统计信息直方图与会话采样。日常排查用 2 级足够,遇到疑难问题或需要向开发展示执行计划证据时,升到 3 级。级别越高,报告体积越大,生成时间越长,但关键的执行计划细节越完整。

第三个必调参数是绑定变量捕获。SQLT 默认记录绑定变量名与类型,但不一定记录变量值。当你怀疑"绑定变量窥探"导致执行计划走偏时,必须把捕获变量值打开。开启后 SQLT 会把最后一次执行的绑定变量值写进报告,你可以直接用这些值做一次 EXPLAIN PLAN,看优化器在"看到真实变量值"时是否会选择另一条路径。

-- 参数文件内设置示例 define sqlt_sampling_rows = 100000 define sqlt_xplan_level = 2 define sqlt_capture_bind_values = YES

这三个参数之间有关联。采样行数越大,收集统计信息的时间越长;执行计划级别越高,生成的报告越详细;绑定变量捕获开关打开后,报告里会多出绑定变量值列表。新手最容易犯的错误是把三者同时调到最大,然后抱怨 SQLT 跑不完。合理做法是:第一份报告用 2 级计划加默认采样,确认链路能走通;第二份报告再针对怀疑点打开对应开关。比如发现执行计划对绑定变量敏感,就把绑定变量捕获打开,其余保持默认。

参数调整后的效果可以从报告体积直接感受到。默认参数生成的 zip 通常在 1~3MB 之间,如果打开全部开关,特别是统计信息直方图和 ASH 采样,zip 可能膨胀到 20MB 以上。这未必是坏事,但要注意:报告越大,HTML 在浏览器中渲染越慢,查看关键执行计划时越容易卡顿。如果只是快速确认计划变化,2 级计划加默认采样已经完全够用。

4.3 参数改错会怎样:常见误用与后果

误用参数带来的问题通常不是报错,而是报告"看着很全却缺少关键信息"。把绑定变量捕获关掉后,报告里依然有执行计划,但你在排查 SQL 性能突变时就是找不到变量值,只能回去重跑。把采样行数调成0在某些版本里意味着不采样,而不是采样所有行,这是最容易误导人的一个细节。参数文件里的注释一般会写清 0 的含义,建议执行前先读注释。

另一个常见误用是在交互式问答时,把执行计划级别输成0或4。SQLT 内部通常只认 1、2、3,传入 0 会被当作默认级别,传入 4 可能导致脚本走异常分支。如果不确定,直接回车使用默认值,比拍脑袋输入一个数字更安全。我在带人时经常说:SQLT 参数设计的核心思想是"按需加深",不是"一次全开"。

还有一种误用是让 SQLT 在未确认目标 SQL 归属的情况下运行。比如生产库上同名 SQL 有多个版本,SQL_ID 对应的是最新一次执行的游标,但开发想要的可能是上周那个性能较差的版本。SQLT 采集的是当前游标的执行计划,如果你没有确认 SQL_ID 与问题时间的对应关系,报告就只能呈现当下的情况,无法还原过去那次执行。解决方法是先通过v$sql查询last_active_time,确认这个游标确实是目标时间窗口内的执行,再开始采集。这个步骤不花一分钟,但能避免报告与分析方向错位。

5. 避坑指南:SQLT 在 11g、12c、18c、19c 上的五类翻车现场

5.1 现象:脚本执行到一半报 ORA-00942 / ORA-04043

现象:运行sqltxtract.sql到中间步骤时中断,报ORA-00942: table or view does not exist,或ORA-04043: object already exists / does not exist。

原因:绝大多数情况是跳过了sqltcreate.sql的初始化步骤,或者辅助对象建在了别的用户下。SQLT 内部会引用一张管理表来记录当前运行的任务信息,这张表不存在时,后面的所有插入动作都会失败。另一种原因是连接的用户没有访问这些对象的权限,SQLT 没有用创建辅助对象的账号登录。

解决:回到第 2.2 节,先以 SYS 身份执行sqltcreate.sql,然后确认运行账号具备SELECT ANY DICTIONARY或SELECT_CATALOG_ROLE权限。如果辅助对象已经存在但权限缺失,可以单独执行一次授权脚本,或者直接重建辅助对象。注意在 12c 以上版本,要在目标 PDB 内执行,而不是在 CDB 根容器执行。这个坑是出现频率最高的一个,几乎每个新环境部署都会遇到一次。

-- 排查辅助对象是否存在 SELECT owner, object_name, object_type FROM dba_objects WHERE object_name LIKE 'SQLT%';

执行这条语句能快速判断问题出在初始化还是权限。如果查询结果为空,说明sqltcreate.sql没跑成功;如果有对象但当前用户查询报 ORA-00942,说明权限不足。另一个辅助排查点是看当前用户是否是辅助对象的 owner,SQLT 在非 owner 用户下运行时,虽然能通过授权访问,但个别脚本版本存在对同义词解析不完整的问题,最省事的方案是让运行账号直接以 owner 身份使用。

5.2 现象:输出 zip 里缺少执行计划

现象:报告 zip 正常生成,打开 HTML 主报告,发现执行计划部分是空的,或者只有计划概要没有明细。

原因:目标 SQL 的游标已经被淘汰出共享池,SQLT 在v$SQL里找不到对应的 SQL_ID;或者采集级别设置过低,SQLT 在低级别下没有调用DBMS_XPLAN的详细输出。

解决:先确认 SQL_ID 真实存在,执行一遍SELECT sql_id, sql_text FROM v$sql WHERE sql_id='...',如果查不到,说明游标已过期。常见做法是让业务再执行一次目标 SQL,然后立刻运行 SQLT 采集。如果想保留历史计划,需要开启 SQLT 的 AWR 计划采集开关,让它在dba_hist_sql_plan里查找过往计划。这个开关默认不一定打开,建议在参数文件里提前设置。日常排障中,这个问题容易被误判为 SQLT 工具包本身有问题,实际上只是采集时机不对。

5.3 现象:12c 之后 CDB/PDB 环境下授权失败

现象:在 19c 的 PDB 环境执行sqltcreate.sql或授权脚本时,有时会报 ORA-00942,或者报告里大量对象元数据缺失。这里需要先区分两种情况:一种是授予用户权限时容器路径错误,另一种是辅助对象建错地方。

原因:SQLT 的部分脚本版本里针对 PDB 的授权语句使用了容器感知语法,但如果辅助对象建在 CDB 根,而业务查询在 PDB,字典视图看不到对应记录。另一种情况是错误地在 CDB 根里创建了辅助对象,但实际业务库在 PDB。

解决:把执行环境切到目标 PDB,使用 PDB 内具备 ALTER USER 权限的账号登录,再执行初始化与采集。

-- 在 PDB 内确认当前容器与用户 SHOW CON_NAME; SHOW USER;

这两条命令能立刻暴露环境错位问题。很多 19c 下的授权失败,根本原因是登录时连的服务名指向了 CDB 根而不是 PDB。确认当前容器后,再执行授权与初始化通常就能通过。如果仍然失败,检查 SQLT 包对应的脚本版本是否支持当前 19c 小版本,某些 18c 时期的脚本在 19c 上运行会因数据字典变化而半途退出。

5.4 现象:19c 上 SQLT 报告里的中文内容变乱码或字符集报错

现象:报告 HTML 打开后中文注释变乱码,或者执行过程中出现ORA-12704: character set mismatch。

原因:数据库字符集与客户端 NLS_LANG 不一致,SQLT 在拼接 SQL 文本和注释时混合了不同字符集。多发生在从 11g 迁移到 19c 的环境,旧库字符集为 ZHS16GBK,新库为 AL32UTF8,而客户端的 NLS_LANG 没有同步更新。

解决:运行 SQLT 前统一 NLS_LANG,常见做法是export NLS_LANG=AMERICAN_AMERICA.AL32UTF8。如果数据库本身是 ZHS16GBK,就把 NLS_LANG 设置为AMERICAN_AMERICA.ZHS16GBK。关键是让客户端、数据库和 SQLT 输出文件三者的字符集保持一致。另外,报告里嵌的 SQL 文本包含中文注释时,用浏览器打开前确认 HTML 声明的 charset 与文件实际编码一致,否则浏览器按错误编码渲染,会看到一片乱码。

乱码问题看似只是显示问题,但它会直接影响排查效率。执行计划里的注释列如果全是乱码,你很难快速判断哪个步骤是哪个表的扫描。更隐蔽的影响是,SQLT 在采集时如果遇到字符集转换异常,可能跳过部分注释内容的收集,导致报告里出现缺失片段。所以即使你不在乎美观,字符集问题也值得在采集前解决。

5.5 现象:长时间运行不回显,看起来像卡死

现象:SQLT 执行到某个步骤后终端长时间没有输出,既不报错也不结束,持续几分钟到几十分钟。

原因:最常见的是采样行数或统计信息收集开关把所有精力花在大表上,特别是那些长期未收集统计信息的表。其次是一些版本在收集 ASH 数据时需要扫描大量会话历史,本身耗时。

解决:先用操作系统命令确认会话状态,别急着 kill。观察v$session里该会话的 SQL_ID、等待事件和 CPU 消耗,如果正在等待 db file sequential read 且反复读取同一表,多半是在做全表采样。此时可以接受等待,也可以按 Ctrl+C 中断后调低采样行数重跑。我的习惯是:首次在测试环境跑全量,确认耗时可以接受,再到生产环境按需裁剪参数。

-- 观察 SQLT 会话正在做什么 SELECT sid, serial#, sql_id, event, wait_time, seconds_in_wait FROM v$session WHERE username = 'SQTL_USER';

这条语句能区分"卡死"与"正在干活"。seconds_in_wait持续变化说明在等待 I/O,event是 db file sequential read 或 direct path read 都指向数据扫描;如果event是 SQL*Net message from client,说明脚本在等待你输入交互参数,这属于操作层面问题,不是性能问题。确认是等待后,耐心等它跑完比杀掉重来要高效得多。

6. 把 SQLT 用出彩:从诊断报告到优化建议的落地验证

6.1 用 SQLT 对比两个执行计划:绑定变量与字面量的差异

拿到 SQLT 报告后,最实用的一步是把报告中的执行计划编号导出,然后手动在库里执行带绑定变量的 EXPLAIN PLAN 与带字面量的 EXPLAIN PLAN,对比两条计划差异。SQLT 报告结构里已经按计划类型分好章节,你只需要把其中的谓词条件复制出来。

EXPLAIN PLAN SET statement_id='BIND' FOR SELECT * FROM orders WHERE customer_id = :c_id AND status = :status; EXPLAIN PLAN SET statement_id='LITERAL' FOR SELECT * FROM orders WHERE customer_id = 10086 AND status = 'SHIPPED'; SELECT plan_table_id, operation, options, object_name, cost, cardinality FROM plan_table WHERE statement_id IN ('BIND','LITERAL') ORDER BY statement_id, plan_table_id;

这样做的意义在于验证 SQLT 报告中的猜测。报告显示优化器选错了索引,你可以在本地快速确认是不是绑定变量值影响了选择性估算。如果绑定时走全表扫描、字面量时走索引,基本坐实了绑定变量窥探或自适应游标的问题,后续优化方案就有了明确方向。比对着报告猜测要稳妥得多。

6.2 验证优化方案:改完 SQL 后再跑一次 SQLT

对 SQL 做了等价改写、加了提示或重建了统计信息之后,别急着下结论。正确做法是对改后的 SQL 重新采集一次 SQLT,拿两份报告的执行计划和实际消耗做对比。SQLT 报告中的执行计划文本可以直接 diff,HTML 报告里的计划表格也可以用文本形式导出。

# 两份报告的 plan 文本对比 unzip -p first_report.zip plan.txt > plan_first.txt unzip -p second_report.zip plan.txt > plan_second.txt diff -y plan_first.txt plan_second.txt | head -60

对比时先看驱动表、连接方法、访问路径三者的变化,再看 E-Rows 与实际 Rows 的偏差。如果改完后执行计划变了但资源消耗没降,问题可能在统计信息或并发层,而不是 SQL 写法。还有一个小技巧:把两次报告的 SQL 文本里绑定变量值也 diff 一遍,排除变量值变化带来的干扰。这样得出的结论才可靠,也方便写复盘报告时直接引用证据。

6.3 收尾技巧与习惯

长期用 SQLT 的经验来看,最值得养成的习惯是统一报告归档命名。我会把每次采集的 zip 按"日期_库名_SQLID_问题描述"重命名,例如20250320_orcl_5g9x2k1p_customer_order_slow.zip。这样半年后回看工单时,不用逐个打开 zip 才知道是哪次变更。另一点是建立团队内共享的 SQLT 报告解读模板,把执行计划、统计信息新鲜度、绑定变量三个板块固定写在模板里,后人接手时能快速定位关键信息。

SQLT 不是万能钥匙,它只负责把现场证据采全,真正的优化判断还是要靠人对执行计划和数据的理解。但一套规范化的采集流程能把排障时间压缩到一个可控区间,避免不同人查出来的证据五花八门。我的习惯是每次做性能复盘前,先跑一次 SQLT 拿底稿,再决定要不要深入 AWR 或做 SQL 改写实验,这个底稿习惯帮我省掉了很多回头看工单时说不清数据的尴尬。希望这些思路和踩过的坑能帮到你,让你的下一次 SQL 性能排障少走几步弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询