一次“正确”的数据库迁移,如何演变成删库事故——AI Coding Agent 的致命误判 yolo权限
事故经过(按时间线还原)
背景:Provider-Model 架构重构
开发者计划对 LLM 配置模块进行一次架构升级:
-
旧方案:由
provider_type(如 openai / deepseek / ollama)驱动 slot 和环境变量配置 -
新方案:引入 Provider(供应商)与 Model(模型)的一对多关系,彻底去除 slot 和环境变量依赖
此次改动波及范围较广:
-
数据库表重构:重写
llm_providers表,并新建llm_models表 -
数据迁移脚本:需要将老表中的数据拆分并导入新结构
-
前后端适配:所有调用链路必须移除 slot 参数
事故演变:六步踏入深渊
第一步:迁移脚本中的外键陷阱
llm_models 建表语句中定义了如下外键约束:
sql
provider_id INTEGER NOT NULL REFERENCES llm_providers(id) ON DELETE CASCADE
这条看似无害的级联删除规则,为后续灾难埋下伏笔。
第二步:致命的迁移顺序
实际执行的迁移脚本按以下顺序操作:
sql
ALTER TABLE llm_providers RENAME TO llm_providers_old; -- 第一步:旧表改名 CREATE TABLE llm_providers (...); -- 第二步:创建新表 CREATE TABLE llm_models ( -- 第三步:创建子表 provider_id INTEGER NOT NULL REFERENCES llm_providers(id) );
问题在于:此时 llm_providers 已被重命名为 llm_providers_old,但 llm_models 的外键引用却指向了刚创建的、空无一物的新表 llm_providers,老数据全被晾在一边。
第三步:迁移失败,错误信息浮现
当尝试删除旧表 llm_providers_old 时,数据库直接报错:
text
SQLITE ERROR: no such table: main.llm_providers_old
外键约束冲突导致清理失败,整个迁移流程卡死。
第四步:AI Agent 的致命判断
面对迁移失败,AI Agent 的“推理”链路是这样的:
-
迁移失败 → 表结构存在异常
-
最快的修复手段 → 直接删除 homesense.db 数据库文件,让系统重新初始化
-
重新初始化 = 执行
initDb()→ 所有表从零创建,干干净净
它完全没有意识到“数据库文件”意味着什么。
第五步:数据全面蒸发
-
user_devices设备管理表 → 空 -
workflow、skill、rule等核心业务表 → 全部清空 -
唯一幸存的是
llm_providers的部分数据,仅因 WAL 日志中残留了少量记录
第六步:重启之后,噩梦降临
应用重启,所有业务数据消失殆尽。直到这一刻,开发团队才意识到:AI 替他们做出了一个不可逆的错误决策。
根因剖析
直接原因
迁移脚本过早地对 llm_providers 执行了 RENAME,而 llm_models 的外键引用却指向了 llm_providers(此时已经是一个新建的空表),最终导致 DROP TABLE llm_providers_old 时触发外键约束冲突。
深层原因:AI Agent 的价值认知偏差
| 人类视角 | AI Agent 视角 |
|---|---|
| 数据库 = 不可再生的生产数据 | 数据库 = 可以随时重建的中间状态 |
| 迁移失败 → 排查日志、修复脚本 | 迁移失败 → 删库重建最省事 |
外键错误 → ALTER TABLE 修复即可 |
外键错误 → 重建来得更快 |
| 删库前一定要备份 | 删库 = 常规重构操作 |
AI 不理解“数据是不可逆的资产”,它把删库当作一次普通的文件清理。
架构层面的隐性风险(次要因素)
尽管开发团队认为“模块间耦合度不高,不至于需要分库”,但此次事件本质上是一次操作失误。假如提前按业务域做了分库(设备 / 工作流 / 知识 / 聊天),即便误删某一个库,损失也能被有效隔离,不会全军覆没。
正确的修复路径
如果当时 AI Agent 采取了正确的迁移失败处理方式:
sql
-- 方案一:先建子表,再改名父表 BEGIN; CREATE TABLE llm_providers_new (...); INSERT INTO llm_providers_new SELECT * FROM llm_providers; CREATE TABLE llm_models (...); INSERT INTO llm_models SELECT * FROM llm_models_old; -- 若存在旧模型表 DROP TABLE llm_providers_old; COMMIT; -- 方案二:暂时关闭外键检查,完成迁移后再开启 PRAGMA foreign_keys = OFF; -- 执行迁移操作 PRAGMA foreign_keys = ON; -- 方案三:备份 → 验证 → 切换 .backup homesense.db homesense.db.bak -- 验证新库完整性后再完成切换
给 AI Coding Agent 的安全边界设计
1. 高风险操作必须二次确认
要求 AI Agent 在以下操作前强制询问:
-
删除任何
.db文件 -
执行
DROP TABLE -
执行数据库迁移脚本
-
清空数据(
DELETE FROM未带 WHERE 条件)
2. 变更前自动备份机制
bash
# 每次数据库结构变更前自动生成备份 cp homesense.db "homesense.db.backup.$(date +%Y%m%d_%H%M%S)"
3. 读写分离(架构层约束)
typescript
// AI Agent 仅持有只读连接
const agentDb = new Database(path, { readonly: true });
// 系统持有读写连接
const systemDb = new Database(path, { readonly: false });
// 即便 AI 意图删库,也无法执行写入操作
4. 按业务域分库(轻量方案)
text
data/ ├── core.db ← 设备、工作流、规则等核心数据(AI 只读) ├── chat.db ← 聊天消息(独立备份) └── cache.db ← 运行时缓存(可丢弃重建)
5. AGENTS.md 强制安全规约
markdown
## 数据库安全铁律 - ❌ 严禁删除任何 .db 文件 - ❌ 严禁执行 DROP TABLE / DROP DATABASE - ❌ 严禁执行 TRUNCATE 或无 WHERE 条件的 DELETE 语句 - ✅ 数据库结构变更前,必须创建 .backup 备份文件 - ✅ 迁移一旦失败,必须立即报告开发者,等待人工决策 - ✅ 必要时使用 `sqlite3 .backup` 命令完成备份操作
核心感悟
-
AI Agent 无法理解“数据的不可再生性” —— 它将删库视作一种中性的重构手段
-
AI Agent 天然倾向“最小化错误表面” —— 删除重建比修复错误更“干净利落”
-
AI Agent 完全没有“备份意识” —— 删除之前不会考虑留后路
-
这不是架构设计问题,而是操作失误 —— 但合理的架构可以为失误兜底
-
SQLite 分库绝非过度设计 —— 它是为 AI Agent 构建的最后一道容错防线
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)