拒绝删库跑路!我用 AI大模型+AST安全沙箱 手搓“信创智能数据库助手”,让老板用大白话查达梦/openGauss,DBA看完直接跪了!
方言壁垒:达梦的 ROWNUM 和 Oracle 像但又不完全像;openGauss 的 MERGE INTO 语法和 MySQL 的 ON DUPLICATE KEY UPDATE 完全不同;人大金仓还有各种“兼容模式”的坑。
慢SQL黑盒:国产库的执行计划(Explain)输出格式和 Oracle/MySQL 有差异,很多老DBA看着 openGauss 的流式执行计划(Stream)一脸懵逼,调优全靠猜。
AI 落地风险:市面上的 NL2SQL(自然语言转SQL)开源工具(如 Vanna、Chat2DB),默认都是针对 MySQL/PostgreSQL 的。直接用在信创库上,不仅函数报错,更可怕的是没有企业级安全拦截,大模型一旦生成 DDL 或无条件的 DML,生产库直接原地升天。
我的解法:
自己撸一个 企业级信创智能数据库助手(Agent)!用 RAG(检索增强生成) 注入信创元数据与方言知识,用 JSqlParser 构建 AST 级安全沙箱 死死守住生产底线,用 Explain JSON 解析 让大模型化身“资深调优专家”。今天,我把这套生产级、防幻觉、带兜底的代码全盘托出!
二、 架构设计:企业级信创 AI 助手的“三位一体”
在动手写代码前,咱们得先理清架构。企业级 AI 助手绝不是“Prompt + API”那么简单,它必须是一个具备感知、决策、执行、反思能力的 Agent。
🏗️ 智能数据库助手架构图
核心设计思想:
方言隔离(Dialect Isolation):不同信创库(达梦、openGauss)的函数和系统表差异巨大,必须在 Prompt 层做严格的方言注入。
AST 绝对防御(AST Defense):永远不要相信大模型生成的 SQL! 必须在执行前,用 SQL 解析器(如 JSqlParser)将其解析为抽象语法树(AST),进行白名单/黑名单校验。
执行计划降维(Explain Parsing):国产库的 Explain 文本格式大模型很难理解,必须将其转换为 JSON 格式,并提取核心算子(如 Seq Scan, Hash Join),再喂给大模型分析。
三、 核心代码实战:生产级 AI 助手引擎(干货拉满)
老铁,接下来是全文最值钱的部分。这套代码是我们团队在金融信创核心系统里扛住审计、零事故运行的工程级代码。
3.1 模块一:信创元数据 RAG 与方言 Prompt 注入(让 AI 懂“土话”)
大模型默认只懂 MySQL/PG,让它写达梦 SQL 它会把 NVL 写成 IFNULL,把 SYSDATE 写成 NOW()。我们必须通过 RAG 动态注入信创方言。
// XinChuangDialectPromptBuilder.java
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
@Component
public class XinChuangDialectPromptBuilder {
// 【设计思想】:信创方言知识库。
// 将不同国产库的特有函数、系统表、避坑指南硬编码或存入向量库。
// 当用户提问时,动态拼接到 System Prompt 中,强制大模型遵守。
private static final Map<String, String> DIALECT_RULES = Map.of(
"DM", """
【达梦数据库(DM) 严格约束】:
字符串拼接必须使用 ||,禁止使用 CONCAT() (除非是兼容模式)。
判空必须使用 NVL(col, default) 或 COALESCE,绝对禁止使用 MySQL 的 IFNULL!
获取当前时间使用 SYSDATE 或 CURRENT_TIMESTAMP,禁止使用 NOW()。
分页查询推荐使用 LIMIT offset, count (DM8支持) 或 ROWNUM 子查询。
日期格式化使用 TO_CHAR(date, 'YYYY-MM-DD')。
""",
"OPENGAUSS", """
【openGauss 严格约束】:
判空使用 COALESCE 或 NVL。
字符串拼接使用 ||。
分页使用标准的 LIMIT count OFFSET offset。
如果涉及 UPSERT,必须使用 INSERT ... ON CONFLICT (pk) DO UPDATE SET ...,禁止使用 MySQL 的 ON DUPLICATE KEY UPDATE。
获取当前时间使用 CURRENT_TIMESTAMP 或 NOW()。
"""
);
/**
构建带有信创方言和 RAG 元数据的 System Prompt
【性能点】:元数据 RAG 检索只取 Top 5 相关表,避免 Token 超限。
*/
public String buildSystemPrompt(String dbType, List<String> ragTableSchemas) {
StringBuilder prompt = new StringBuilder();
prompt.append("你是一个资深的信创数据库专家与 SQL 架构师。你的任务是将用户的自然语言转化为绝对安全、高效的 SQL 语句,或诊断慢 SQL。nn");
// 1. 注入方言约束 (核心!)
String dialectRule = DIALECT_RULES.getOrDefault(dbType.toUpperCase(), "未知数据库类型,请使用标准 SQL。");
prompt.append("### 目标数据库方言与严格约束n").append(dialectRule).append("n");
// 2. 注入 RAG 检索到的表结构 (Schema)
prompt.append("### 相关数据库表结构 (Schema)n");
prompt.append("sqln");
for (String schema : ragTableSchemas) {
prompt.append(schema).append("n");
}
prompt.append("n");
// 3. 注入安全约束 (防删库)
prompt.append("""
### 绝对安全红线 (违反将导致生产事故):
只能生成 SELECT 查询语句!绝对禁止生成 INSERT, UPDATE, DELETE, DROP, TRUNCATE, ALTER 等任何修改数据的语句!
如果用户意图是修改数据,请礼貌拒绝并回复:“抱歉,作为智能助手,我仅支持数据查询与诊断。”
SELECT 语句必须包含 LIMIT 限制(默认 LIMIT 100),绝对禁止全表无限制扫描!
输出必须是严格的 JSON 格式:{"sql": "你的SQL", "thought": "你的思考过程"}。
""");
return prompt.toString();
}
}
3.2 模块二:AST 级安全沙箱(防删库的“生死线”)
这是全文最重要的一段代码!没有它,你的 AI 助手就是个定时炸弹!
大模型是有“幻觉”的,哪怕你在 Prompt 里写了“禁止 DELETE”,它有时候还是会“自作聪明”地生成一句 DELETE FROM users。我们必须在 Java 层用 JSqlParser 把 SQL 解析成 AST(抽象语法树),进行物理拦截!
// SqlSecuritySandbox.java
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.drop.Drop;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.truncate.Truncate;
import org.springframework.stereotype.Component;
@Component
public class SqlSecuritySandbox {
/**
AST 级 SQL 安全校验
【设计思想】:不依赖正则表达式(正则容易被绕过,如 /**/ 注释或换行),
而是将 SQL 解析为语法树,从 AST 根节点判断语句类型,从 WHERE 子句判断是否带条件。
* @param sql 大模型生成的 SQL
@return 校验结果,包含是否放行及拦截原因
*/
public SandboxResult validate(String sql) {
if (sql == null || sql.trim().isEmpty()) {
return SandboxResult.block("SQL 为空");
}
try {
// 【性能点】:JSqlParser 解析复杂 SQL 可能耗时几十毫秒,建议设置超时或异步处理
Statement statement = CCJSqlParserUtil.parse(sql);
// 1. 拦截 DDL (DROP, TRUNCATE, ALTER)
if (statement instanceof Drop || statement instanceof Truncate) {
return SandboxResult.block("🚨 致命拦截:检测到 DDL 操作 (DROP/TRUNCATE),已触发安全熔断!");
}
// 2. 拦截 DML (INSERT, UPDATE, DELETE)
if (statement instanceof Update) {
return SandboxResult.block("🚨 致命拦截:检测到 UPDATE 操作,智能助手仅允许 SELECT!");
}
if (statement instanceof Delete) {
Delete delete = (Delete) statement;
// 【边界处理】:哪怕大模型生成了 DELETE,如果它带了 WHERE,我们这里也直接拦截。
// 如果业务确实需要 AI 辅助删除,这里可以改为:如果 delete.getWhere() == null 则拦截。
return SandboxResult.block("🚨 致命拦截:检测到 DELETE 操作,智能助手仅允许 SELECT!");
}
// 3. 校验 SELECT 的安全性 (防全表扫描)
if (statement instanceof Select) {
Select select = (Select) statement;
if (select.getSelectBody() instanceof PlainSelect) {
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
// 【易错点】:检查是否包含 LIMIT。如果没有 LIMIT,大模型可能生成 SELECT * FROM huge_table,直接打爆内存!
if (plainSelect.getLimit() == null) {
return SandboxResult.block("⚠️ 安全拦截:SELECT 语句缺少 LIMIT 限制,存在全表扫描风险!请让 AI 重新生成带 LIMIT 的 SQL。");
}
// 【进阶校验】:可以遍历 AST,检查是否包含危险函数(如 pg_sleep, dbms_pipe 等可能导致拒绝服务的函数)
// 这里省略具体的 Visitor 模式遍历代码
}
}
// 4. 校验通过,放行
return SandboxResult.allow(sql);
} catch (Exception e) {
// 【边界处理】:如果 JSqlParser 解析失败(说明大模型生成了语法错误的“乱码SQL”),直接拦截!
return SandboxResult.block("⚠️ 语法解析失败:AI 生成的 SQL 存在语法错误 (" + e.getMessage() + "),已拦截。");
}
}
}
// 沙箱结果 POJO
@Data
@AllArgsConstructor
public class SandboxResult {
private boolean allowed;
private String finalSql;
private String blockReason;
public static SandboxResult allow(String sql) {
return new SandboxResult(true, sql, null);
}
public static SandboxResult block(String reason) {
return new SandboxResult(false, null, reason);
}
}
3.3 模块三:慢 SQL 智能诊断与信创 Explain 解析(让 AI 化身调优专家)
老板问:“这个报表为什么跑了 10 秒?”
DBA 看 Explain 看不懂?让大模型来!但绝不能直接把 Explain 的纯文本扔给大模型,它会被那些缩进和箭头搞晕。我们必须提取 Explain 的 JSON 格式,并做“降维提取”。
// SlowSqlDiagnosisAgent.java
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@Service
public class SlowSqlDiagnosisAgent {
private final JdbcTemplate jdbcTemplate;
private final ObjectMapper objectMapper;
private final LlmClient llmClient; // 假设封装的大模型调用客户端
public SlowSqlDiagnosisAgent(JdbcTemplate jdbcTemplate, ObjectMapper objectMapper, LlmClient llmClient) {
this.jdbcTemplate = jdbcTemplate;
this.objectMapper = objectMapper;
this.llmClient = llmClient;
}
/**
诊断慢 SQL 并给出优化建议
【设计思想】:
获取信创库的 Explain JSON (如 openGauss 的 EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON))。
提取核心算子(如 Seq Scan, Hash Join, Sort)和耗时瓶颈。
将降维后的执行计划喂给大模型,让其结合信创库特性给出索引建议。
*/
public DiagnosisReport diagnose(String slowSql, String dbType) {
try {
// 1. 获取执行计划 (以 openGauss/PG 为例)
// 【易错点】:不同信创库获取 Explain 的命令不同!
// 达梦:EXPLAIN SELECT ... (返回文本,需正则解析)
// openGauss:EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...
String explainSql = buildExplainSql(slowSql, dbType);
// 执行 Explain,获取结果
String explainResult = jdbcTemplate.queryForObject(explainSql, String.class);
// 2. 执行计划降维提取 (防止 Token 爆炸)
String simplifiedPlan = simplifyExplainPlan(explainResult, dbType);
// 3. 构建诊断 Prompt
String prompt = String.format("""
你是一个资深的 %s 数据库调优专家。
以下是一条慢 SQL 及其执行计划的核心摘要。
【慢 SQL】:
%s
【执行计划摘要】:
%s
【你的任务】:
分析导致慢查询的核心瓶颈(如:全表扫描 Seq Scan、嵌套循环过深 Nested Loop、排序溢出 Sort)。
给出具体的索引创建建议(必须符合 %s 的语法)。
给出 SQL 改写建议(如:避免隐式转换、优化 JOIN 顺序)。
输出严格的 JSON 格式:{"bottleneck": "...", "index_suggestion": "...", "rewrite_sql": "..."}
""", dbType, slowSql, simplifiedPlan, dbType);
// 4. 调用大模型
String llmResponse = llmClient.chat(prompt);
return objectMapper.readValue(llmResponse, DiagnosisReport.class);
} catch (Exception e) {
return DiagnosisReport.error("诊断失败: " + e.getMessage());
}
}
/**
降维提取执行计划
【性能点】:完整的 Explain JSON 可能高达几 MB,直接喂给 LLM 会超限且昂贵。
我们只提取 Node Type, Relation Name, Total Time, Rows 等核心字段。
*/
private String simplifyExplainPlan(String explainJson, String dbType) throws Exception {
if ("OPENGAUSS".equalsIgnoreCase(dbType) || "POSTGRESQL".equalsIgnoreCase(dbType)) {
JsonNode root = objectMapper.readTree(explainJson);
JsonNode plan = root.get(0).get("Plan");
StringBuilder sb = new StringBuilder();
extractNodes(plan, sb, 0);
return sb.toString();
}
// 达梦的 Explain 通常是文本格式,这里需要写正则提取 "NSET", "PRJT", "SLCT", "SSEK"(索引扫描), "CSCN"(全表扫描) 等关键字
return "达梦执行计划文本解析逻辑 (省略)...";
}
private void extractNodes(JsonNode node, StringBuilder sb, int depth) {
if (node == null) return;
String nodeType = node.has("Node Type") ? node.get("Node Type").asText() : "Unknown";
String relation = node.has("Relation Name") ? node.get("Relation Name").asText() : "";
double time = node.has("Actual Total Time") ? node.get("Actual Total Time").asDouble() : 0;
long rows = node.has("Actual Rows") ? node.get("Actual Rows").asLong() : 0;
// 【核心逻辑】:只记录耗时超过 10ms 或关键算子(如 Scan, Join, Sort)的节点
if (time > 10 || nodeType.contains("Scan") || nodeType.contains("Join") || nodeType.contains("Sort")) {
sb.append(" ".repeat(depth))
.append(String.format("-> %s on %s (Time: %.2fms, Rows: %d)n", nodeType, relation, time, rows));
}
// 递归遍历子节点 (Plans)
if (node.has("Plans")) {
for (JsonNode child : node.get("Plans")) {
extractNodes(child, sb, depth + 1);
}
}
}
private String buildExplainSql(String sql, String dbType) {
if ("OPENGAUSS".equalsIgnoreCase(dbType)) {
return "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) " + sql;
} else if ("DM".equalsIgnoreCase(dbType)) {
return "EXPLAIN " + sql; // 达梦返回文本
}
return "EXPLAIN " + sql;
}
}
@Data
public class DiagnosisReport {
private String bottleneck;
private String indexSuggestion;
private String rewriteSql;
private String error;
public static DiagnosisReport error(String msg) {
DiagnosisReport r = new DiagnosisReport();
r.setError(msg);
return r;
}
}
四、 血泪避坑指南(全网首发,建议收藏)
墨夶,代码给你了,但下面这几个坑,是我带着团队在凌晨三点的机房里爬出来的,你一定要写进你的技术备忘录里!
💣 坑1:大模型的“隐式转换”陷阱与索引失效
现象:AI 生成的 SQL 查得很慢,DBA 一看,表上明明有索引,为什么走了全表扫描(CSCN / Seq Scan)?
真相:大模型在生成 WHERE 条件时,经常搞错字段类型。比如 user_id 是 VARCHAR,大模型生成了 WHERE user_id = 12345(数字)。在达梦和 openGauss 中,字符串字段与数字比较,会触发隐式转换,导致索引瞬间失效!
解法:在 RAG 注入表结构时,必须把字段的 Data Type(数据类型)一并喂给大模型,并在 Prompt 中强调:“严格匹配字段类型,VARCHAR 类型的值必须加单引号!”
💣 坑2:达梦的“页大小”与超长 SQL 截断
现象:AI 生成了一段包含几十个字段的超长 SELECT 语句,在 MySQL 里跑得好好的,在达梦里直接报错 SQL statement too long 或内存溢出。
真相:达梦数据库在初始化时有一个 PAGE_SIZE(页大小) 参数(默认 8KB)。如果单条 SQL 语句的长度或单行数据的大小超过了页大小的限制,就会直接报错!
解法:在 AST 安全沙箱中,增加SQL 长度阈值校验(如超过 4000 字符直接拦截)。同时,在 Prompt 中约束大模型:“尽量精简 SELECT 字段,禁止无脑 SELECT *”。
💣 坑3:openGauss 的“列存表”与点查灾难
现象:业务用 AI 助手查一条订单明细(WHERE order_id = ‘xxx’),结果跑了 5 秒钟!
真相:openGauss 支持列存表(Column Store),这种表适合大规模聚合分析(OLAP),但极其不擅长单条记录的点查(Point Query)!如果业务表被建成了列存表,点查会扫描整个列的元数据,慢得令人发指。
解法:在智能助手的元数据 RAG 中,必须提取表的存储引擎类型(行存/列存)。如果大模型发现用户在列存表上做高频点查,应在“反思阶段”给出警告:“该表为列存表,不适合单条主键查询,建议业务层改用 Redis 或行存表。”
五、 结语:信创不是退步,而是重构的契机
老铁们,很多人觉得搞信创就是“换个国产壳子,忍受难用的工具和黑盒”。错!真正的信创落地,是用现代化的 AI 工程思维,去填平原厂工具在云原生时代的空白,实现架构的升维。
当我们用 RAG 让大模型精通达梦/openGauss 的方言,当 AST 安全沙箱死死守住生产库的底线,当 AI 瞬间解析出 openGauss 复杂的流式执行计划并给出精准的索引建议,那种掌控感,才是技术人最大的浪漫。
“AI 从来不是来取代 DBA 的,而是来取代那些不会用 AI 的 DBA 的。懂信创、懂安全、懂业务的 AI 助手,才是企业数智化的终极武器。”
更多推荐

所有评论(0)