在 KES AI 数据库上搭一条 NL2SQL 链路:语义、召回、权限各放哪

喜欢把枯燥的技术文档变成"手把手教程",不讲空话,只讲怎么连、怎么写、怎么优化。


让大模型写 SQL,第一周很爽,第三周就想删功能。

原因很简单:它写得出来,但写不对。amount 是含税还是不含税,它不知道。"上季度"是自然季度还是财年季度,它靠猜。"活跃客户"按谁的定义算,它自己发明一个。

这不是模型不行。是你没给它一套能查的语义层。

上个月那篇写了 AI 数据库的四段链路,语义建模只讲了开头(注释、词表、语义视图)。这篇往深里走一层:NL2SQL 场景下,语义层到底该建成什么样,才能从"能生成"走到"能执行"。

四件事:语义层该有哪些对象、怎么限制模型能看到的范围、歧义怎么处理、执行怎么设护栏。每节都给表和 SQL。

写完之后,06 节会把整条链路落到金仓 KES AI 数据库上跑一遍------它的语义构建、向量检索、行级安全都在内核里,正好是 NL2SQL 最需要的那三样。省掉的不只是一个中间件,是整条链路上的"两套真相"。

说明:DDL 是通用写法,类型和名称按你环境的版本来。重点是对象设计和取舍逻辑,这部分换成哪家库都一样。


00 先分清三层,别混着做

大多数 NL2SQL 项目死在"只有一层":把 CREATE TABLE 语句一股脑塞进 prompt,指望模型自己理解业务。

三层得分开:

层 装什么 谁维护 不做的后果
物理层 表、字段、索引、分区 DBA 模型看到的是一堆缩写字段名
语义层 实体、维度、指标、口径、关系 数据团队 + 业务 同一个词在不同问句里答案不一样
访问层 视图、函数、角色、行级安全、审计 DBA + 安全 模型可能生成越权查询

物理层已经有了,访问层通常也有,缺的几乎都是中间那层------而它恰好是决定准确率的那层。

这三层的边界要划清楚。 物理层改了(加字段、拆表),语义层要跟着动,但不该让业务口径跟着变。语义层改了(口径调整),访问层不用动。这个依赖方向理清楚了,后面才不会一改全崩。


01 语义层的四个对象:指标、维度、关系、日历

语义层不是一份文档,是一组能被查询的表。少了任何一个,模型都会缺信息。

指标(metric):口径的唯一来源

这是最核心的一张表。所有"能问出来的数"都应该在这里有定义。

sql 复制代码
CREATE TABLE sem_metric (
    metric_code  VARCHAR(64) PRIMARY KEY,   -- revenue, active_customer
    metric_name  VARCHAR(64) NOT NULL,
    synonyms     TEXT[],                    -- 口语说法:营收、收入、销售额
    agg_expr     TEXT NOT NULL,             -- SUM(o.amount)
    base_object  VARCHAR(200) NOT NULL,     -- t_order
    grain        VARCHAR(32) NOT NULL,      -- day / month / quarter
    additivity   VARCHAR(16) NOT NULL,      -- additive / semi / non
    filter_expr  TEXT,                      -- 默认过滤:status NOT IN (5)
    dims         TEXT[],                    -- 允许下钻的维度
    owner        VARCHAR(64),               -- 口径负责人
    version      INT DEFAULT 1,
    note         TEXT,
    updated_at   TIMESTAMP DEFAULT now()
);

三个字段决定了这张表有没有用:

additivity(可加性)。 这是 NL2SQL 错答的头号来源。营收是可加的,跨月能相加;库存余额、账户余额、在途数量是半可加的,跨时间相加就是错的,只能跨维度相加。模型不知道这个区别,除非你写进表里,并且在 prompt 里明确告诉它"半可加指标禁止按时间 SUM"。

filter_expr(默认过滤)。 测试单、内部单、作废单,业务人员说话时默认排除,模型不知道。没有这个字段,同一句"上个月多少单",两个人问出两个数。

