AI技术|

LLM 写的代码能跑,但慢了 2 万倍:一个 SQLite 重写项目的翻车实录

有人用 LLM 把 SQLite 重写成 Rust。代码能编译,测试能过,但最简单的查询慢了 2 万倍。问题不是语法错误,而是 LLM 生成了看起来对的代码,而不是真的对的代码。

有人用 LLM 把 SQLite 用 Rust 重写了一遍。代码能编译,测试能过,README 写得很专业。

然后跑了个最简单的性能测试:查询 100 行数据。

SQLite 用了 0.09 毫秒。LLM 生成的版本用了 1815 毫秒。

慢了 20,171 倍。

这不是打错小数点。这是一个能编译、能通过测试、看起来完全正常的数据库,在最基础的操作上直接崩了。

代码看起来没问题

这个 Rust 重写项目有 57.6 万行代码,625 个文件。有 parser、planner、B-tree、WAL,模块名字都对,架构看起来也对。

但有两个致命 bug:

Bug 1:查询规划器不认主键

SQLite 里,当你声明 id INTEGER PRIMARY KEY 时,查询 WHERE id = 5 会直接走 B-tree 搜索,时间复杂度 O(log n)。

LLM 生成的版本?它只认三个魔法字符串:rowid_rowid_oid。你用 id 当主键?对不起,全表扫描,O(n²)。

100 行数据,100 次查询 = 10,000 次行比较。SQLite 只需要 700 次 B-tree 操作。

Bug 2:每次插入都 fsync

每个 INSERT 语句外面都包了一层 autocommit,每次都调 fsync()。100 条插入 = 100 次磁盘同步。

SQLite 用的是 fdatasync(),只同步数据不同步元数据,快 1.6-2.7 倍。而且 SQLite 会复用编译好的语句,LLM 版本每次都重新编译。

结果:批量插入慢了 78 倍。

更离谱的是那些安全的选择

除了这两个明显的 bug,还有一堆看起来合理的设计决策:

  • 每次缓存命中都 .clone() 整个 AST,然后重新编译成字节码
  • 每次读取都分配 4KB 的 Vec,即使是缓存命中
  • 每次 autocommit 后都重新加载整个数据库 schema
  • 每条语句都创建新的事务、程序、引擎对象

每个决策单独看都有道理:我们 clone 是因为 Rust 的所有权很复杂、我们用 sync_all() 因为这是安全默认值。

但加在一起?慢了 2900 倍。

这不是个例

同一个开发者还做了另一个项目:清理 Rust 编译产物的守护进程。

问题:Rust 的 target/ 目录太大,占满磁盘。

解决方案:8.2 万行 Rust 代码,192 个依赖,带终端 dashboard、贝叶斯评分引擎、EWMA 预测器、PID 控制器...

实际需要的解决方案:

BASH
*/5 * * * * find ~/*/target -type d -name "incremental" -mtime +7 -exec rm -rf {} +

一行 cron,0 依赖。或者直接用 Rust 官方工具 cargo-sweep

LLM 的问题不是语法错误

LLM 生成的代码能编译,能通过测试,README 写得很专业。问题是:

它生成的是你描述的东西,不是你需要的东西。

你说实现一个查询规划器,它就给你一个查询规划器——只不过每个查询都走全表扫描。

你说智能管理磁盘空间,它就给你 8.2 万行的智能系统——只不过问题本来只需要一行 cron。

代码在语法和语义上都是正确的。它就是不解决问题。

谁最危险?

反驳的声音会说:技术好的人能发现这些 bug。

对,这正是问题所在。

LLM 对最不具备验证能力的人最危险。如果你能在查询规划器里发现 is_ipk 检查缺失,LLM 确实能帮你提速。如果你发现不了,你根本不知道代码是错的。

它能编译,测试能过,LLM 还会告诉你看起来很棒。

SQLite 为什么快?

不是因为它用 C 写的。是因为 26 年的性能分析找到了每一个瓶颈。

  • 零拷贝页缓存:直接返回内存指针,不复制
  • 预编译语句复用:编译一次,执行多次
  • Schema cookie 检查:读一个整数,不重新解析整个 schema
  • fdatasync 而不是 fsync:只同步数据
  • is_ipk 检查:一行代码,让主键查询走 B-tree

这些细节不在文档里。它们在 26 年的 commit 历史里,因为真实用户遇到了真实的性能问题。

LLM 训练在文档和 Stack Overflow 上。它不知道这些细节。

你的代码是你的吗?

如果你用 LLM 写代码(2026 年大部分人都在用),问题不是代码能不能编译。

问题是:你能不能自己找到这个 bug?

提示找到所有 bug 并修复不会有用。这不是语法错误,是语义错误:错误的算法,错误的系统调用。

如果你提示生成了代码,但解释不了为什么它选择全表扫描而不是 B-tree 搜索,那这代码不是你的。

代码只有在你理解到能把它搞坏的程度时,才真正属于你。

结论

LLM 很有用。当使用者知道正确长什么样时,它能大幅提速。

但如果你不知道,你就不是在编程——你只是在生成 token,然后祈祷。

光靠感觉不够。定义什么是正确的,然后测量。

准备好了吗?

免费注册,立即体验全部功能