Oracle Skills:nl-to-sql-patterns_自然语言到SQL映射表深度解读
如果你的 AI Agent 能自己查锁、诊断慢 SQL、定位阻塞源头——它差的不是推理能力,而是一张把「人话」翻译成 Oracle 系统视图的对照表。oracle/skills 里这个 13KB 的映射文件(https://github.com/oracle/skills/blob/main/db/agent/nl-to-sql-patterns.md),就是做这件事的。
一、当 AI 面对 Oracle 的上千个系统视图
通用大模型写 SELECT ... FROM orders WHERE ... 没有问题。但当你问:
“谁锁了这张表?”
“昨晚那个失败的定时任务,日志在哪?”
“帮我看看哪些索引从来没用过。”
AI 面临的问题不是「不会写 SQL」,而是 「不知道该查哪张系统视图」。
Oracle 有上千个 v$ 和 dba_ 视图。v$session、v$lock、v$sql、dba_hist_sqlstat、dba_scheduler_job_log……每个都有特定的列名、关联方式和权限要求。AI 的训练数据里不缺这些知识,但它缺乏一个结构化的索引——当用户说「锁」,AI 应该立刻知道去 v$session 里找 blocking_session 列做自连接。
这就是 nl-to-sql-patterns.md 要解决的问题。
它不是一本 SQL 语法书——窗口函数怎么写、CTE 怎么嵌套、PIVOT 怎么用,那是 sql-patterns.md 的领域。这张表只盯着一件事:把运维人员和 DBA 的自然语言需求,精准翻译成 Oracle 系统视图的正确查询。
二、运维诊断:从「数据库慢了」到精准定位
文件中「性能诊断」和「锁与阻塞」两个板块共 10 条映射,是整张表最有价值的部分——它们覆盖了 DBA 日常排障的核心路径。
2.1 等待事件:诊断的起点
-- "What's waiting in the database?"
SELECT event, COUNT(*)
FROM v$session
WHERE wait_class != 'Idle'
GROUP BY event
ORDER BY 2 DESC;
这条 SQL 看似简单,但映射表给了三个关键过滤条件:
wait_class != 'Idle'——排除空闲等待(如「SQL*Net message from client」),否则结果会被大量正常的空闲连接淹没GROUP BY event——按等待事件聚合,一眼看出当前瓶颈集中在哪个环节ORDER BY 2 DESC——频率最高的排最前,紧急程度一目了然
没有这个模式,AI 大概率写出 SELECT event FROM v$session 然后让用户面对几百行输出自己找规律。
2.2 阻塞链路:v$session 自连接的奥秘
最容易被 AI 写错的是锁诊断。阻塞者和被阻塞者的信息都在同一张 v$session 里,通过 blocking_session 列关联:
SELECT blocker.sid AS blocker_sid,
blocker.username AS blocker_user,
blocked.sid AS blocked_sid,
blocked.username AS blocked_user,
blocked.seconds_in_wait,
blocked.event,
obj.object_name
FROM v$session blocked
JOIN v$session blocker ON blocked.blocking_session = blocker.sid
LEFT JOIN v$lock lk ON lk.sid = blocker.sid
AND lk.type = 'TM'
AND lk.lmode > 0
LEFT JOIN dba_objects obj ON obj.object_id = lk.id1
WHERE blocked.blocking_session IS NOT NULL
ORDER BY blocked.seconds_in_wait DESC;
这里有四个 AI 不可能凭空猜对的点:
| 关键点 | 为什么 AI 猜不到 |
|---|---|
blocking_session 列 |
v$session 有几十个列,这个列名不直观,没用过的人根本不知道 |
v$session 自连接 |
同一张视图 JOIN 自己——不是常规思维路径 |
v$lock 的 type = 'TM' + lmode > 0 |
TM 是表级锁(DML),TX 是事务锁。必须同时检查锁类型和锁模式——lmode > 0 确保只取实际持有的锁,滤掉仅处于请求状态的行 |
dba_objects 的 object_id = lk.id1 |
v$lock 的 id1 存的是 object_id 而非 object_name,需要再 JOIN 一次翻译成可读的对象名 |
这张表的核心价值不是「告诉 AI v$session 的存在」,而是把四张视图之间的正确关联路径一次性给出。 AI 不需要推理,直接复用。
2.3 慢 SQL 定位:从实时到历史
映射表区分了两个层次——实时查询和 AWR 历史,各自解决不同的问题场景:
实时层面(当前正在跑的慢查询——适合即刻排障):
SELECT s.sid, s.serial#, s.username,
ROUND(s.last_call_et / 60, 1) AS running_minutes,
sq.sql_text
FROM v$session s
JOIN v$sql sq ON s.sql_id = sq.sql_id
AND s.sql_child_number = sq.child_number
WHERE s.status = 'ACTIVE'
AND s.last_call_et > 60
AND s.username IS NOT NULL
ORDER BY s.last_call_et DESC;
几个值得注意的过滤细节:s.username IS NOT NULL 排除了 Oracle 后台进程(如 PMON、SMON),避免干扰结果;last_call_et > 60 可调——60 秒只是示范阈值,生产环境中按需调整。
历史层面(过去 24 小时最耗资源的 SQL——适合事后复盘):
SELECT s.sql_id,
ROUND(s.elapsed_time_delta / 1e6 / s.executions_delta, 2) AS avg_sec,
s.executions_delta AS executions,
SUBSTR(t.sql_text, 1, 100) AS sql_preview
FROM dba_hist_sqlstat s
JOIN dba_hist_sqltext t USING (sql_id)
WHERE s.snap_id IN (
SELECT snap_id FROM dba_hist_snapshot
WHERE begin_interval_time > SYSDATE - 1
)
AND s.executions_delta > 0
ORDER BY s.elapsed_time_delta DESC
FETCH FIRST 20 ROWS ONLY;
文件在这个历史查询旁加了明确标注——需要 Diagnostics Pack 许可。这不是技术细节,是合规提醒。AI 如果不知道这个限制,可能在无许可环境里执行 dba_hist_* 查询,轻则报 ORA-00942,重则违反许可协议。
注意 executions_delta > 0 的作用:保护除法运算——elapsed_time_delta / executions_delta 在分母为零时会报错,这个谓词防的就是这种情况。
2.4 定时任务排障
映射表还覆盖了一个容易被忽视的场景——调度任务失败排查:
-- Recent job failures
SELECT job_name, log_date, status, error#, additional_info
FROM all_scheduler_job_log
WHERE owner = :schema
AND status = 'FAILED'
AND log_date > SYSTIMESTAMP - INTERVAL '7' DAY
ORDER BY log_date DESC;
all_scheduler_job_log 和 all_scheduler_job_run_details 这两张表,很多有经验的 DBA 都不一定随时记得列名,更别说 AI。映射表直接给出了标准查询,连 INTERVAL '7' DAY 这种 Oracle 特有的时间间隔写法都给好了——AI 独自写的时候大概率会用 SYSDATE - 7,虽然结果近似,但 SYSTIMESTAMP - INTERVAL '7' DAY 与 log_date(TIMESTAMP 类型)在数据类型上更匹配。
三、Schema 探查:动手前的安全网
AI Agent 操作数据库最大的风险不是 SQL 写错,而是在不了解表结构的情况下直接执行 DDL。
映射表中 7 条 Schema 探查映射,构成了 AI 操作前的标准预案:
用户:"在 orders 表上加个 discount_rate 列"
↓
Step 1: "What columns does this table have?"
→ SELECT column_name, data_type, nullable FROM all_tab_columns
WHERE owner = :schema AND table_name = :tbl
ORDER BY column_id
(发现已有一个 DISCOUNT 列——用户可能只是不知道)
Step 2: "Primary keys?"
→ SELECT cols.column_name FROM all_constraints c
JOIN all_cons_columns cols ON c.constraint_name = cols.constraint_name
WHERE c.owner = :schema AND c.table_name = :tbl
AND c.constraint_type = 'P'
Step 3: "Table DDL?"
→ SELECT DBMS_METADATA.GET_DDL('TABLE', :tbl, :schema) FROM DUAL
最后才执行 ALTER TABLE——而且会带着完整上下文:
"orders 表已有 DISCOUNT 列(NUMBER),确认要新增 discount_rate?"
没有这套 Schema 探查,AI 直接写 ALTER TABLE orders ADD (discount_rate NUMBER)——然后因为列名冲突或者不了解现有约束而失败。映射表强制 AI 先看后动。ORDER BY column_id 在这里不是装饰——它保证了列按建表时的顺序展示,与 DESCRIBE 命令的输出一致,DBA 看着更顺手。
四、看似简单的日期模式:暗藏的性能陷阱
映射表中的 6 个日期过滤模式,乍看都是基础操作,但每个都体现了生产环境的最佳实践:
-- 本周(周一起始)
WHERE col >= TRUNC(SYSDATE, 'IW')
AND col < TRUNC(SYSDATE, 'IW') + 7
-- 本月
WHERE col >= TRUNC(SYSDATE, 'MM')
AND col < ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1)
这些日期模式都用了 >= ... AND < 的半开区间,而非 BETWEEN。这不是风格偏好——BETWEEN 包含两端边界,当时间列带时分秒时,BETWEEN '2026-06-01' AND '2026-06-30' 会漏掉 6 月 30 日当天的所有非零时分秒数据(如 2026-06-30 14:30:00)。
映射表对此的处理一丝不苟:
| 写法 | 问题 | 映射表用 |
|---|---|---|
BETWEEN |
含右边界,丢失当天后续时间数据 | 不用 |
TRUNC(col) 在 WHERE 里 |
函数包裹列,索引失效 | 不用 |
col >= ... AND col < ... |
半开区间 + 列不包裹 = 走索引 | ✅ 始终用这个 |
还要注意一个小细节:TRUNC(SYSDATE, 'IW') + 7 是用整数加法跳到下周一零点——ISO 周从周一开始,加 7 天正好跨越一整周。而「本月」用的是 ADD_MONTHS(TRUNC(SYSDATE, 'MM'), 1),因为月份天数不固定,不能简单地用 + 30 或 + 31,必须借助 ADD_MONTHS 保证精确。
这些细节 AI 不可能从训练数据中区分出优先级——训练数据里四种写法都有,而且各有适用场景。映射表给了明确的选择标准。
五、消歧指南:比 SQL 模板更重要的东西
映射表有一张独特的对照表——「模糊需求 → 追问澄清」:
| 模糊表述 | 应追问 |
|---|---|
| “Show me employees” | 哪个 schema?全部列?只查在职的? |
| “Recent orders” | 今天?本周?最近 30 天? |
| “Big tables” | 按行数?按存储空间?阈值多少? |
| “Slow queries” | 按总耗时?平均耗时?CPU 时间? |
| “Delete old records” | 哪个表?什么算「旧」?硬删除还是软标记? |
AI 最危险的倾向不是写错 SQL,而是在需求模糊时自己猜一个答案。
取最后一条作例子。用户说「帮我把旧数据删了」。没有映射表的 AI 可能直接生成:
DELETE FROM orders WHERE created_date < SYSDATE - 365;
提交后发现有 300 万行被删——用户说的「旧」可能是指已归档的、标记为废弃的,而不是按时间一刀切。
加载了消歧指南后,AI 的标准行为应该是五连问:
- 哪张表?
- 「旧」的定义?(30 天?一年?
status = 'ARCHIVED'?) - 物理删除还是软标记(
is_deleted = 1)? - 要不要先
SELECT COUNT(*)确认影响行数? - 有没有外键依赖?
追问逻辑是跨语言通用的。中文提问「帮我清理一下旧数据」同样触达这条消歧规则——大模型不需要英文关键词匹配,它读的是语义结构。这一点在后面的中文优化章节会进一步展开。
六、安全约束:嵌入在 Skill 中的操作纪律
映射表不仅给出正确的 SQL,还对危险操作嵌入了操作纪律。以 KILL SESSION 为例:
-- Kill the session (requires ALTER SYSTEM privilege)
-- IMPORTANT: always confirm with user before killing; this is irreversible
ALTER SYSTEM KILL SESSION ':sid,:serial#' IMMEDIATE;
下面紧跟着一条 Agent rule:
Agent rule: Never execute ALTER SYSTEM KILL SESSION
without explicit user confirmation.
Validate both SID and SERIAL# before constructing the command.
这不是 SQL 语法——是操作纪律,直接写入 Skill 文件中。它的效果是:无论 AI Agent 的 system prompt 怎么写、无论后面的对话上下文怎么变,只要加载了这个文件,KILL SESSION 之前一定会确认。
类似的隐式安全约束还体现在:
dba_hist_*查询前的 Diagnostics Pack 许可警告- Schema 探查放在 DDL 操作之前(先读后写)
WHERE username IS NOT NULL过滤系统进程(避免误杀后台会话)
这些约束不是靠 prompt 工程能保证的——prompt 可以被长对话冲淡。嵌入 Skill 文件的规则,每次加载都重新生效。这是 Skill 系统区别于 prompt 指令的根本优势。
七、实战:从一句话到完整诊断
假设用户在凌晨两点发来一条消息:「数据库有点慢,帮我看下」。
不加载映射表:AI 大概率生成 SELECT * FROM v$session WHERE status = 'ACTIVE',然后把几百行输出丢回去。
加载映射表后,AI 按照文件中「从全局到具体」的诊断链路,三步完成:
第一步:等待事件总览
SELECT event, COUNT(*)
FROM v$session
WHERE wait_class != 'Idle'
GROUP BY event
ORDER BY 2 DESC;
输出:enq: TX - row lock contention: 34 sessions waiting
第二步:定位阻塞源
SELECT blocker.sid, blocker.username, blocked.sid,
blocked.seconds_in_wait, obj.object_name
FROM v$session blocked
JOIN v$session blocker ON blocked.blocking_session = blocker.sid
LEFT JOIN v$lock lk ON lk.sid = blocker.sid
AND lk.type = 'TM'
AND lk.lmode > 0
LEFT JOIN dba_objects obj ON obj.object_id = lk.id1
WHERE blocked.blocking_session IS NOT NULL
ORDER BY blocked.seconds_in_wait DESC;
输出:SID 287 (用户 BATCH_JOB) 阻塞了 34 个会话,被锁对象是 orders 表,已阻塞 23 分钟。
第三步:查看阻塞者正在跑什么
SELECT sq.sql_text
FROM v$session s
JOIN v$sql sq ON s.sql_id = sq.sql_id
AND s.sql_child_number = sq.child_number
WHERE s.sid = 287;
诊断结论:「SID 287 的一个批量更新事务持有 orders 表锁,阻塞了 34 个会话,已持续 23 分钟。该事务正在执行全表更新——建议确认是否可以终止。」
这个诊断路径来自映射表中多条查询的隐式排序——从宏观等待事件到微观阻塞链路,文件本身的组织方式就是诊断流程。
八、中文场景:让映射表说中文
前面七节都在讲映射表「有什么」和「怎么用」。但有一个很现实的问题被绕过去了——当前这张表全是英文。
8.1 英文映射表在中文团队的摩擦
当前 nl-to-sql-patterns.md 的所有 trigger phrase 都是英文:
"What's waiting in the database?"
"Show me the slowest queries"
"who is locking what"
而实际对话中,用户和 Agent 之间大概率是中文:
“数据库在等什么?”
“谁把这张表锁了?”
“昨天那个定时任务为什么失败了?”
这中间的翻译依赖的是什么?大模型内在的跨语言理解能力。 没有一条显式的中文 → SQL 映射路径。大多数时候大模型能自行对应——毕竟 “slow queries” 和 “慢查询” 语义完全对齐——但在一些细微的、文化特定的表达上就可能跑偏。
举个例子:中文 DBA 习惯说「hang 住了」、「卡死了」、「锁等待超时」——这三个短语指向的是同一个场景(阻塞诊断),但字面上没有任何一个和 “blocking session” 直接对应。如果 Agent 没有在映射表中见过这些中文说法,它可能不知道该执行那套阻塞诊断 SQL。
8.2 两种优化方案
方案一:中文触发词并行映射(推荐,改动最小)
直接在现有英文模式旁边增加中文触发短语列:
| Natural Language (EN) | 自然语言 (CN) | Oracle SQL Pattern |
|---|---|---|
| “What’s waiting in the database?” | “数据库在等什么” / “当前等待事件” | SELECT event, COUNT(*) FROM v$session WHERE wait_class != 'Idle' GROUP BY event ORDER BY 2 DESC |
| “who is locking what” | “谁锁了表” / “阻塞源头” / “hang住了” | SELECT blocker.sid ... FROM v$session blocked JOIN v$session blocker ON ... |
| “Show me the slowest queries” | “最慢的查询” / “慢SQL” / “TOP SQL” | ORDER BY elapsed_time DESC on v$sql |
| “Records from the last 7 days” | “最近一周的数据” / “过去7天” | WHERE created_date >= SYSDATE - 7 |
关键不是穷举所有中文说法,而是列出同一语义下的高频同义表述。以「阻塞」为例,在生产环境中可能出现的中文说法包括:
- 谁锁了表?
- 查一下阻塞
- 锁等待
- 卡住了 / hang 住了
- 这个会话为什么在排队?
- enq: TX - row lock contention
一个 Agent 看到以上任何一句话,都应该路由到同一套阻塞诊断 SQL。把这几条中文说法写进映射表,就从「靠大模型自己猜」变成了「精确匹配」。
方案二:中文分词 + 关键词路由
在 Agent 框架层做一层轻量中文分词,提取关键词后路由到映射表类别:
中文输入:"帮我看看哪些表没有主键"
↓ 分词
关键词:表、主键、没有
↓ 匹配类别
Tables Without Primary Keys
↓ 执行对应 SQL
这个方案的好处是不需要逐条写中文映射——只要关键词覆盖模式够全,就能收敛到正确的类别。但也引入了额外的分词和匹配逻辑,不如方案一直接。
建议:用方案一作为基础,从最高频的两个板块(性能诊断 + 锁与阻塞)开始落地。方案二作为后续优化方向。
8.3 中文消歧:模糊性比英文更高
第五章讲的消歧指南跨语言通用,但中文有一个特性值得专门关注:中文在数据库运维场景下的模糊性往往比英文更高。
| 中文表述 | 可能的含义(危险等级各异) | 必须追问 |
|---|---|---|
| “数据库慢了” | 整体慢 / 某个 SQL 慢 / 某个时段慢 / 网络慢 | 什么现象?什么时间段?影响范围? |
| “表太大了” | 行数多 / 存储占用大 / 索引膨胀 | 按行数还是按存储?阈值多少? |
| “帮我清理一下” | DELETE / TRUNCATE / DROP / 归档 / 分区清理 | 哪种操作?哪个表?保留条件? |
| “这个用户” | schema 名 / OS 用户名 / 应用连接用户 / 大小写? | 具体用户名是什么? |
尤其是「清理」这个词——英文对应多个完全不同危险等级的操作:DELETE(可回滚)、TRUNCATE(不可回滚的 DDL)、DROP(直接删表)、归档(INSERT INTO archive + DELETE)。Agent 如果在「清理」这一步猜错了,后果是灾难性的。
所以中文消歧指南需要比英文版更细——不能只在模糊词出现时追问一次,而要对「清理」「删掉」「处理一下」这类动作动词做强制二次确认,无论上下文看起来多清晰。
8.4 落地策略:分步扩展
在实际工程中,不需要一次性给所有板块都配上中文映射。按使用频率和风险分批推进:
| 优先级 | 板块 | 理由 |
|---|---|---|
| P0 | 性能诊断(等待事件 / 慢查询) | 最高频,占日常排障 80% |
| P0 | 锁与阻塞 | 应急场景,错一步就多卡 N 分钟 |
| P1 | Schema 探查 | 日常操作,AI 做 DDL 前的安全检查 |
| P1 | 定时任务 | 运维例行,失败排查高频 |
| P2 | 空间 / 存储 | 低频,错了也只是数字不准确 |
| P2 | 用户 / 权限 | 安全相关但操作频率低 |
8.5 最终的映射表长什么样
将上述思路落地后,扩展版映射表的每个 section 会变成三列结构:
Performance and Diagnostics Questions
| Natural Language (EN) | 自然语言 (CN) | Oracle SQL Pattern |
|---|---|---|
| “What’s waiting in the database?” | “数据库在等什么” / “当前等待事件” / “等待事件分布” | SELECT event, count(*) FROM v$session WHERE wait_class != ‘Idle’ GROUP BY event ORDER BY 2 DESC |
| “Show me the slowest queries” | “最慢的SQL” / “慢查询TOP N” / “耗时最长的查询” | SELECT sql_text, elapsed_time/1e6 elapsed_sec FROM v$sql ORDER BY elapsed_time DESC FETCH FIRST 20 ROWS ONLY |
| “Which queries use the most CPU?” | “CPU最高的SQL” / “CPU密集型查询” | ORDER BY cpu_time DESC on v$sql |
Agent 加载 skill 时同时获得中英文触发路径——英文保留作为 fallback,中文提供第一优先级匹配。不需要额外的路由层,不需要改框架代码,只加一列。
九、这张表不做什么
明确边界和明确能力同样重要:
不适用的场景:
- 分析型 SQL:环比、同比、运行合计、多层次 CTE——需要
sql-patterns.md - 性能调优决策:该加什么索引、Hint 怎么用——需要
sql-tuning.md - 跨数据库:所有视图都是 Oracle 专有(
v$、dba_),MySQL/PostgreSQL 无法复用
加载时的注意事项:
dba_hist_*系列查询需要 Diagnostics Pack 许可v$session、dba_users需要 SELECT ANY DICTIONARY 或对应系统权限- KILL SESSION 需要 ALTER SYSTEM 权限
all_scheduler_*视图仅显示当前用户有权限看到的 Job——不是全局视图
十、总结
这张 13KB 的小文件,本质上是一本给 AI Agent 读的「Oracle 运维速查手册」:
| 它提供什么 | 它不提供什么 |
|---|---|
锁诊断 → v$session 自连接 |
怎么写窗口函数 |
慢查询 → v$sql + AWR 两套方案 |
索引该加在哪列上 |
定时任务 → dba_scheduler_* 三表关联 |
SQL 语法教程 |
| 模糊需求 → 追问清单 | 业务逻辑建议 |
| 危险操作 → 安全约束规则 | 性能调优策略 |
它的设计哲学很简单:AI 不需要背下 Oracle 的全部系统视图,只需要知道「用户说锁 → 查 v$session.blocking_session」、「用户说慢 → 查 v$sql 按 elapsed_time 排序」。
对于每天跟 Oracle 打交道的工程师,把这个skill配进 Agent,相当于让 AI 默认自带一个中级 DBA 的排障直觉——知道查哪张表、用哪个列、怎么关联。而在此基础上再加以中文优化,则让这张表从「英文手册」真正变成「中文团队的生产力工具」。
📎 源文件:https://github.com/oracle/skills/blob/main/db/agent/nl-to-sql-patterns.md
更多推荐




所有评论(0)