dims(允许下钻的维度)。 不是所有维度都对这个指标有意义。把"地区"挂到"人均通话时长"上,能查出数,但是废话。白名单比黑名单可靠。

维度(dimension):层级和取值要能查

sql 复制代码
CREATE TABLE sem_dimension (
    dim_code     VARCHAR(64) PRIMARY KEY,   -- region, product, channel
    dim_name     VARCHAR(64) NOT NULL,
    synonyms     TEXT[],
    source_expr  TEXT NOT NULL,             -- 从哪算出来
    hierarchy    TEXT[],                    -- 国家 > 省 > 市
    value_table  VARCHAR(200),              -- 取值清单表,供模型理解枚举
    note         TEXT
);

value_table 这一项经常被跳过,代价很大。用户问"华东区",模型得知道"华东"是 region_code IN ('01','02','03') 还是 region_name LIKE '华东%'。把枚举值单独存一张小表,让模型能查,比在 prompt 里塞一堆例句可靠。

关系(join):只给它白名单

模型自己发明 join 条件是另一个高频事故源,尤其是多对多关系------一次错误的 join 会让金额翻倍,SQL 还能跑通。

sql 复制代码
CREATE TABLE sem_join (
    from_object  VARCHAR(200) NOT NULL,
    to_object    VARCHAR(200) NOT NULL,
    join_type    VARCHAR(16)  NOT NULL,     -- inner / left
    on_expr      TEXT         NOT NULL,     -- o.customer_id = c.id
    card         VARCHAR(16)  NOT NULL,     -- 1:1 / 1:N / N:1 / N:N
    note         TEXT,
    PRIMARY KEY (from_object, to_object)
);

card 字段是给校验器用的:生成出来的 SQL 如果出现 N:N 的 join,直接拦下来,不进执行。没有白名单的组合,一律不生成------这条规则听起来很硬,但它把"答错"变成了"答不了",后者对业务友好得多。

日历(calendar):自然月、财月、时区,一次说清

sql 复制代码
CREATE TABLE sem_calendar (
    cal_date     DATE PRIMARY KEY,
    fiscal_year  INT  NOT NULL,
    fiscal_period INT NOT NULL,             -- 财年第几期
    is_workday   BOOLEAN NOT NULL,
    timezone     VARCHAR(32) DEFAULT 'Asia/Shanghai'
);

"上季度"用自然季度还是财年季度,这个差异在有的行业能差出一个月的数据。日历表是最便宜的解决方案,比在 prompt 里写说明靠谱。

踩坑提醒 :四张表都要有 updated_at 和 owner。语义层最大的风险不是缺,是旧。口径改了没人同步,模型会持续给出"看起来很对"的错误结果,而且错得很有说服力。


02 让模型只看到"能组合的东西"

语义层建好之后,下一个问题:prompt 里塞什么?

全量塞进去,几万行元数据,token 账单先崩。什么都不塞,模型又回到猜。

正确做法是两级收窄:先按问题召回相关的指标和维度,再把"允许的组合"明确写进 prompt。

第一级召回,比关键词匹配更靠谱的做法是把指标描述向量化,用库内近邻检索去捞。指标描述就是"名称 + 同义词 + 口径说明"拼起来的一段文本,它和用户问句在语义空间里天然接近:

sql 复制代码
-- 指标表加一列向量,描述文本由脚本在写入时同步生成
ALTER TABLE sem_metric ADD COLUMN emb VECTOR(768);

CREATE INDEX idx_sem_metric_emb ON sem_metric
USING hnsw (emb vector_cosine_ops)
WITH (m = 16, ef_construction = 200);

SET hnsw.ef_search = 80;

-- 语义召回 + 关键词兜底,一条 SQL 出结果
WITH vec_hit AS (
    SELECT metric_code, 1 - (emb <=> :qvec) AS sim
    FROM sem_metric
    WHERE emb IS NOT NULL
    ORDER BY emb <=> :qvec
    LIMIT 20
),
kw_hit AS (
    SELECT metric_code, 1.0 AS sim
    FROM sem_metric
    WHERE metric_name = ANY(:terms) OR synonyms && :terms
)
SELECT m.metric_code, m.metric_name, m.agg_expr, m.grain,
       m.additivity, m.filter_expr, m.dims, m.note,
       COALESCE(v.sim, 0) * 0.7 + COALESCE(k.sim, 0) * 0.3 AS score
