OpenClaw技能安全执行MySQL增删改查:让大模型只懂抽参,执行器守护数据库
简介针对OpenClaw官方技能库中暂无现成MySQL-CRUD技能的现状这份资源面向需要自行扩展数据库操作能力的开发者演示了基于Python编程语言与常用MySQL驱动如pymysql封装数据库技能的实现路径。内容围绕增删改查四类核心操作展开覆盖数据库连接配置、参数化查询防注入、异常捕获与事务处理等关键环节并给出技能输入输出格式的封装示例方便直接嵌入OpenClaw框架调用。压缩包共3个文件包含Python脚本、Markdown说明文档和HTML演示页面整体仅3KB轻量但结构完整适合快速阅读后二次开发。已有165人学习下载可帮助开发者避开常见的配置与调试弯路快速掌握OpenClaw自定义数据操作技能的设计思路。1. 让 OpenClaw 技能直接操作 MySQL一次把「能聊」变成「能干」OpenClaw 接入 MySQL 做增删改查第一反应通常是让模型直接写 SQL 然后执行——我劝你千万别这么干。模型写的 SQL 没经过参数校验、没有 LIMIT、甚至可能漏掉 DELETE 的 WHERE 条件生产库经不起一次这样的翻车。OpenClaw 的技能Skill机制恰好把这个口子堵住了大模型只负责把用户的自然语言转成结构化参数真正执行增删改查的是一个你写好的 Python 执行器。这篇文章把整套技能从文件结构、执行器代码到避坑清单完整拆解适合已经部署了 OpenClaw、想让它帮你查库、改数据、干实际活的工程师照着复现。2. 先拆 OpenClaw 的 Skill 机制技能文件、参数触发与最小执行器2.1 Skill 在 OpenClaw 里是什么从提示词模板到可执行工具的边界在使用 OpenClaw 的过程中你会发现它默认能聊、能总结、能调用一些内置工具但要让它做一件具体的业务操作比如「查一下订单表里昨天有多少条记录」就必须把这件事封装成一个技能。技能的本质是两部分的组合一份描述文件告诉大模型这个工具在什么场景下用、需要哪些参数、参数长什么样一个执行脚本真正连数据库、跑 SQL、返回结果。关键边界在于大模型永远不直接执行代码。它只负责从用户的话里抽参数比如用户说「把 id 为 5 的用户状态改成禁用」模型要抽出来的是tableusers、set_clausestatusdisabled、where_clauseid5然后把这三个参数交给执行器。这样即使有人通过提示注入诱导模型写出DROP TABLE这种语句执行器层面也可以直接拒绝不会真跑到数据库里去。2.2 最小技能包一个目录、三个文件OpenClaw 的技能通常就是一个独立目录放进技能目录后由框架动态加载。我一般会这样组织mysql_crud/ ├── SKILL.md # 技能描述 参数 schema ├── handler.py # 增删改查执行器 └── requirements.txt # pymysql / sqlalchemy 等依赖SKILL.md是整个技能的「说明书」大模型靠它来判断什么时候使用这个技能、参数怎么填。handler.py是真正干活的脚本它收到模型抽取的参数后完成数据库连接、SQL 拼接、执行、返回结构化结果。requirements.txt声明运行时依赖部署到新环境时pip install -r requirements.txt一条命令搞定。这种最小结构的好处是把「模型的判断」和「代码的执行」彻底分开各自演进互不干扰。模型侧的 prompt 调优只改 SKILL.md数据库侧的连接策略只改 handler.py两者不需要同时改。2.3 参数定义与触发逻辑让模型知道什么时候该出手SKILL.md里的参数 schema 决定了模型能从自然语言里抽出什么。下面是一个可用的最小描述文件name: mysql_crud description: 当用户需要查询、插入、更新或删除 MySQL 数据库中的记录时使用此技能。 parameters: type: object properties: query_type: type: string enum: [SELECT, INSERT, UPDATE, DELETE] description: 要执行的操作类型 table_name: type: string description: 目标表名 data: type: object description: INSERT 时插入的字段键值对或 UPDATE 时待更新的字段键值对 where_clause: type: object description: 查询或更新条件例如 {id: 5} limit: type: integer description: SELECT 返回的最大行数默认 50 required: [query_type, table_name]这段配置的逻辑是模型先判断用户意图属于增删改查中的哪一种填入query_type再从用户话里提取表名和条件。limit是可选的但我会在描述里明确要求模型对查询类操作带上这个参数防止一次查出几十万行把上下文撑爆。触发条件写在description里只有当用户请求涉及数据库操作时模型才会调用这个技能普通闲聊不会触发。参数抽好了接下来就看执行器怎么接住这些参数并安全地跑起来这是下一章的重点。3. 封装 MySQL 增删改查连接方式、执行器与技能注册一条龙3.1 先选连接方式直连、连接池还是走网关让执行器连 MySQL常见的有三种方式用 PyMySQL 直连、用 SQLAlchemy 管理连接池、或者通过 HTTP 网关间接访问。三者的区别直接决定了技能的可靠性和并发能力我做了个对比连接方式优点缺点适用场景PyMySQL 直连依赖少、代码直观每次新建连接开销大、并发高会打满数据库连接数技能个人使用、低频查询SQLAlchemy 连接池连接复用、自动回收、方言兼容好多一层抽象、排查问题要了解池化机制OpenClaw 被多人/多会话调用HTTP 网关数据库不直接暴露、权限收敛在网关要额外维护一个服务多套技能共用一套数据库我一般推荐 SQLAlchemy。原因很实际OpenClaw 跑起来之后可能会有多个会话同时触发同一个技能直连模式在并发稍高时就会出现Too many connections报错而连接池可以让连接数稳定在一个可控范围内。如果你的 OpenClaw 部署在 Docker 容器里MySQL 跑在宿主机上记得连接串里的 host 要写宿主机在容器网络中的可达地址反过来 MySQL 跑在容器里也要确认端口映射到了宿主机的哪个端口。3.2 写一个带超时、重试和关闭动作的执行器执行器的核心职责是接参数、拼 SQL、执行、返回结构化结果。下面这个handler.py是我在实际环境里调过的一版包含连接池、超时、重试和显式关闭连接import os import json import time from sqlalchemy import create_engine, text from sqlalchemy.pool import QueuePool DB_USER os.getenv(MYSQL_USER, claw_bot) DB_PASSWORD os.getenv(MYSQL_PASSWORD, ) DB_HOST os.getenv(MYSQL_HOST, 127.0.0.1) DB_PORT os.getenv(MYSQL_PORT, 3306) DB_NAME os.getenv(MYSQL_DB, app_db) engine create_engine( fmysqlpymysql://{DB_USER}:{DB_PASSWORD}{DB_HOST}:{DB_PORT}/{DB_NAME}?charsetutf8mb4, poolclassQueuePool, pool_size5, max_overflow3, pool_recycle3600, pool_pre_pingTrue, connect_args{connect_timeout: 5}, ) def execute(params: dict) - dict: query_type params.get(query_type, ).upper() table params.get(table_name, ) if query_type not in (SELECT, INSERT, UPDATE, DELETE): return {ok: False, error: funsupported query_type: {query_type}} data params.get(data) or {} where params.get(where_clause) or {} limit int(params.get(limit) or 50) # 危险操作拦截UPDATE/DELETE 必须有 where 条件 if query_type in (UPDATE, DELETE) and not where: return {ok: False, error: UPDATE/DELETE must have where_clause} sql, bind build_sql(query_type, table, data, where, limit) for attempt in range(2): try: with engine.connect() as conn: result conn.execute(text(sql), bind) if query_type SELECT: rows [dict(r) for r in result.fetchmany(limit)] return {ok: True, rows: rows, count: len(rows)} conn.commit() return {ok: True, affected_rows: result.rowcount} except Exception as e: if attempt 1: return {ok: False, error: str(e)} time.sleep(0.3)逻辑说明执行器首先对query_type做枚举校验避免模型传进来一个奇怪的字符串然后对 UPDATE 和 DELETE 做强制条件校验——没有where_clause直接拒绝这一步是整条安全链的底线。build_sql函数负责根据操作类型拼接带绑定参数的 SQL绑定参数用:key占位而不是字符串拼接既防注入又能让 MySQL 走预编译缓存。重试逻辑只做一次间隔 0.3 秒主要应对网络抖动造成的瞬时报错连接池的pool_pre_pingTrue会在取连接前先测活避免拿到失效连接。参数说明pool_size5表示连接池保持 5 个连接max_overflow3允许高峰期再多创建 3 个这样最多 8 个连接对绝大多数小团队够用。pool_recycle3600强制连接每小时的回收规避 MySQL 的wait_timeout把空闲连接断掉的问题。connect_timeout5让技能在数据库不可达时快速失败返回错误信息而不是让大模型干等。3.3 四个核心操作如何映射到参数每个操作的返回语义增删改查不是简单地把参数拼进 SQL而是每一类操作都有明确的语义约定模型才能把结果转述给用户SELECT返回rows列表每行是字段名到值的字典。返回行数受limit约束默认 50 行超出部分在描述里明确告知模型「结果可能不完整」。INSERT返回affected_rows值为 1 表示插入成功需要拿到自增 ID 的话可以在执行器里加result.lastrowid一并返回。UPDATE返回affected_rows这个数字对用户很有意义——更新了 0 行说明条件可能写错了。DELETE返回affected_rows同一张表的删除操作我会建议模型在执行前先跑一个 SELECT 确认影响范围。这四种操作的共同原则是执行器只返回「结构化的事实」不返回夸大的成功描述。比如 UPDATE 影响了 0 行执行器就如实返回 0模型会把「没有匹配到记录」告诉用户而不是含糊地回一句「已更新」。3.4 把技能注册到 OpenClaw配置目录和触发测试技能文件写好后放进 OpenClaw 配置指定的技能目录然后在配置文件里声明skills: enabled: true dir: ./skills把mysql_crud/整个目录放进./skills/下重新加载配置或重启 OpenClaw 进程即可。Windows 上部署的话路径用绝对路径更省心比如dir: D:/openclaw/skills注意反斜杠要转义或直接用正斜杠。验证是否加载成功有个笨但有效的办法在对话里直接问一句「帮我查一下 users 表有多少条记录」然后看执行器的日志有没有对应输出。如果返回的是「我没有权限执行」或者模型答非所问先检查技能目录名是否和 SKILL.md 里的name一致再确认 OpenClaw 进程有权限读 handler.py。这套流程跑通之后增删改查就真的交给 OpenClaw 了但下一步要面对的是各种意想不到的坑。4. 避坑OpenClaw 操作 MySQL 的 5 个常见翻车点4.1 模型生成 SQL 跑偏把 DROP 表当查询做现象用户问「这个表没用了删掉吧」模型真的调用了技能执行器如果没拦截表就没了。原因大模型对「删除」的理解是语义级的它区分不清「删除某条记录」和「删除整张表」在 SQL 里的天壤之别。解决在执行器里加一条硬校验——table_name必须来自一个白名单执行前检查SHOW TABLES的结果同时用正则禁止 SQL 中出现DROP、ALTER、TRUNCATE、CREATE这些非白名单关键词。就算模型抽参数抽错了执行器也不会放行。4.2 连不上 Docker 容器里的 MySQL现象技能执行时报Cant connect to MySQL server on 127.0.0.1但 MySQL 容器明明在跑。原因MySQL 跑在 Docker 容器里时3306 端口没有映射到宿主机或者映射到了别的宿主端口。还有一个隐蔽坑MySQL 用户授权只给了容器网段的 IP宿主机连接时被拒绝。解决启动容器时加-p 3306:3306映射进入容器用SELECT user, host FROM mysql.user确认claw_bot账号的 host 是%或宿主机可达的 IP 段然后GRANT ... TO claw_bot%并FLUSH PRIVILEGES。4.3 Windows 下 MySQL 服务没起来导致技能假死现象OpenClaw 在 Windows 上部署好后技能调用超时日志里只有连接超时没有具体错误。原因Windows 上 MySQL 安装后服务默认是手动启动机器重启后服务没跟着起来还有一种情况是之前net start mysql启动的是 5.x 服务但连接串上写的端口还是 3306实际新版占了别的端口。解决先在命令行确认服务和端口net start | findstr mysql和mysql -h 127.0.0.1 -P 3306 -u claw_bot -p手动连通后再让技能重试。连接串里 host 写127.0.0.1而不是localhost后者在部分 Windows 环境会走命名管道导致 PyMySQL 连不上。4.4 中文乱码和 emoji 写入报错现象SELECT 查出来的中文是???INSERT 带 emoji 直接报Incorrect string value。原因连接串没指定字符集MySQL 会话默认用了 latin1或者表本身是utf8而 utf8 在 MySQL 里最多存 3 字节存不下 emoji 这类 4 字节字符。解决连接串统一加?charsetutf8mb4建表时用DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci。修改已有表用ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4只改连接串不改表结构写入依然会报错。4.5 长事务和行锁等待把技能卡死现象技能第一次调用 UPDATE 很慢第二次调用直接报Lock wait timeout exceeded。原因第一次的 UPDATE 条件没走索引扫了全表把大量行锁住了事务没提交锁一直不释放后续会话全部堵住。解决在技能描述里强制要求模型对 UPDATE/DELETE 先做 SELECT 预览影响行数超过 50 就要求用户确认后再执行MySQL 侧设置innodb_lock_wait_timeout10让它快速失败而不是无限等锁。5. 从能跑变好用最小权限、事务兜底与操作审计5.1 给技能开一个专用账号别用 rootOpenClaw 技能连 MySQL最忌讳用 root 账号一旦模型抽参抽错或者提示注入生效代价是整个库。我一般会给技能单独建一个账号权限精确到库和操作类型CREATE USER claw_botlocalhost IDENTIFIED BY 替换成强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO claw_botlocalhost; FLUSH PRIVILEGES;逻辑说明app_db.*把权限限制在单个业务库内技能连不到其他库只授权 SELECT、INSERT、UPDATE、DELETE 四个 DML 操作没有 DDL 权限即使执行器被绕过也做不了DROP TABLE。localhost限定了来源 IP如果 OpenClaw 在另一台机器上改成对应 IP 或网段。参数说明不要把GRANT ALL PRIVILEGES ON *.*给这个账号这类授权是给 DBA 用的不是给智能体用的。改完权限后记得FLUSH PRIVILEGES否则部分环境不会立即生效。5.2 事务兜底技能调用要么全成功要么全回滚执行器里的事务策略是每次调用开启一个短事务执行成功立即提交任何一条异常都回滚并返回错误信息。engine.connect()默认开启事务所以commit()和rollback()的位置很重要for attempt in range(2): try: with engine.begin() as conn: result conn.execute(text(sql), bind) if query_type SELECT: rows [dict(r) for r in result.fetchmany(limit)] return {ok: True, rows: rows, count: len(rows)} return {ok: True, affected_rows: result.rowcount} except Exception as e: if attempt 1: return {ok: False, error: str(e)} time.sleep(0.3)逻辑说明engine.begin()是 SQLAlchemy 推荐的事务写法进入with块自动开启事务正常走完自动提交抛异常自动回滚不需要手动commit()/rollback()少一个「忘了回滚」的隐患。重试只针对连接层的瞬时报错事务一旦开始执行并报了业务错误不会重试避免把重复数据写进去。5.3 结果人话化让模型拿到能看懂的事实执行器返回的裸结果对用户不友好。SELECT 返回 50 行原始字典模型转述时会非常吃力所以执行器要做一层格式化def format_result(query_type: str, result: dict) - dict: if query_type ! SELECT: return result rows result.get(rows, []) if len(rows) 10: return { ok: True, message: f共查到 {result[count]} 行仅展示前 10 行, preview: rows[:10], } return {ok: True, rows: rows, count: len(rows)}逻辑说明超过 10 行时不再把全部数据塞进模型上下文而是只给前 10 行预览和总数让模型告诉用户「数据太多已展示前 10 条」。这样既避免上下文被撑爆也让模型的行为更可靠——用户如果想看更多会明确要求增加 limit而不是让模型在截断的数据上瞎猜结论。5.4 审计日志每次操作都要留痕技能放给团队用之后审计日志就是后悔药。执行器每执行完一个操作往本地日志文件追加一条 JSONdef audit_log(skill_name, params, sql, result, elapsed_ms): entry { time: time.strftime(%Y-%m-%d %H:%M:%S), skill: skill_name, query_type: params.get(query_type), table: params.get(table_name), sql: sql, ok: result.get(ok), elapsed_ms: elapsed_ms, } with open(openclaw_audit.log, a, encodingutf-8) as f: f.write(json.dumps(entry, ensure_asciiFalse) \n)逻辑说明日志记录的是执行层的客观事实不记录完整参数里的敏感数据这样排查问题时能回答「这个技能在什么时间做了什么操作」又不会把大批量导出的数据明文落盘。每次操作都记录行数而不是完整数据内容是日志安全和可观测性之间平衡的做法。6. 先跑通 SELECT 再放开写操作渐进开放与验证清单技能上线最稳妥的路径是分阶段放开。第一步只开放 SELECT让 OpenClaw 先成为一个能查库的问答工具跑几天确认模型抽参稳定、没有误触发之后再开放 INSERTUPDATE 和 DELETE 放到最后且必须保留执行器里的无条件拦截。每个阶段用固定的验证问题集去压一遍查不存在的表、查空表、插入重复主键、更新不存在的记录、删除带条件的记录每一条都要看执行器返回的错误是不是能被模型转述成用户能懂的话。我自己的血泪教训是当初觉得 SELECT 跑得很稳直接把 UPDATE 一起放开了结果一条「把这个用户的余额清零」的指令因为where_clause被模型抽成了{id: unknown}加上测试库里恰好有三条脏数据带这个 id一次改了三条。从那以后执行器里加了一条硬规矩UPDATE 和 DELETE 的where_clause必须包含主键字段否则拒绝执行。不是所有业务场景都适用这条但它在绝大多数情况下能拦住最危险的那类误操作。验证清单可以按这个顺序过一遍第一危险指令测试——故意问「把 users 表清空」「把所有商品价格改成 0」看技能怎么反应第二并发测试——同时开三个会话查询同一张表看连接池是否稳定第三可用性测试——停掉 MySQL 再启动看技能能否在连接池重建后自动恢复。这三轮过完基本可以放心交给业务方用了。如果你只记住一个结论那就是OpenClaw 的技能机制本身不负责安全安全全写在你的执行器里。参数白名单、无条件拦截、影响行数限制、审计日志缺一条都可能在未来某个时刻翻车。这套方案我已经跑了几个季度前期多花半天把执行器做扎实后面维护成本会低到你几乎感受不到它的存在。希望帮到你。本文还有配套的精品资源点击获取