编程 Bun 1.4 深度实战:当 JS 运行时内置了数据库客户端——Bun.SQL 统一 API 与 Zig→Rust AI 重写如何重写全栈心智模型

2026-07-15 05:42:13 +0800 CST views 8

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 26V8npm(独立)需装需装需装
Deno 2.9V8内置内置内置需装(或 deno doc)
Bun 1.4JavaScriptCore内置内置内置内置 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 极致的性能和极致的控制力,但也带来两个现实问题:

  1. 招聘难:能用 Zig 写底层运行时的人,全球一只手数得过来;
  2. 内存安全负担: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
连 PostgreSQLpg + 手写池内置,开箱即用
连 MySQLmysql2内置,开箱即用
连 SQLitebetter-sqlite3(需编译)内置,无需编译
参数化查询手动 ? / $1标签模板自动转义
预处理语句各库 API 不同统一
依赖体积数个 npm 包 + native binding0

根据 Bun 创始人 Jarred Sumner 披露的基准(基于 10 万次简单查询耗时):Bun.SQL 比在 Node 里用 mysql2 快约 9 倍,比在 Node 里用 MariaDB 官方客户端快约 4 倍。这个差距主要来自:去掉了 JS 层的对象分配开销、原生预处理语句、以及 Bun 运行时层面的连接复用优化。


三、架构分析:Bun 1.4 是怎么把数据库「焊死」在运行时里的

3.1 Bun.SQL 的内部实现思路

虽然 Bun 没有把每一行源码摊开讲,但从它的行为可以反推出架构要点:

  1. 原生驱动,非封装Bun.SQL 不是「在 JS 里调 pg」。它直接用各数据库的有线协议(wire protocol)实现了客户端——PostgreSQL 的前端/后端消息协议、MySQL 的文本/二进制协议、SQLite 的 C API 绑定。这意味着零层级转发、零额外 GC 压力。
  2. 统一抽象层:所有数据库适配到同一个 SQL 类。查询被编译成「参数化语句树」,再下发给具体协议的执行器。这就是你写一份 sql\...`` 就能跨库运行的原因。
  3. 连接池内建new SQL(url) 返回的实例内部维护连接池。频繁短查询走池化复用,避免每次 connect/disconnect 的握手开销。
  4. 预处理语句缓存:相同的 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 + 测试套件做裁判的闭环。这套方法论对普通团队也有启发:

  1. 让测试套件当「真理之源」:没有高覆盖、可信赖的测试,AI 重写寸步难行。Bun 敢这么干,前提是它有一套能跑红/绿的成熟测试。
  2. 小步、可验证、可回滚:每个 agent 负责一个语义清晰的模块,翻译完立刻跑对应测试。红了就修,修不动就隔离。
  3. 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.jsontsconfig.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 倍,这个差距来自几个叠加因素:

  1. 免 JS 驱动层:没有 pg/mysql2 那层 JS 对象封装,数据直接从 native 缓冲区映射成 JS 值。
  2. 预处理语句缓存:相同模板第二次起跳过 SQL 解析。
  3. 连接池复用:避免握手开销。
  4. 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 背后的两个判断:

  1. 把高频痛点做成默认能力,而不是丢给社区各自造轮子;
  2. 用工程流程驾驭 AI,让测试套件当裁判,让人定边界。

下次当你又为了「装驱动、配连接池、防注入」写第 N 行样板时,不妨想想:这件事,本不该由你来操心。

本文代码示例基于 Bun 1.4 与 Bun.SQL 公开 API,PostgreSQL / MySQL / MariaDB / SQLite 四库通用;迁移到具体数据库仅需替换连接串。建议搭配官方文档与本站其他运行时深度文(Deno 2.9、Node.js 26、TypeScript 7.0)对照阅读。

推荐文章

向满屏的 Import 语句说再见!
2024-11-18 12:20:51 +0800 CST
html夫妻约定
2024-11-19 01:24:21 +0800 CST
JavaScript设计模式:组合模式
2024-11-18 11:14:46 +0800 CST
PHP openssl 生成公私钥匙
2024-11-17 05:00:37 +0800 CST
10个几乎无人使用的罕见HTML标签
2024-11-18 21:44:46 +0800 CST
前端如何优化资源加载
2024-11-18 13:35:45 +0800 CST
程序员茄子在线接单