FROM sem_metric m
LEFT JOIN vec_hit v ON v.metric_code = m.metric_code
LEFT JOIN kw_hit k ON k.metric_code = m.metric_code
WHERE v.metric_code IS NOT NULL OR k.metric_code IS NOT NULL
ORDER BY score DESC
LIMIT 10;

为什么两路都要:用户说"上季度营收",关键词能精确命中;说"这生意怎么样",关键词一个都命中不了,向量能把 revenue、order_count、gross_margin 一起捞出来。相反的情况也有------用户在问编号、型号这类标识符时,向量表达能力很差,关键词才是准的。混合召回,权重从 7:3 起调。

第二级,按召回的指标带出可用维度和白名单关系:

sql 复制代码
SELECT d.dim_code, d.source_expr, d.value_table
FROM sem_dimension d
WHERE d.dim_code = ANY(:allowed_dims);

SELECT from_object, to_object, join_type, on_expr, card
FROM sem_join
WHERE from_object = ANY(:objects)
   OR to_object   = ANY(:objects);

然后把这些结果整理成一段结构化描述,而不是原始 DDL:

ini 复制代码
可用指标:
- revenue(营收):SUM(o.amount),含税、不含运费,排除 status=5;
  可下钻:region, product, channel;可加,可按时间聚合
- inventory_balance(库存余额):SUM(s.qty),期末值,半可加,禁止按月求和

可用关系(仅限以下组合):
- t_order -> t_customer (N:1, ON o.customer_id = c.id)
- t_order -> t_order_item (1:N, ON o.order_id = i.order_id)

时间口径:自然月,日历表 sem_calendar;问"上季度"按 fiscal_period 取

这段话比塞二十张表的 DDL 有效得多。模型缺的不是表结构,是可组合性信息------它得知道哪些维度能跟哪些指标配。

语义视图:高频问题别让模型现算

同一个问题每天都有人问,就没必要每天让模型重新生成一次 SQL 再执行一次。

vbnet 复制代码
CREATE VIEW v_sem_revenue_monthly AS
SELECT c.fiscal_year,
       c.fiscal_period,
       o.region,
       o.channel,
       SUM(o.amount)                 AS revenue,
       COUNT(DISTINCT o.customer_id) AS customers
FROM t_order o
JOIN sem_calendar c ON c.cal_date = o.biz_date
WHERE o.status NOT IN (5)
GROUP BY 1, 2, 3, 4;

视图一旦存在,模型只需要写 SELECT ... FROM v_sem_revenue_monthly WHERE fiscal_period = :p。生成难度下降一个数量级,准确率上升一个数量级。

这套东西建在库里还是建在库外,差别比想象中大。

建在库外的做法是:YAML / dbt / BI 工具里维护一份语义模型,再翻译成 SQL 发给数据库。问题是它和库里的真实结构是两套真相------库里的表改了、注释改了、权限策略改了,外部语义层不知道。时间一长,语义层描述的和库里跑的对不上,而且没人会定期核对。

建在库内的做法是把指标、维度、关系、日历都做成库对象。好处很实在:模型查语义层和查业务数据走同一个连接、同一套权限、同一份备份、同一套审计;口径变更和数据结构变更在同一处提交,不会分叉;语义视图本身就是视图,SQL 直接可执行,不需要"翻译"这一层。

金仓这套 AI 数据库能力里把"库内语义构建"放在内核侧,逻辑就在这里------语义层不该是数据库外面的一份说明书,它应该是数据库的一部分。落到实现上,就是注释、词表、语义视图、权限策略都跟着库走,智能体生成 SQL 时读的是这份元数据,不是一份可能已经过期三天的文档。

