1. 从 v$sql 到真实执行计划为什么 DISPLAY_CURSOR 更靠谱线上 SQL 变慢时很多人第一反应是拿 SQL 文本去EXPLAIN PLAN FOR跑一遍。但这样得到的只是「优化器现在会怎么执行」而不是「刚才那条 SQL 实际怎么执行的」。两者可能因为绑定变量、统计信息变化、游标共享等原因完全不同。真正要排查性能问题得看游标缓存里那条已经执行过的 SQL 的真实执行计划这时候DBMS_XPLAN.DISPLAY_CURSOR()就是主力工具。它的作用很直接从游标缓存cursor cache里把某个已加载游标的执行计划捞出来还能带上 I/O、内存、耗时等运行时统计。适合谁用DBA、后端开发、做 SQL 调优的工程师尤其是遇到「同一条 SQL 有时快有时慢」这种典型场景。整个链路是先从v$sql拿到SQL_ID和CHILD_NUMBER再用DISPLAY_CURSOR展示计划最后把排查记录整理成可分析的结构化文本。这篇会给出可复制的查询片段、参数配置骨架以及一次从SQL_ID到执行计划的完整验证动作。同时我会把排查记录通过 TaoToken 的统一 Key 通道接到 AI 辅助分析上让「看计划」和「解读计划」串成一条链路而不是看完一堆表格还得自己硬啃。2. 前置准备TaoToken 统一 Key 与排查链路在动手之前先把工具链准备好。TaoToken 在这里的角色是提供一个统一的 API Key 和调用通道把 Oracle 排查过程中产生的文本执行计划、等待事件、SQL 文本交给模型做辅助解读。它不替代数据库客户端也不碰你的生产库连接只是把「分析」这一步接上。你需要准备两样东西第一一个可用的 TaoToken API Key。登录控制台后在 API Keys 页面创建地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapikeys 。创建后复制保存后面调用时放在请求头里。第二确认你的调用入口。模型对话走 https://taotoken.net/api 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc 。如果你打算长期做编码和 Agent 类任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcodingplan 。注意TaoToken 只负责模型调用通道数据库连接、SQL 执行仍然在你自己的 Oracle 客户端或应用里完成。不要把生产库连接串交给任何外部服务。准备就绪后整个排查链路是这样的Oracle 里执行 SQL → 查v$sql拿标识 →DISPLAY_CURSOR出计划 → 把计划文本通过 TaoToken 发给模型做解读 → 回到数据库验证优化。下面进入具体操作。3. 可复制配置从 SQL_ID 到 DISPLAY_CURSOR 参数骨架先看DISPLAY_CURSOR的函数签名这是所有操作的起点DBMS_XPLAN.DISPLAY_CURSOR( sql_id IN VARCHAR2 DEFAULT NULL, child_number IN NUMBER DEFAULT NULL, format IN VARCHAR2 DEFAULT TYPICAL );三个参数的含义sql_id是游标缓存里的 SQL 标识child_number是子游标号同一条 SQL 因绑定变量或环境不同可能有多个子游标format控制输出详细程度常用值有BASIC、TYPICAL、ALL、ADVANCED。想看运行时统计用ALL或ALLSTATS LAST。第一步执行一条带标记的 SQL方便后面定位SELECT /* TOTO */ ename, dname FROM dept d JOIN emp e USING (deptno);第二步从v$sql拿到SQL_ID和CHILD_NUMBERSELECT sql_id, child_number, hash_value, executions FROM v$sql WHERE sql_text LIKE %TOTO%;输出类似SQL_ID CHILD_NUMBER HASH_VALUE EXECUTIONS --------------- ------------ ----------- ---------- gwp663cqh5qbf 0 3693697075 1这里SQL_ID和HASH_VALUE本质上是同一套东西的不同表示DISPLAY_CURSOR的入参虽然写的是sql_id但传hash_value也能定位到同一个游标。实际用的时候我一般优先用SQL_ID因为它更稳定、可读性也更好。第三步直接展示执行计划SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(gwp663cqh5qbf, 0, ALLSTATS LAST));如果你手上只有HASH_VALUE可以这样写SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR( (SELECT sql_id FROM v$sql WHERE hash_value 3693697075 AND ROWNUM 1), 0, TYPICAL ));第四步把v$sql和DISPLAY_CURSOR关联起来一次查出所有匹配 SQL 的计划SELECT t.* FROM v$sql s, TABLE(DBMS_XPLAN.DISPLAY_CURSOR(s.sql_id, s.child_number, ALLSTATS LAST)) t WHERE s.sql_text LIKE %TOTO%;这种写法适合批量排查但要注意v$sql里可能有多个子游标输出会比较多。个人更推荐先精确定位单个SQL_ID再单独展示结果更干净。4. 验证请求一次完整的 SQL_ID 到执行计划动作光看语法不够走一遍完整流程。假设线上有个慢查询你从 AWR 或v$sql里拿到了SQL_ID是gwp663cqh5qbfCHILD_NUMBER是 0。先确认这个游标还在缓存里SELECT sql_id, child_number, plan_hash_value, executions, buffer_gets, elapsed_time FROM v$sql WHERE sql_id gwp663cqh5qbf;如果查不到说明游标已经被挤出缓存这时候DISPLAY_CURSOR会返回空或者报错需要从 AWR 历史里找或者重新执行一次 SQL 再抓。确认存在后展示执行计划SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(gwp663cqh5qbf, 0, ALLSTATS LAST));输出大致如下Plan hash value: 3693697075 | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | |----|--------------------|------|------|-------|------------|----------| | 0 | SELECT STATEMENT | | | | 7 (100) | | | 1 | SORT GROUP BY | | 4 | 64 | 7 (43) | 00:00:01 | |* 2 | HASH JOIN | | 14 | 224 | 6 (34) | 00:00:01 | | 3 | TABLE ACCESS FULL| DEPT | 4 | 44 | 3 (34) | 00:00:01 | | 4 | TABLE ACCESS FULL| EMP | 14 | 70 | 3 (34) | 00:00:01 | Predicate Information (identified by operation id): 2 - access(E.DEPTNOD.DEPTNO)看到TABLE ACCESS FULL出现在大表上通常就是优化点。这时候把这段计划文本复制出来通过 TaoToken 发给模型做解读。调用示例以 curl 为例curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer YOUR_TAOTOKEN_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [ {role: user, content: 这是 Oracle 执行计划请分析瓶颈并给出索引建议\n| Id | Operation | Name | Rows |\n| 3 | TABLE ACCESS FULL | DEPT | 4 |\n| 4 | TABLE ACCESS FULL | EMP | 14 |} ] }模型返回后你会得到类似「EMP 表全表扫描建议在 DEPTNO 上建索引」的建议。然后回到数据库验证CREATE INDEX idx_emp_deptno ON emp(deptno);再执行一次原 SQL重新用DISPLAY_CURSOR看计划是否变成INDEX RANGE SCAN。这就是一次完整的「定位 → 展示 → 解读 → 验证」闭环。5. 本篇常见错排查实际操作中DISPLAY_CURSOR有几个高频坑我踩过也见别人踩过。第一个DISPLAY_CURSOR返回空。最常见原因是游标已经不在缓存里了。v$sql是循环使用的SQL 执行完一段时间没再执行或者缓存压力大就会被挤出去。解决办法是重新执行一次原 SQL或者从dba_hist_sqlstat配合 AWR 报告里找历史计划。第二个SQL_ID传了但报ORA-01403: no data found。这通常是因为child_number不对。同一条 SQL 可能有多个子游标child_number从 0 开始编号。先用v$sql查出所有子游标再逐个展示SELECT child_number, plan_hash_value, executions FROM v$sql WHERE sql_id gwp663cqh5qbf;第三个format参数用了ALLSTATS LAST但没看到统计信息。这是因为该游标执行时没有开启统计收集。ALLSTATS依赖V$SQL_PLAN_STATISTICS_ALL只有 SQL 在statistics_levelTYPICAL或ALL且游标被标记收集时才有数据。如果看不到改用TYPICAL或BASIC先看结构。第四个权限问题。普通用户可能没有查v$sql的权限需要SELECT_CATALOG_ROLE或SELECT ANY DICTIONARY。报ORA-00942: table or view does not exist时先确认权限。第五个HASH_VALUE和SQL_ID混用导致定位错误。虽然两者本质一样但HASH_VALUE是数字SQL_ID是字符串传参时类型要对。用HASH_VALUE查v$sql时记得加ROWNUM限制避免多行返回。提示排查时把v$sql的sql_text、plan_hash_value、executions、buffer_gets一起查出来和DISPLAY_CURSOR的输出对照能快速判断是计划变了还是数据量变了。6. 把排查记录接上 AI统一 Key 的语义一致用法排查完一条 SQL记录往往散落在各个客户端里。我的做法是每次用DISPLAY_CURSOR拿到计划后把计划文本、SQL_ID、PLAN_HASH_VALUE、执行时间一起整理成一段结构化文本通过 TaoToken 的统一 Key 发给模型让它做三件事识别全表扫描和笛卡尔积、对比前后计划差异、给出索引或改写建议。调用入口统一走 https://taotoken.net/api Key 在控制台管理。如果你做的是长期编码和 Agent 任务Coding Plan 会更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentcodingplan 。模型对话可以直接在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentmodels 里试。这样做的价值在于DISPLAY_CURSOR给你事实模型给你解读两者用同一个 Key 串起来排查记录不再是一次性的。下次遇到同类SQL_ID翻出之前的分析记录对比PLAN_HASH_VALUE就能判断计划是否退化。整个链路里TaoToken 只做通道数据库的事还是数据库自己解决边界清晰用起来也放心。