让AI小助手用自然语言查数据库听着很爽用户问上个月华东区销售额多少它直接给你拉数。但只要你让模型去碰SQL就得先把安全这关守住不然出事很快。我搭这玩意儿的时候踩了几个坑记下来。最大的威胁是什么模型生成SQL有两个风险。一个是它可能生成 DROP TABLE、DELETE、UPDATE 这种破坏性语句——你以为它只会查结果用户一句把测试数据清掉它真给你写了删表。另一个是经典SQL注入用户在自然语言里夹带 ‘; DROP TABLE users; – 这种东西要是你直接把模型输出拼进执行就完蛋。我搭这个智能体用的是一个拖拽配节点的低代码平台数据库查询那步是它的一个工具节点。但工具节点本身不会替你做安全得自己加防护层。第一道数据库账号只给只读权限这条最重要也最省心。我专门建了个数据库账号只授 SELECT别的权限一概不给。CREATE USER ‘agent_ro’’%’ IDENTIFIED BY ‘你的强密码’;GRANT SELECT ON sales_db.* TO ‘agent_ro’‘%’;– 不给 INSERT/UPDATE/DELETE/DROP/ALTERFLUSH PRIVILEGES;智能体连库就用这个账号。这样哪怕模型脑子一抽生成了删表语句数据库层面直接拒了权限不够执行不了。这是底线比任何提示词防护都靠谱。我个人觉得这一条做好了安全这事就稳了一大半。第二道别让模型直接写完整SQL我没让模型自由生成整条SQL那太放飞了。改成让模型只输出查询意图的结构化参数比如查哪张表、哪些字段、过滤条件是什么、时间范围多少输出成JSON。然后我用一段固定的代码拿这些参数去拼参数化查询。模型只输出这种结构不碰原始SQL{“table”: “sales”,“region”: “华东”,“start”: “2026-05-01”,“end”: “2026-05-31”}拿到之后用参数化方式执行cursor.execute(“SELECT SUM(amount) FROM sales WHERE region%s AND dt BETWEEN %s AND %s”,(params[“region”], params[“start”], params[“end”]))注意是 %s 占位符加参数元组不是字符串拼接。这是防注入的标准做法用户夹带的恶意内容会被当成普通字符串值不会被当SQL执行。这套的代价是灵活性差了点。模型不能想查啥查啥只能查我预先支持的几种模式。复杂的多表JOIN它干不了。但对大多数业务问答够用安全换灵活我认了。第三道表名字段名白名单校验模型输出的 table 字段我不信万一它编了个表名呢。所以拿到参数后先过一道白名单ALLOWED_TABLES {“sales”, “orders”, “products”}if params[“table”] not in ALLOWED_TABLES:raise ValueError(“不允许的表”)字段名同理。表名字段名这种东西没法参数化占位符只能填值不能填标识符只能靠白名单挡。这点容易漏我一开始就没校验表名被同事用一句精心构造的话术诱导模型查了张本不该暴露的表吓出一身汗。一个小跑题顺便提一句查询结果也别一股脑全返给模型。我有次没加 LIMIT模型拉回来三万多行token直接爆了还慢。后来统一加 LIMIT 100超了就提示用户缩小范围。三道防线叠起来这个查库智能体跑了俩月没出过事。模型那层我接的讯飞星辰 MaaS现成大模型API不用自建算力。