踩坑提醒:语义视图不要包太多层。视图套视图套视图,执行计划会崩,而且模型生成的 SQL 一过滤,谓词可能下推不进去。两层以内,超了就落物化视图。


03 歧义处理:会反问比会猜值钱

真实用户的问题,一大半是有歧义的。

"最近业绩怎么样"------最近是多久?业绩是营收还是订单量?按什么维度看?

这时候模型只有三个选择:猜、反问、拒答。猜是最坏的选择,因为猜出来的结果看起来是对的。

我给这类问题定了三档判定,写进 prompt 和校验器里:

档位 判定条件 例子 处理
可直答 指标唯一、时间明确、维度合法 上个月华东区营收 直接生成并执行
需澄清 指标多义、时间缺失、维度越界 最近业绩怎么样 反问一到两个问题,不给结果
应拒答 涉及敏感字段、跨权限、超出语义层 每个人的薪资明细 说明原因,给替代口径

澄清不是随便问一句。有效的澄清有三个特征:

一次只问一个点。 连问三个问题,用户就关了。

给出候选而不是开放问题。 不要问"您想看哪个指标",要问"看营收还是订单量"。

记住上下文。 用户上一轮说了"华东",这一轮问"那上个月呢",别再问一遍地区。

sql 复制代码
-- 澄清记录落表,用于后续优化语义层
CREATE TABLE nl2sql_clarify (
    qid          BIGSERIAL PRIMARY KEY,
    raw_question TEXT NOT NULL,
    ambiguous_on TEXT[] NOT NULL,        -- 歧义点:metric / time / dim
    asked        TEXT,                   -- 实际问出去的话
    chosen       TEXT,                   -- 用户的选择
    created_at   TIMESTAMP DEFAULT now()
);

这张表是语义层的"缺口清单"。同一个歧义点出现二十次,说明语义层缺一条定义,该补指标或者补同义词了。歧义不是模型的缺陷,是语义层没覆盖到的证据。

踩坑提醒:不要把"澄清率"当成坏指标。澄清率高说明系统知道自己在猜;澄清率接近零、准确率也上不去,说明它在瞎猜还装懂。


04 执行护栏:生成的 SQL 不能直接跑

到这一步,SQL 生成了,能不能执行是另一回事。

我见过最危险的做法,是用业务账号去执行模型生成的 SQL------那等于把库的权限全交给一个概率模型。

护栏四件事,缺一不可。

只读账号 + 资源限制

ini 复制代码
CREATE ROLE role_nl2sql LOGIN PASSWORD '***';

ALTER ROLE role_nl2sql SET default_transaction_read_only = on;
ALTER ROLE role_nl2sql SET statement_timeout = '30s';
ALTER ROLE role_nl2sql SET idle_in_transaction_session_timeout = '60s';
ALTER ROLE role_nl2sql SET work_mem = '64MB';

-- 只授语义视图,不授基表
GRANT USAGE ON SCHEMA sem TO role_nl2sql;
GRANT SELECT ON ALL TABLES IN SCHEMA sem TO role_nl2sql;

statement_timeout 这条最容易被忽略。模型偶尔会生成一个没有过滤条件的全表扫描,没有超时就是一次生产事故。

行级安全:让权限跟着数据走

多租户、多分支机构的数据,不能靠"生成时记得加 where"。靠一次就可能漏一次。

sql 复制代码
ALTER TABLE t_fact_order ENABLE ROW LEVEL SECURITY;

CREATE POLICY p_tenant ON t_fact_order
USING (tenant_id = current_setting('app.tenant_id')::int);

-- 应用建立连接后绑定身份,模型生成的 SQL 无论怎么写都绕不过
SET app.tenant_id = '1024';

策略绑在表上,模型生成的查询、BI 工具连上来的查询、手工执行的查询,一视同仁。这一层是金仓库内原生安全管控做的事------行级安全、脱敏视图、全链路审计都在内核里,NL2SQL 链路不需要额外加一个"权限代理"组件。

生成后校验:三道自动检查

