Bun 1.4 深度实战:当 JS 运行时内置了数据库客户端——Bun.SQL 统一 API 与 Zig→Rust AI 重写如何重写全栈心智模型
2026 年 7 月 14 日,一条消息在 Hacker News 炸开:Bun 创始人 Jarred Sumner 宣布,他靠一组并行运行的 Claude 智能体集群,仅用 11 天就把整个 Bun 从 Zig 语言移植到了 Rust,按 API 定价折算成本约 16.5 万美元。而更早的 5 月 14 日,那次改变 Bun 命运的 PR #30412「Rewrite Bun in Rust」已经合并——6755 次提交、2188 个文件变更、新增超过 100 万行代码,并且在 Linux x64 glibc 下通过了既有测试套件的 99.8%。
这不是又一篇「XX 发布新版本」的流水账。这是一次观察窗口:一个现代 JS 运行时,如何同时完成底层语言重写与全栈能力内建两件事。而 Bun 1.4 最让后端工程师眼前一亮的,是它把数据库客户端直接焊进了运行时——
Bun.SQL一个 API 同时驱动 PostgreSQL、MySQL、MariaDB 和 SQLite,且零外部依赖。
本文从工程师视角,把 Bun 1.4 的技术内核、架构演变、真实代码实战与性能调优一次讲透。读完你应该能回答三个问题:Bun 凭什么比 Node 快?Bun.SQL 到底省了多少事?以及——AI 帮我们写代码,这次到底写了什么、又留下了什么。
一、背景介绍:运行时的三国杀与 Bun 的入场
1.1 从「一个运行时」到「一个工具链」
如果你 2018 年写 Node,你的工具箱是这样的:Node 只负责跑 JS;数据库要装 pg / mysql2 / better-sqlite3;打包要装 webpack / esbuild;测试要装 jest / vitest;甚至 npm install 慢到你想砸键盘。
Deno 在 2020 年试图用「零配置 + 安全沙箱 + 内置工具」破局,但真正把「运行时即工具链」这件事做到极致的,是 2021 年底冒出来的 Bun。它的口号很狂:「Incredibly fast JavaScript runtime, bundler, test runner and package manager — all in one.」 一个二进制,把 runtime / bundler / test runner / package manager 全包了。
到 2026 年,格局已经变成三国杀:
| 运行时 | JS 引擎 | 包管理器 | 内置测试 | 内置打包 | 数据库客户端 |
|---|---|---|---|---|---|
| Node.js 26 | V8 | npm(独立) | 需装 | 需装 | 需装 |
| Deno 2.9 | V8 | 内置 | 内置 | 内置 | 需装(或 deno doc) |
| Bun 1.4 | JavaScriptCore | 内置 | 内置 | 内置 | 内置 Bun.SQL |
注意最后一行——这是 Bun 1.4 相对另外两个对手最本质的区别:数据库访问不再是「装个驱动」的事,而是「打开就用」的事。
1.2 JavaScriptCore:Bun 快的第一个秘密
Node 和 Deno 都用 Google 的 V8。Bun 选了 Apple 的 JavaScriptCore(JSC)——也就是 Safari 背后那台引擎。这个选择不是拍脑袋:
- 启动更快:JSC 的字节码缓存(Bytecode Cache)机制让冷启动明显优于 V8 的冷路径;
- 内存更省:JSC 的垃圾回收策略在短生命周期脚本(CLI、Serverless 函数)场景下内存 footprint 更小;
- Apple 生态友好:在 macOS / iOS 上天然亲和。
当然 JSC 也有代价——V8 的优化编译器(TurboFan)在长期运行的热路径上更猛。所以 Bun 不是「处处比 V8 快」,而是「在启动密集、I/O 密集的真实 Web 场景里,综合体感更快」。这也是为什么 Bun 官方基准里 HTTP 吞吐、打包速度、测试速度全面领先,但纯计算 benchmark 并不总是碾压。
1.3 那场 11 天的重写:为什么从 Zig 迁到 Rust?
Bun 一开始用 Zig 写。Zig 给了 Bun 极致的性能和极致的控制力,但也带来两个现实问题:
- 招聘难:能用 Zig 写底层运行时的人,全球一只手数得过来;
- 内存安全负担:Zig 不帮你管内存,Bun 用户上报的 bug 越来越多是内存泄漏和 use-after-free 这类「只有 Rust 最擅长的领域」。
于是 Jarred 做了一个被外界称为「史上最大 AI 重构」的决定:用 Claude 智能体集群把 Bun 从 Zig 重写成 Rust。过程大致是:
- 把代码库切成无数小任务,分给并行运行的 Claude agent;
- 每个 agent 负责把一块 Zig 模块翻译成等价的 Rust + unsafe 边界;
- 跑既有测试套件,红了的反复修,直到 99.8% 通过;
- 合并进主线(PR #30412,2026-05-14)。
为什么这件事值得开发者认真看?因为它标志着一个拐点:以前我们认为「规模太大、无法安全重构」的代码库,现在在 AI agent 的并行火力下,变成了「11 天 + 16.5 万美元」的工程问题。 这对所有维护老旧大型系统的团队,都是一记警钟。
二、核心概念:Bun.SQL 统一数据库 API
2.1 为什么「内置数据库客户端」是大事?
在 Node 世界里,你接 PostgreSQL 要 pg,接 MySQL 要 mysql2,接 SQLite 要 better-sqlite3。每个库的 API 风格不同、连接池配置不同、预处理语句写法不同。一个全栈项目往往在 package.json 里躺着三四个数据库依赖,还要操心 native binding 在 CI 上编译失败。
Bun 的解法很「苹果」:一个 Bun.SQL,四种数据库,统一心智模型。它支持 PostgreSQL、MySQL、MariaDB、SQLite,全部原生实现、零 npm 依赖。
import { sql, SQL } from "bun";
// 四种数据库,同一个 API
const postgres = new SQL("postgres://user:pass@localhost:5432/app");
const mysql = new SQL("mysql://user:pass@localhost:3306/app");
const mariadb = new SQL("mariadb://user:pass@localhost:3306/app");
const sqlite = new SQL("sqlite://./local.db");
// 查询:标签模板(tagged template),写法完全一致
const username = "alice";
const rows = await postgres`
SELECT id, name, role
FROM users
WHERE username = ${username}
`;
console.log(rows);
注意两件事:
- 零依赖:
new SQL(...)不需要npm install任何东西。Bun 编译时就把这层原生驱动打进了运行时。 - 标签模板即查询:
sql\...`不是字符串拼接,而是带类型安全的查询构造器。${}` 里的变量会被自动参数化转义,从根本上杜绝 SQL 注入。
2.2 默认客户端与环境变量
如果你不想每次 new SQL(...),Bun 还提供一个默认全局客户端 sql,它读取环境变量 BUN_DB 自动连接:
# .env / shell
export BUN_DB="postgres://user:pass@localhost:5432/app"
import { sql } from "bun";
// 直接用全局 sql,连接信息来自 BUN_DB
const recent = await sql`SELECT * FROM orders ORDER BY created_at DESC LIMIT 10`;
这在进行本地开发、跑迁移脚本、写 one-liner 时极其顺手——不需要任何样板代码。
2.3 与 Node 生态的对比
| 能力 | Node 方案 | Bun.SQL |
|---|---|---|
| 连 PostgreSQL | pg + 手写池 | 内置,开箱即用 |
| 连 MySQL | mysql2 | 内置,开箱即用 |
| 连 SQLite | better-sqlite3(需编译) | 内置,无需编译 |
| 参数化查询 | 手动 ? / $1 | 标签模板自动转义 |
| 预处理语句 | 各库 API 不同 | 统一 |
| 依赖体积 | 数个 npm 包 + native binding | 0 |
根据 Bun 创始人 Jarred Sumner 披露的基准(基于 10 万次简单查询耗时):Bun.SQL 比在 Node 里用 mysql2 快约 9 倍,比在 Node 里用 MariaDB 官方客户端快约 4 倍。这个差距主要来自:去掉了 JS 层的对象分配开销、原生预处理语句、以及 Bun 运行时层面的连接复用优化。
三、架构分析:Bun 1.4 是怎么把数据库「焊死」在运行时里的
3.1 Bun.SQL 的内部实现思路
虽然 Bun 没有把每一行源码摊开讲,但从它的行为可以反推出架构要点:
- 原生驱动,非封装:
Bun.SQL不是「在 JS 里调pg」。它直接用各数据库的有线协议(wire protocol)实现了客户端——PostgreSQL 的前端/后端消息协议、MySQL 的文本/二进制协议、SQLite 的 C API 绑定。这意味着零层级转发、零额外 GC 压力。 - 统一抽象层:所有数据库适配到同一个
SQL类。查询被编译成「参数化语句树」,再下发给具体协议的执行器。这就是你写一份sql\...`` 就能跨库运行的原因。 - 连接池内建:
new SQL(url)返回的实例内部维护连接池。频繁短查询走池化复用,避免每次connect/disconnect的握手开销。 - 预处理语句缓存:相同的 SQL 模板首次执行后会被缓存为 prepared statement,后续只传参数,跳过解析阶段。
3.2 Zig→Rust 重写对 Bun.SQL 的实际影响
你可能会问:底层从 Zig 换到 Rust,跟我用 Bun.SQL 有什么关系?关系很大:
- 内存安全红利:Rust 的所有权模型直接干掉了 Zig 时代那批「内存泄漏 / use-after-free」类 bug。数据库客户端恰恰是内存错误高发区(连接生命周期、buffer 复用),重写后稳定性肉眼可见提升。
- flaky test 收敛:Jarred 在合并说明里专门提到「顺手修了一批 flaky tests」——这正是 Rust 编译期约束带来的工程确定性。
- 可维护性:Rust 的招聘池比 Zig 大两个数量级,意味着 Bun 后续迭代更快、社区贡献更容易落地。
换句话说,那场 11 天的重写,最终买单的是每一个用 Bun.SQL 跑生产流量的开发者。
3.3 并行 AI agent 重写:工程方法论的启示
把 100 万行代码在 11 天内从一种系统语言翻到另一种,靠的不是「一个人疯狂敲键盘」,而是任务切分 + 并行 agent + 测试套件做裁判的闭环。这套方法论对普通团队也有启发:
- 让测试套件当「真理之源」:没有高覆盖、可信赖的测试,AI 重写寸步难行。Bun 敢这么干,前提是它有一套能跑红/绿的成熟测试。
- 小步、可验证、可回滚:每个 agent 负责一个语义清晰的模块,翻译完立刻跑对应测试。红了就修,修不动就隔离。
- human-in-the-loop 定边界:unsafe 边界、FFI 调用、性能热点,仍由人类拍板。AI 负责「体力翻译」,人负责「关键判断」。
这恰好印证了本站另一篇关于 Superpowers 的观点:流程大于提示词。Bun 的成功不是「prompt 写得好」,而是「工程流程设计得好」。
四、代码实战:从零搭一个 Bun.SQL 全栈服务
下面所有代码都基于 Bun 1.4,可直接 bun run 运行。我们将用 SQLite(零配置、文件即数据库)做演示,并标注换成 Postgres/MySQL 只需改连接串。
4.1 环境准备
# 安装 Bun 1.4(如已安装可跳过)
curl -fsSL https://bun.sh/install | bash
# 验证版本
bun --version # 应输出 1.4.x
# 新建项目
mkdir bun-sql-demo && cd bun-sql-demo
bun init -y
bun init 会生成 package.json、tsconfig.json(Bun 原生支持 TypeScript,不需要 tsc 预编译)和一个 index.ts。
4.2 实战一:基础 CRUD
先建一张表,再做增删改查。注意我们用标签模板,变量自动参数化:
// index.ts
import { SQL } from "bun";
// 换成 postgres 只需改这一行:
// const db = new SQL("postgres://user:pass@localhost:5432/app");
const db = new SQL("sqlite://./app.db");
// 1) 建表(DDL 同样用标签模板)
await db`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
`;
// 2) 插入(多值批量)
const inserts = await db`
INSERT INTO users (name, email) VALUES
${"Alice"} ${"alice@example.com"},
${"Bob"} ${"bob@example.com"}
`;
console.log("插入影响行数:", inserts);
// 3) 查询(带条件)
const alice = await db`
SELECT * FROM users WHERE name = ${"Alice"}
`;
console.log(alice);
// 4) 更新
await db`
UPDATE users SET name = ${"Alicia"} WHERE email = ${"alice@example.com"}
`;
// 5) 删除
await db`
DELETE FROM users WHERE name = ${"Bob"}
`;
const all = await db`SELECT * FROM users`;
console.log("最终数据:", all);
关键点:所有 ${} 变量 Bun 都会在协议层作为绑定参数发送,绝不会拼成裸 SQL 字符串。即使 name 的值是 '); DROP TABLE users;--,也只会作为普通字符串被存进去,而不是被执行。这就是「默认安全」。
4.3 实战二:参数化查询与防注入(深入)
为了让你直观看到「自动转义」的威力,我们故意传入恶意输入:
import { SQL } from "bun";
const db = new SQL("sqlite://./app.db");
const evil = "'; DELETE FROM users; --";
// ⚠️ 看似危险,实际被当字符串处理
const result = await db`
SELECT * FROM users WHERE name = ${evil}
`;
console.log("查询结果(空,因为没叫这个名的用户,且删除没发生):", result);
// 对比:如果你用字符串拼接(千万不要这样做!)
// const bad = `SELECT * FROM users WHERE name = '${evil}'`; // 会被注入!
在 Node 的 mysql2 里你也要 "..." 包成 ? 占位符;而在 Bun.SQL,标签模板天然就是占位符。少写一行,就少一个安全漏洞的可能。
4.4 实战三:事务(transaction)
Bun.SQL 的事务 API 很优雅——db.transaction() 返回一个函数,函数体内的所有语句在同一个事务里执行,发生序列化冲突会自动重试:
import { SQL } from "bun";
const db = new SQL("sqlite://./app.db");
await db`
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
)
`;
await db`INSERT INTO accounts (id, balance) VALUES (1, 100), (2, 50)`;
// 定义转账事务:从 from 扣钱,给 to 加钱
const transfer = db.transaction((from: number, to: number, amount: number) => {
db`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${from}`;
db`UPDATE accounts SET balance = balance + ${amount} WHERE id = ${to}`;
});
// 执行(要么全成功,要么全回滚)
transfer(1, 2, 30);
const balances = await db`SELECT * FROM accounts`;
console.log(balances); // id=1 余额 70,id=2 余额 80
如果你在事务函数里抛错,Bun 会自动 ROLLBACK。这比手写 BEGIN / COMMIT / ROLLBACK 的样板代码清爽太多。
4.5 实战四:连接池与多数据库并存
真实项目经常要同时连多个库。Bun.SQL 每个实例自带池,创建多个实例即可:
import { SQL } from "bun";
// 主库(Postgres)+ 分析库(SQLite),各自独立池
const primary = new SQL("postgres://app:secret@db.internal:5432/primary");
const analytics = new SQL("sqlite://./analytics.db");
// 主库写
await primary`INSERT INTO events (type) VALUES (${"signup"})`;
// 分析库读
const stats = await analytics`
SELECT type, COUNT(*) AS cnt FROM events GROUP BY type
`;
console.log(stats);
注意:Bun 的连接池是实例级的,不会因为你 new SQL 多次就无限开连接。生产环境建议用一个模块导出的单例 db,避免重复建池:
// db.ts
import { SQL } from "bun";
export const db = new SQL(process.env.BUN_DB!);
4.6 实战五:用 Bun.serve + Bun.SQL 写一个 REST API
Bun 内置 HTTP 服务器 Bun.serve,配合 Bun.SQL,一个完整的 CRUD API 几十行搞定:
// server.ts
import { SQL } from "bun";
const db = new SQL("sqlite://./app.db");
await db`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)
`;
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
const method = req.method;
// 列出所有 todo
if (url.pathname === "/todos" && method === "GET") {
const todos = await db`SELECT * FROM todos ORDER BY id DESC`;
return Response.json(todos);
}
// 新增 todo
if (url.pathname === "/todos" && method === "POST") {
const { title } = await req.json();
await db`INSERT INTO todos (title) VALUES (${title})`;
return Response.json({ ok: true }, { status: 201 });
}
// 标记完成
if (url.pathname.startsWith("/todos/") && method === "PATCH") {
const id = url.pathname.split("/").pop();
await db`UPDATE todos SET done = 1 WHERE id = ${Number(id)}`;
return Response.json({ ok: true });
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`🚀 Todo API 跑在 http://localhost:${server.port}`);
跑起来:
bun run server.ts
curl -X POST localhost:3000/todos -H 'Content-Type: application/json' -d '{"title":"写篇文章"}'
curl localhost:3000/todos
零框架、零 ORM、零数据库驱动依赖。这就是 Bun 想给你的一体化体验。
4.7 实战六:迁移与 JSON 字段
Bun 没有强制你用某个 migration 框架,但你可以用 sql.file() 跑 SQL 文件,也可以用 JSON 字段存半结构化数据:
import { SQL } from "bun";
const db = new SQL("sqlite://./app.db");
// 直接执行一个 .sql 文件(适合做迁移脚本)
await db.file("./migrations/001_init.sql");
// 存 JSON(用原生 json() 函数,各大库通用)
await db`
INSERT INTO users (name, email, meta)
VALUES (${"Carol"}, ${"carol@example.com"}, ${JSON.stringify({ plan: "pro", logins: 3 })})
`;
// 查 JSON 字段里的子属性(Postgres 用 ->>,这里以 SQLite json_extract 为例)
const pro = await db`
SELECT name FROM users
WHERE json_extract(meta, '$.plan') = ${"pro"}
`;
console.log(pro);
五、性能优化:让 Bun.SQL 跑满它的设计上限
5.1 基准数据拆解
前面提到 Bun.SQL 比 Node 生态快 4~9 倍,这个差距来自几个叠加因素:
- 免 JS 驱动层:没有
pg/mysql2那层 JS 对象封装,数据直接从 native 缓冲区映射成 JS 值。 - 预处理语句缓存:相同模板第二次起跳过 SQL 解析。
- 连接池复用:避免握手开销。
- JSC 启动快:短任务(CLI、Serverless)体感尤其明显。
5.2 实战优化清单
① 复用同一个 db 实例(最重要)
// ❌ 错误:每次请求都 new SQL,连接池被反复创建销毁
async function handler() {
const db = new SQL(process.env.BUN_DB!); // 灾难
return db`SELECT 1`;
}
// ✅ 正确:模块级单例
import { SQL } from "bun";
export const db = new SQL(process.env.BUN_DB!);
② 用标签模板而非字符串拼接
标签模板走绑定参数,能命中预处理缓存;字符串拼接每次都要重新解析,还引入注入风险。
③ 批量插入用多值语法
// ✅ 一次 INSERT 多行,比循环单插快一个数量级
await db`
INSERT INTO events (type, uid) VALUES
${"click"} ${1},
${"view"} ${2},
${"share"} ${3}
`;
④ 读多写少场景,给 SQLite 加 WAL 模式
await db`PRAGMA journal_mode = WAL`; // SQLite 专属,显著提升并发读
⑤ 大结果集用流式 / 分页
不要一次性 SELECT * 拉十万行到内存。用 LIMIT/OFFSET 或游标分页,配合 Bun 的异步迭代。
5.3 何时不该用 Bun.SQL?
诚实地说,有几个场景我建议你谨慎:
- 需要重度 ORM 特性(迁移版本管理、复杂关联、Type-safe schema 推导):
Bun.SQL是「客户端」不是「ORM」,复杂领域还是上 Prisma / Drizzle(它们都已支持 Bun 作为运行时)。 - 极致 OLAP:分析型查询请用本站另文拆解的 ClickHouse / DuckDB,别拿事务型数据库扛报表。
- 团队全是 Node 老兵、不愿换运行时:Bun 兼容性已极佳,但切换仍是有成本的工程决策,别为「快一点」牺牲团队稳定性。
六、总结展望:运行时内建数据库,意味着什么
6.1 Bun 的全栈一体化愿景
Bun 1.4 给出的信号很清晰:未来的 JS 运行时,不该只跑 JS,它应该「懂」数据库、「懂」HTTP、「懂」打包和测试。当数据库客户端成为运行时的第一公民,全栈开发的「胶水代码」会越来越少——你不再需要为「选哪个驱动、怎么配池、怎么防注入」纠结,这些决策被运行时一次性做对了。
这对独立开发者、创业团队尤其友好:一个人,一个 bun run,从前端到数据库到 API 全链路打通。
6.2 AI 重写软件的拐点已经到来
回到开头的那场 11 天重写。它最大的价值不是「Bun 变 Rust 了」,而是它证明了一件事:大型、高风险、被认为「动不得」的代码库,现在可以被 AI agent 集群安全重构。16.5 万美元的成本,对于一份百万行级别、维护十年的核心基础设施来说,便宜得惊人。
但也要泼盆冷水:Bun 敢这么干,是因为它有可信的测试套件当裁判。如果你的系统没有测试、没有清晰的模块边界、没有可验证的回归标准,AI 重写只会生产「看起来能跑、实际随时炸」的代码。AI 放大的是你已有的工程纪律,而不是替代它。
6.3 该不该现在就用 Bun?
我的建议:
- 新项目 / 原型 / 内部工具:无脑上 Bun 1.4。
Bun.SQL+Bun.serve的组合能让你一天干完过去三天的活。 - 已有 Node 生产系统:别急着整体迁移。可以先把构建脚本、测试、CLI 工具切到 Bun(兼容性极好),稳定运行一段时间后再考虑运行时层面的迁移。
- 数据库访问:只要你的场景是标准 CRUD / 事务 / 多库读写,
Bun.SQL现在就是 Node 生态之外最省心的一档选择。
写在最后
技术世界里,「快」从来不是单一维度的胜利。Bun 1.4 的快,来自 JavaScriptCore 的底层选择,来自把数据库焊进运行时的架构勇气,也来自那场用 AI 完成的、看似不可能的语言重写。
作为工程师,我们真正该学的,不是「又一个新工具怎么用」,而是 Bun 背后的两个判断:
- 把高频痛点做成默认能力,而不是丢给社区各自造轮子;
- 用工程流程驾驭 AI,让测试套件当裁判,让人定边界。
下次当你又为了「装驱动、配连接池、防注入」写第 N 行样板时,不妨想想:这件事,本不该由你来操心。
本文代码示例基于 Bun 1.4 与
Bun.SQL公开 API,PostgreSQL / MySQL / MariaDB / SQLite 四库通用;迁移到具体数据库仅需替换连接串。建议搭配官方文档与本站其他运行时深度文(Deno 2.9、Node.js 26、TypeScript 7.0)对照阅读。