AI 操作数据库的事故合集
真实发生过的"Agent 把库搞坏了"故事,每条都附"用 MariaDB 特性如何预防"
看这一页,是为了让你的 Agent 不会变成下一条故事的主角。
故事来自社区公开复盘(Hacker News / X / 公司事故报告),事实部分匿名化处理。每条都给出"如果当时按这一站的建议做了,会怎样"。
事故 1:Cursor 把 production 表 TRUNCATE 了
起因:开发者让 Cursor "clean up the test data in users",Cursor 生成 TRUNCATE TABLE users 并在自动执行模式下跑了——连的是生产库。
后果:50 万用户数据归零,靠备份恢复,停服 4 小时。
为什么会发生:
- 没有 SQL 注入护栏
- AI 工具的"db connection"只配了一个 URL,没有"危险动作必须人工确认"
- 用的是
root账号
如果按 MariaDB 推荐姿势:
- AI 走 MCP server,连接走只读账号
- 任何
TRUNCATE/DROP/ALTER在 guard 层就被拦掉 - 即使 guard 失效,账号没有这些权限也跑不动
-- AI 专用账号
CREATE USER 'ai_agent'@'%' IDENTIFIED BY 'xxx';
GRANT SELECT, SHOW VIEW ON app.* TO 'ai_agent'@'%';
-- 没有 DROP / TRUNCATE / DELETE 权限事故 2:Claude 跑了一个没 WHERE 的 UPDATE
起因:让 Claude "把所有 trial 用户改成 paid",它生成:
UPDATE users SET plan = 'paid';漏了 WHERE plan = 'trial'。Claude 自己其实加了 review 步骤,但开发者按了 Y。
后果:免费用户全部"升级",扣不到钱,影响营收数据 + 客服爆炸。
预防:
-- guard 层强制带 WHERE
function guard(sql) {
const lower = sql.toLowerCase();
if (/^\s*(update|delete)/.test(lower) && !/\bwhere\b/.test(lower)) {
return { ok: false, reason: 'write without WHERE rejected' };
}
}也可以在客户端规则里写:
Never generate UPDATE/DELETE without WHERE.
If you must mutate a full table, add `-- intentional: full table` comment AND ask me to confirm.事故 3:Agent 写出 N+1 把数据库打挂
起因:让 Agent 写"导出用户订单 CSV",它写出:
for user_id in all_users:
orders = db.query(f"SELECT * FROM orders WHERE user_id = {user_id}")100 万用户 = 100 万次 query,1 小时打挂从库。
预防:
- EXPLAIN 校验在 Text-to-SQL pipeline 里——能拦掉很多但拦不掉"循环里调 query"
- 代码级 rules:让 AI 优先用
WHERE user_id IN (...)或 JOIN,写出循环时强制 review - 慢查询告警:
long_query_time = 0.5,超过就告警;ChatOps 接通后告警直接 ping 写代码的人
-- 在配置层
SET GLOBAL long_query_time = 0.5;
SET GLOBAL slow_query_log = 1;事故 4:MCP server 没限流,Agent 暴力扫描泄露数据
起因:自建 MCP server 没限流,Agent 在 prompt 注入诱导下,连续跑了 5000 次 SELECT * FROM users LIMIT 100 OFFSET ?,把所有用户数据外传。
预防:
import { RateLimiterMemory } from 'rate-limiter-flexible';
const limiter = new RateLimiterMemory({
points: 60, // 每分钟 60 次
duration: 60,
blockDuration: 600, // 触发限流后封 10 分钟
});
server.tool('run_query', /* ... */, async (args) => {
try { await limiter.consume(client_id); }
catch { return errorResp('rate limited'); }
/* ... */
});外加 审计日志,异常行为可追溯。
事故 5:向量索引建错,RAG 检索全表扫描
起因:开发者用 MariaDB VECTOR 做 RAG,忘了加 VECTOR INDEX。库里 200 万 chunks,每次问答耗时 30s+,最终拖垮数据库。
预防:
-- 建表时就把索引建好
CREATE TABLE chunks (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
embed VECTOR(1536) NOT NULL,
VECTOR INDEX (embed) M=16 DISTANCE=cosine -- ← 必须有
);
-- 验证查询用了索引
EXPLAIN SELECT ... ORDER BY VEC_DISTANCE_COSINE(embed, ...) LIMIT 8;
-- type 应该不是 ALL详见 RAG 教程。
事故 6:MySQL 8 → MariaDB 11 迁移后,AI 生成的 JSON 函数报错
起因:迁移到 MariaDB 11 后,AI 仍然按 MySQL 8 的语法生成 JSON_TABLE,CI 全红。
预防:
在 rules 文件里显式标注差异:
## Differences from MySQL 8 (LLM tends to forget)
- JSON_TABLE syntax is restricted in MariaDB vs MySQL 8
- Default utf8mb4 collation differs
- caching_sha2_password does not exist
- ...同时 迁移指南 里列了所有要改的点。
事故 7:Agent 写 schema 时把 FK 全删了说"提高性能"
起因:让 Agent "优化这个慢查询",它给的方案之一是"删除外键约束",开发者照做。3 个月后发现有大量孤立数据。
预防:
## Rules
- Never suggest removing foreign keys for performance reasons
- If FK is suspected to be the bottleneck, propose `OPTIMIZE TABLE` or index changes first
- Any DDL change needs human review with rollback plan事故 8:测试库被当成生产库
起因:CI 跑测试连的 URL 配错,写到了 production。Agent 看到"测试失败因为没数据",于是生成 INSERT 一通灌测试数据。
预防:
- 生产连接的 URL 必须包含
prod字符串,并在应用启动时硬校验 - 测试环境用完全不同 host 的实例,不只是不同 DB
- 用
read_only=1flag 的从库给 AI 用
-- 启动时校验
SELECT @@hostname; -- 应用层检查包含 'staging' 或 'dev'总结:让你的 Agent 安全的 7 条最低要求
| # | 要求 | 落实手段 |
|---|---|---|
| 1 | AI 只读 | 数据库账号 + 应用层 guard 双保险 |
| 2 | 任何写操作必须人工确认 | MCP requires_approval: true 或 review step |
| 3 | 行数上限 | MCP guard 强制 LIMIT |
| 4 | 限流 | rate-limiter,每客户端每分钟 60 次 |
| 5 | 审计日志 | 独立审计库,独立账号 |
| 6 | 危险语句白名单 | guard 层 deny DROP/TRUNCATE/ALTER/GRANT |
| 7 | EXPLAIN 校验 | 生成后自动跑 EXPLAIN,全表扫描拒绝 |
按这一套做下来,绝大多数事故都能避免。
想分享你的事故?
如果你有公开过的 AI + DB 事故复盘,欢迎 PR 贡献到这一页。匿名也行,让别人少踩坑。