执行之前过一遍,能拦掉大部分废 SQL:

检查 做法 拦什么
对象白名单 解析出的表/视图必须在授权清单里 模型幻觉出来的表名、跨 schema 访问
危险语句 出现 DDL、DML、FOR UPDATE 直接拒 只读账号也会被误用
代价预估 先 EXPLAIN,预估行数超阈值转人工或加提示 全表扫描、错误 join 导致的笛卡尔积
sql 复制代码
EXPLAIN (FORMAT JSON, COSTS OFF)
SELECT region, SUM(amount) FROM t_order WHERE biz_date >= '2026-10-01' GROUP BY region;

预估行数超过千万级,就不该自动执行------要么让模型加过滤条件重写,要么转给人工确认。

审计:把"谁问了什么"记全

审计这件事,外挂架构最难做齐。检索和生成在两个系统里,埋点漏一处就断链。

在库内做,链路天然完整:一条 SQL 进去,审计日志里有"谁、什么时候、什么语句、影响多少行";再配一张应用侧的生成记录表,两边按 qid 对上,就能还原一次完整的问答。

sql 复制代码
CREATE TABLE nl2sql_log (
    qid        BIGINT PRIMARY KEY,
    user_id    VARCHAR(64),
    question   TEXT,
    gen_sql    TEXT,
    est_rows   BIGINT,
    act_rows   BIGINT,
    status     VARCHAR(16),      -- ok / rejected / timeout / error
    cost_ms    INT,
    created_at TIMESTAMP DEFAULT now()
);

这张表不只是合规材料,是准确率分析的原料。哪类问题拒得多、哪类超时多,看一眼就知道语义层该往哪补。

踩坑提醒:行级安全开启后,部分查询的执行计划会变差(谓词下推受限)。压测必须在 RLS 打开的状态下做,用关掉策略的环境测出来的数字没有意义。


05 评估:没有 golden set,就别说准确率

"我们的 NL2SQL 准确率 90%"------听到这话我第一个问题是:拿什么测的?

自测的常见做法是找几个工程师自己提问,问的都是结构清晰的句子。真实用户的问法不是这样:缩写、错别字、口语、指代、跨轮追问。

建评估集的做法:

sql 复制代码
CREATE TABLE eval_case (
    case_id      BIGSERIAL PRIMARY KEY,
    question     TEXT NOT NULL,
    expect_kind  VARCHAR(16) NOT NULL,   -- answer / clarify / reject
    expect_sql   TEXT,                   -- 参考答案,允许等价改写
    gold_value   TEXT,                   -- 期望结果值(聚合类)
    tags         TEXT[],                 -- 时间口径/多表join/半可加/敏感
    created_by   VARCHAR(64)
);

三条经验:

200 条起步,覆盖标签。 时间口径、多表 join、半可加指标、跨轮追问、敏感词,每类至少 20 条。总量不够就按标签补,别按数量凑。

判"结果对不对",不判"SQL 像不像"。 等价的 SQL 写法很多,比字符串相似度没有意义。聚合类比对数值,明细类比主键集合。

把澄清和拒答算进正确率。 该反问的时候直接给了答案,即便答案碰巧对,也算错------因为这培养了错误的使用习惯。

看板上的四个指标:

指标 定义 关注点
生成成功率 生成出可解析 SQL 的比例 低于 90% 查语义层覆盖度
执行成功率 执行未报错未超时的比例 低于 95% 查权限和超时配置
结果正确率 与 golden 一致的比例 核心指标,按标签拆开看
澄清率 触发反问的比例 突增说明口径有变更

06 在 KES AI 数据库上跑通:链路到底长什么样

前五节是通用的,这一节把它们落到具体产品上。金仓 KES AI 数据库的三条能力------库内语义构建、库内计算推理、库内原生安全管控------正好对应 NL2SQL 最吃紧的三个位置。下面给的是落位关系和代码,不是功能罗列。

6.1 语义层落进库:四张表、一组视图、一批注释

语义层在 KES 里没有特殊对象,就是普通表加普通视图。这件事的价值在于:它自动享受同实例的备份、权限、审计、复制,不需要单独为"语义层"准备一套运维。

vbnet 复制代码
-- 注释是语义层最外一层,模型先靠它定位字段
COMMENT ON COLUMN t_order.amount IS '订单金额,单位元,含税,不含运费,排除作废单';

-- 存量系统的注释可以直接读出来,生成词表初稿
SELECT c.relname, a.attname,
       col_description(c.oid, a.attnum) AS col_comment
FROM pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE c.relnamespace = 'public'::regnamespace
  AND a.attnum > 0 AND NOT a.attisdropped
  AND col_description(c.oid, a.attnum) IS NOT NULL;

这一步省的是前期最费时的工作量。跑了十年的系统,字段注释里往往攒着最完整的业务口径,直接读出来当词表初稿,比让业务团队重新口述一遍快得多,也准得多。

指标召回也走库内:sem_metric.emb 那一列(02 节的混合召回)配 HNSW 索引,<=> 算余弦距离,召回、过滤、打分在一条 SQL 里完成。不用把指标描述同步一份到外部向量库------同步链路一断,召回的就是过期元数据,这种错误很隐蔽,因为它照样返回结果。

6.2 方言必须跟实例的兼容模式对齐

这条是 NL2SQL 在国产库上最容易踩的坑,也是通用方案里很少提的。

KES 初始化实例时可以选兼容模式:原生 SQL 标准、Oracle、MySQL、SQL Server、PG 生态。同一条自然语言问句,在不同模式的实例上得生成不同写法的 SQL:

需求 Oracle 兼容 SQL 标准写法
按月截取 TRUNC(d, 'MM') date_trunc('month', d)
取前 10 行 ROWNUM <= 10 LIMIT 10
空值替代 NVL(x, 0) COALESCE(x, 0)
日期加减 d + 7(按天) d + INTERVAL '7 day'

做法是语义层里落一个 sql_dialect 字段,或者把方言写死在 prompt 里,生成时按实例模式渲染。

我踩过的坑是:开发库选的 Oracle 兼容,压测库为了迁就某套旧系统选了 MySQL 兼容,模型生成的 SQL 在两边表现不一致------一边跑得快,一边跑不动,评估出来的"准确率"是假的。评估环境必须和生成时的方言假设一致。

反过来说,兼容多模式对迁移项目是好事(Oracle 的老代码能少改),只是对 NL2SQL 提了个要求:方言要在语义层里显式化。你不写清楚,模型就按它训练数据里最常见的方言去猜。

6.3 生成交给智能体,复核留给人

发布会配套的全栈智能数据开发工具和多场景智能体里,开发侧的能力就包括 SQL 生成与巡检。

合理的分工:智能体负责拼 prompt、读语义层、召回指标、出 SQL 第一稿;人负责复核口径类断言------这个指标是不是他要的,这个过滤条件放得对不对。参数调整、索引变更这类动生产的动作,始终保留人工确认。

不合理的用法我也见过:让智能体连上生产库直接跑。04 节那些护栏就是为这一步准备的,不是可选项。

6.4 权限和审计不用再拼一层

RLS 绑在表上、脱敏视图给 AI 应用账号、审计日志记 SQL------这三件事在内核里,链路就不跨系统。跨系统的写法要额外维护"权限代理"组件,而权限代理本身就是新的故障点和新的越权入口。

前面 04 节已经给过代码,这里只补一句结论:NL2SQL 的越权事故,九成不是模型恶意,是链路上多了一个没人管的环节。

6.5 一张落位表

环节 落在哪 用 KES 的什么 替掉的外部组件
语义定义 库 表、视图、字段注释 YAML / dbt 语义层
指标召回 库 VECTOR 列 + HNSW + <=> 外部向量库 + 同步链路
候选收窄 库 SQL 过滤、打分、聚合 独立检索服务
权限 库 行级安全、脱敏视图 应用层权限代理
审计 库 审计日志 + SQL 本身 应用埋点
生成 工具侧 开发侧智能体 自建 prompt 服务
口径复核、评估 人 --- ---

这张表的读法是:左边三列是能力,最后一列是省下来的组件和它们各自的运维成本。

6.6 边界,得说清楚

说了这么多好处,也得说它管不了什么:

  • 它解决的是"链路不跨系统",不解决"口径定义对不对"。业务口径没吵明白,语义层写进去也是错的。
  • 向量召回的质量取决于描述文本写得好不好。指标描述潦草、新指标没补向量,召回就会跑偏,而且偏得没规律。
  • 兼容模式、ef_search、索引内存这些参数,都得按你自己实例的数据量和硬件测,别照抄。
  • 智能体生成的 SQL 一定要过校验器。它的价值是把第一稿写出来,不是替你做决定。

踩坑提醒 :语义层新增指标时,emb 列和 biz_glossary 必须同一次提交里一起更新。漏一次向量,这个指标从此只能靠关键词命中------用户换个说法就问不到,还查不出原因。

小结:库内语义 + 库内向量 + 库内安全,三条链路收在一处,NL2SQL 才有条件从"能演示"走到"敢上线"。省掉的组件不是为了省钱,是为了少三个会坏的环节。


三种做法,选之前先看代价

维度 直接给模型看 schema 外部语义层(YAML/dbt/BI) 库内语义层
上线速度 最快,一天能跑 demo 中等,要先建模型 中等,建表 + 建视图
准确率上限 低,靠模型猜口径 高,但要人维护 高,且与库同源
口径漂移风险 高 中高(语义层与库两套真相) 低(同一处提交)
权限一致性 差,容易越权 差,权限在应用侧拼 好,行级安全在内核
审计完整度 差 中,靠应用埋点 好,SQL 即审计对象
维护成本 低起步,后期爆炸 中,需专人 中,跟着库走

demo 阶段用第一种没问题。上生产的项目,第三种是唯一能把"准确率、权限、审计"三件事同时说圆的做法------金仓选的就是第三条路,把语义构建、向量计算、安全管控都放进内核,代价是这些东西必须跟着数据库版本升级,收益是链路里不存在"同步"这个环节。


决策框架:你的场景该做到哪一步

场景 建议做法 语义层投入
固定几张报表 直接做视图,不上 NL2SQL 不需要
高频问题 20 类以内 语义视图 + 结果缓存,模型只做参数填充 低
业务人员自助取数 完整语义层 + 澄清机制 高
跨部门敏感数据 不做 NL2SQL,走审批取数流程 ---
亿级以上明细表 先落汇总层,再开放问数 高(含物化)
面向外部客户 语义层 + 强护栏 + 人工兜底 很高

判断依据很简单:问题的组合空间有多大。 组合空间小,视图就够了;组合空间大,才值得建语义层。


深度分析:NL2SQL 的天花板在语义层,不在模型

这两年的模型进步很快,语法错误基本消失了。但准确率没有跟着线性提升------因为剩下的错误不是语法问题,是概念问题。

模型不知道你们公司的"营收"扣不扣退款,不知道"活跃"是 30 天还是 90 天,不知道库存余额不能按月相加。这些信息不在模型权重里,只在你的元数据里。

所以技术路线其实只有一条:把业务概念编译成机器可查的结构,再让模型去组合。

这也解释了一个现象:同一套模型,有的公司做到 85% 准确率,有的公司卡在 50%。差别不在 prompt 技巧,在语义资产的完备度。语义层做扎实的公司,模型升级是锦上添花;语义层空着的公司,换哪个模型都一样。

再往深一层看,"AI 数据库"这个说法真正的分量就在这里。把语义构建、向量计算、安全管控放进内核,表面上是在做功能集成,实际上解决的是 NL2SQL 里最难的那个结构性问题:语义层、权限策略、业务数据,能不能是同一份真相。 外挂架构里这三样分属三个系统,任何一次变更都可能让它们不同步,而不同步的后果是"结果看起来对、实际错",比报错难查十倍。

所以选型的时候,我会问一个很具体的问题:语义层里描述的 amount 和业务表里的 amount,改的时候是不是同一笔提交?答"是",这条路就走得通;答"要靠一个同步任务",那这个同步任务就是将来的事故源。

最后说一句清醒的:语义层解决不了"业务自己没定义清楚"的问题。口径在会上没吵完,写进表里也是错的。语义层的作用是把这个争议显性化------谁的指标、谁负责、什么时候改的,白纸黑字。


检查清单:上线前过一遍

语义层

  • 核心指标都在 sem_metric 里,且有 owner
  • 半可加指标标注 additivity,prompt 里有对应约束
  • 每个指标的默认过滤条件(测试单、作废单)显式写出
  • 维度有取值清单表,别让模型猜枚举
  • join 白名单维护完整,N:N 组合一律拦截
  • 日历表覆盖财年、自然月、时区
  • 指标 emb 与词表同一次提交更新,漏一次就永久失召

访问控制

  • 只读账号,statement_timeout 与 work_mem 已设
  • 只授语义视图,不授基表
  • 行级安全策略绑在表上,且压测在开启状态下做过
  • 生成后校验:对象白名单、危险语句、代价预估三道齐

落位(KES)

  • 实例兼容模式确认,语义层里 sql_dialect 与之一致
  • 评估环境与生产环境的兼容模式一致,别一边 Oracle 一边 MySQL
  • 指标向量索引参数(m、ef_construction、ef_search)有压测依据
  • 审计日志能还原"谁问了什么、生成了什么 SQL、执行结果行数"
  • 智能体生成的 SQL 一律过校验器,人工复核口径类断言

评估与监控

  • golden set ≥ 200 条,按标签覆盖
  • 判结果不判 SQL,澄清与拒答计入正确率
  • 生成/执行/正确率/澄清率四个指标上盘
  • 澄清记录有人定期看,转化成语义层的待补清单

最后

NL2SQL 这件事,做到最后会发现是个数据治理问题,不是 AI 问题。

模型负责把自然语言翻译成结构,语义层负责让这个结构有意义。前者是买来的,后者是攒出来的。攒的那部分,谁也替不了你。

如果这套链路要落在金仓上,我会先确认三件事:实例的兼容模式定没定、指标向量和词表是不是同一次更新、行级安全是不是从第一天就开着。这三件定了,后面的调优才有意义;定不下来,再好的语义层设计也会在某一环断掉。

下一篇聊向量索引的分片与内存规划------数据量过了千万级,HNSW 的内存账怎么算、分片怎么切,这部分我踩过的坑最多。

有问题评论区见。

相关推荐
jay神15 分钟前
基于 YOLOv8 的手势识别系统(Web 版)
前端·人工智能·python·深度学习·yolo·计算机视觉·计算机毕业设计
奶油话梅糖23 分钟前
用 iPhone 扫一间房,AI 帮你在浏览器里 1:1 重建它:Wes Bos 的 scan-to-threejs 工作流
人工智能·ios·iphone
IT规划师33 分钟前
Agent 总返工? Google 工程师把工程实践写成了技能库
ai编程·代码规范
锋行天下1 小时前
LangGraph HITL 实战:从 interrupt 到 useStream 的最小闭环
人工智能
kyriewen1 小时前
AI 能把页面写出来,为什么改个需求就开始崩?
前端·javascript·人工智能
张彦峰ZYF2 小时前
浏览器调试接口的官方化:从 chrome-devtools-mcp 看 Agent 原生工具层的形成
人工智能·chrome·agent·mcp·apache-2.0许可·devtools-mcp·调试闭环
Dawson Zhu2 小时前
Queen Planner深度解析:Agent竞争从模型时代进入系统时代
人工智能·语言模型·架构·aigc·agi
甲维斯2 小时前
GPT6.2Astra也很强啊,国模...!
人工智能
承渊政道3 小时前
Fooocus本地AI绘图教程:Windows部署、低门槛生图与远程访问
人工智能·windows·cpolar·fooocus·生图