Skip to main content
查询采用带标签的模板字面量编写,客户端支持连接池、事务和预处理语句。
db.ts

功能特性

  • 带标签的模板字面量,可防止 SQL 注入
  • 事务
  • 命名参数和位置参数
  • 连接池
  • BigInt 支持
  • SASL(SCRAM-SHA-256)、MD5 和明文身份验证
  • 连接超时
  • 将行作为数据对象、数组的数组或 Buffer 返回
  • 二进制协议支持,可提升速度
  • TLS 支持(以及身份验证模式)
  • 使用环境变量自动配置
  • PostgreSQL LISTEN / NOTIFY

数据库支持

Bun.SQL 提供多数据库系统的统一 API:

PostgreSQL

PostgreSQL 适用于以下情况:
  • 连接字符串不符合 SQLite 或 MySQL 格式(作为默认适配器)
  • 明确使用 postgres://postgresql:// 协议的连接字符串
  • 无连接字符串,但环境变量指向 PostgreSQL
db.ts

MySQL

MySQL 内置于 Bun.SQL 中,使用相同的标签模板字面量接口,并兼容 MySQL 5.7+ 和 MySQL 8.0+:
db.ts
MySQL 支持多种 URL 格式作为连接字符串:
MySQL 数据库支持:
  • 预处理语句:参数化查询自动创建且缓存语句
  • 二进制协议:提升预处理语句性能并支持准确类型处理
  • 多结果集:支持存储过程返回多个结果集
  • 认证插件:支持 mysql_native_password、caching_sha2_password(MySQL 8.0 默认)和 sha256_password
  • SSL/TLS 连接:SSL 模式配置类似 PostgreSQL
  • 连接属性:客户端信息发送到服务器便于监控
  • 查询流水线:无需等待响应即可执行多个预处理语句
MySQL 8 账户默认使用 caching_sha2_password 插件。用户的首次连接需要完整身份验证:在未加密的连接上,客户端必须下载服务器的 RSA 公钥来加密密码。Bun 默认拒绝这样做(与 mysql2 和 Connector/J 的默认设置相同),因为网络路径上的攻击者可能替换自己的密钥并获取密码。连接会失败,并显示 ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED请使用 TLS 连接;如果你信任网络环境(例如本地开发数据库),也可以显式选择启用:

SQLite

SQLite 内置于 Bun.SQL 中,使用相同的标签模板字面量接口:
SQLite 支持多种 URL 格式作为连接字符串:
简单文件名(如 "myapp.db")必须显式设置 { adapter: "sqlite" },以避免和 PostgreSQL 混淆。
SQLite 支持额外配置选项:
URL 中的查询参数会映射到这些配置:
  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true(默认)

插入数据

将 JavaScript 值直接传递给 SQL 模板字面量;Bun 会处理转义。

批量插入

你也可以传递对象数组,Bun 会将其展开为 INSERT INTO ... VALUES ... 语句。

选择插入列

使用 sql(object, ...string) 选择要插入的列。每一列都必须在对象中定义。

查询结果

默认情况下,Bun 的 SQL 客户端将查询结果返回为对象数组,其中每个对象表示一行,并以列名作为键。还提供另外两种格式。

sql``.values() 格式

sql``.values() 方法将每一行返回为值数组,顺序与查询中的列顺序相同。
行的形式如下:
当查询返回重复的列名时,sql``.values() 非常有用。对于对象(默认格式),由于列名是键,最后一个列会覆盖前面的列。使用 sql``.values() 时,每一列都会存在于数组中,因此可以通过索引读取重复列。

sql``.raw() 格式

.raw() 方法将行返回为 Buffer 对象数组。二进制数据处理或追求性能时可以使用它。

SQL 片段

Bun 可以根据运行时条件动态构建查询,同时避免 SQL 注入风险。

动态表名

要动态引用表或模式,请使用 sql() 辅助函数,它会对其进行转义:

条件查询

使用 sql() 辅助函数构建包含条件子句的查询:

更新时动态列

使用 sql(object, ...string) 选择要更新的列。每一列都必须在对象上定义。如果不列出任何列,则会使用对象中的所有键。

动态值和 WHERE IN

值列表也可以动态创建,用于 WHERE IN 查询。你还可以传入对象数组,并指定用于构建列表的键名。

sql.array 辅助

sql.array 用于将 JS 数组转换为 PostgreSQL 数组字面量:
sql.array 仅支持 PostgreSQL,多维数组和 NULL 元素可能暂不支持。

sql``.simple()

PostgreSQL 协议支持两类查询:“简单”和“扩展”查询。简单查询支持多条语句但不支持参数,扩展查询支持参数但只能一条语句。 要在一次查询中执行多条语句,使用 sql``.simple()
简单查询适用于数据库迁移和设置脚本。 简单查询不能使用参数(${value})。如果需要使用参数,请将查询拆分为多个独立语句。

文件查询

sql.file 从文件中读取查询并执行。如果文件使用 $1$2 之类的占位符,可以向查询传递参数。不传递参数时,文件可以包含多个命令。
使用 SQLite 适配器时,参数也可以是命名参数对象(:name$name@name 占位符),形式与下文 sql.unsafe 接受的对象相同。

不安全查询

sql.unsafe 执行原始 SQL 字符串。请谨慎使用:它不会转义用户输入。不传递参数时,字符串可以包含多个命令。
使用 SQLite 适配器时,参数也可以是使用 :name$name@name 占位符的命名参数对象。对象键会保留前缀({ ":id": 1 }),除非连接设置了 strict: true,此时允许使用不带前缀的键:

执行和取消查询

查询是惰性的:只有在使用 await 或通过 .execute() 运行时才会开始执行。 要取消正在运行的查询,请在查询对象上调用 cancel()

数据库环境变量

你可以使用环境变量配置 sql 连接参数。客户端会按照优先级顺序检查这些变量,并根据连接字符串的格式检测数据库类型。

自动数据库检测

当你不带参数使用 Bun.sql(),或使用带连接字符串的 new SQL() 时,Bun 会根据 URL 格式检测适配器:

MySQL 自动检测

当连接字符串匹配以下模式时,会选择 MySQL:
  • mysql://... — MySQL 协议
  • mysql2://... — MySQL2 协议(兼容别名)

SQLite 自动检测

当连接字符串匹配以下模式时,会选择 SQLite:
  • :memory: — 内存数据库
  • sqlite://... — SQLite 协议
  • sqlite:... — 无斜杠的 SQLite 协议
  • file://... — 文件协议
  • file:... — 无斜杠的文件协议

PostgreSQL 自动检测

当不匹配 MySQL 或 SQLite 格式时,默认使用 PostgreSQL:

MySQL 环境变量

可以使用环境变量配置 MySQL 连接:
如果未提供连接 URL,Bun 会检查以下单独的参数:

PostgreSQL 环境变量

以下环境变量用于定义 PostgreSQL 连接: 如果未提供连接 URL,Bun 会检查以下单独的参数:

SQLite 环境变量

DATABASE_URL 包含兼容 SQLite 的 URL 时,可以使用它配置 SQLite 连接:
注意: 使用 SQLite 时,POSTGRES_URLPGHOST 等 PostgreSQL 专用环境变量会被忽略。

运行时预连接

Bun 可以在应用程序代码运行之前,于启动时预先连接到 PostgreSQL,因此首次查询无需承担连接延迟。
--sql-preconnect 标志会在启动时使用您配置的环境变量建立 PostgreSQL 连接。如果连接失败,错误会在不导致应用程序崩溃的情况下得到处理。

连接选项

你可以通过向 SQL 构造函数传递选项来手动配置连接。选项因适配器而异:

MySQL 选项

PostgreSQL 选项

SQLite 选项

  • 连接池:SQLite 是文件型数据库,不使用连接池,每个 SQL 实例对应单一连接。
  • 事务:SQLite 支持通过保存点实现嵌套事务,类似 PostgreSQL。
  • 并发访问:SQLite 通过文件锁控制并发,建议使用 WAL 日志模式以提升并发能力。
  • 内存数据库:memory: 创建临时数据库,仅存活于连接生命周期内。

动态密码

对于访问令牌等替代身份验证方案,或使用轮换密码的数据库,请将 password 设置为同步或异步函数。Bun 会在连接时调用该函数以获取密码。

SQLite 特有功能

查询执行

SQLite 同步执行查询,与使用异步 I/O 的 PostgreSQL 不同。API 仍然返回 Promise:

SQLite PRAGMA 语句

使用 PRAGMA 语句配置 SQLite 行为:

数据类型差异

SQLite 类型系统比 PostgreSQL 灵活:

事务

sql.begin 启动事务,支持 PostgreSQL 和 SQLite。PostgreSQL 会从连接池中保留连接,SQLite 则在单一连接上开始事务。 BEGIN 命令会自动发送,包括你指定的任何可选配置。如果事务期间发生错误,Bun 会执行 ROLLBACK

基本事务

要在事务中对查询进行流水线处理,请从回调中返回查询数组:

保存点(Savepoints)

保存点会在事务中创建中间检查点,因此可以回滚其中一部分,而不会中止整个事务。

分布式事务

两阶段提交(2PC)是一种分布式事务协议:在第一阶段,协调者准备每个节点,确保其数据已写入并准备提交;在第二阶段,各节点根据协调者的决定提交或回滚。 在 PostgreSQL 和 MySQL 中,分布式事务会在其原始会话结束后继续存在,因此具有相应权限的用户或协调者可以在之后提交或回滚这些事务。PostgreSQL 将其实现为预备事务;MySQL 使用 XA 事务。 分布式事务期间未捕获的异常会回滚所有更改。否则,你可以在之后提交或回滚该事务。

认证

Bun 支持 SCRAM-SHA-256(SASL)、MD5 和明文身份验证。出于更高的安全性考虑,建议使用 SASL。请参阅 Postgres SASL 身份验证

SSL 模式概述

PostgreSQL 的 SSL/TLS 模式控制是否需要安全连接,以及执行何种程度的证书验证。

连接字符串中设置

你也可以在连接字符串中设置 SSL 模式:

连接池

Bun 的 SQL 客户端管理连接池:数据库连接会在多次查询之间重复使用,而不是每次查询都重新打开和关闭连接;连接池还会限制并发连接数。
在执行查询之前,不会建立连接。

保留连接

sql.reserve() 从连接池中获取一个连接,并返回一个封装该连接的客户端,以便你可以在隔离的连接上运行查询。

LISTEN / NOTIFY(PostgreSQL)

sql.listen() 订阅 PostgreSQL 通知频道,sql.notify() 向频道发布通知。共享数据库的进程可以将其用作轻量级消息总线:缓存失效、在插入行时唤醒工作进程(通常由调用 pg_notify 的触发器完成)、分发小型事件。
listen() 会在 PostgreSQL 确认 LISTEN 后完成,因此在它完成后发出的 notify() 都能够送达。它所完成得到的订阅对象也是异步可释放对象:

工作原理

  • 客户端上的所有订阅共享一个专用连接。第一次调用 listen() 时会打开该连接,移除最后一个订阅时会关闭它,因此从未监听的客户端无需承担该连接的开销,取消所有监听后进程也可以在不调用 sql.close() 的情况下退出。
  • 只要存在任何订阅,该连接就会保持进程运行,就像监听中的服务器一样。
  • 如果连接断开,会以指数退避方式重新建立连接(250 毫秒开始,逐步加倍至 32 秒,并加入抖动),然后重新订阅每个频道。PostgreSQL 只会向已连接的监听者发送通知,因此期间发出的通知会丢失;listen() 的可选第三个参数会在初次订阅和每次重新连接后运行,可以在此处补齐遗漏的通知:
  • 每次调用 listen() 都是独立订阅。同一频道上的多个订阅会共享单个服务端 LISTEN,每个回调都会收到每条通知,而每个句柄的 unlisten() 只会移除其自身调用注册的订阅。抛出异常的回调(无论是哪个参数中的回调)会被报告为未捕获异常,但订阅仍会保留。
  • 频道名称会自动作为标识符进行引用;与任何 PostgreSQL 标识符一样,其长度限制为 63 个字节。超过长度的名称会被拒绝,而不是静默截断。PostgreSQL 默认将负载限制为 8000 个字节。

notify()

notify() 是一个普通查询(SELECT pg_notify($1, $2)),会在你调用它的句柄上执行。在 sql 上调用时使用连接池;在 sql.begin() 内调用时会在事务中执行,因此 PostgreSQL 会在 COMMIT 时发送通知,并在 ROLLBACK 时丢弃通知。这样可以只在更改可见后发出通知:
负载是可选的:sql.notify("cache-invalidated") 就是 PostgreSQL 的裸 NOTIFY。保留连接和事务句柄也具有 listen();它始终使用客户端共享的监听连接。 LISTEN/NOTIFY 仅支持 PostgreSQL;在 MySQL 和 SQLite 上调用这些方法会被拒绝。

预处理语句

默认情况下,Bun 的 SQL 客户端会为它能够推断为静态的查询创建具名预处理语句,这样速度更快。要禁用此功能,请在连接选项中设置 prepare: false
设置 prepare: false 后: 查询仍使用“扩展”协议,但会作为未命名预处理语句运行。未命名预处理语句只会持续到下一条指定将未命名语句作为目标的 Parse 语句发出为止。
  • 参数绑定仍然可以安全防止 SQL 注入
  • 每次查询都会由服务器从头开始解析和规划
  • 查询不会进行流水线处理
  • 使用 PGBouncer 事务模式(1.21.0 以后的版本如果配置正确已支持命名语句)
  • 调试查询执行计划
  • 动态 SQL 需频繁重新规划查询
  • 不支持多条语句(除非用 .simple()
  • 在事务模式下使用 PGBouncer(不过从 PGBouncer 1.21.0 开始,在正确配置的情况下已支持协议级别的具名预处理语句)
  • 调试查询执行计划
  • 处理需要频繁重新生成查询计划的动态 SQL
  • 每个查询只支持一条命令(除非使用 sql``.simple()
禁用预处理语句可能会减慢使用不同参数频繁执行的查询,因为服务器需要对每次查询都从头开始解析和规划。

错误处理

客户端为不同的失败场景提供了类型化错误。错误具有数据库特定性,并继承自基础错误类:

错误类示例

PostgreSQL 连接错误

认证错误

查询错误

数据类型错误

协议错误

事务错误

SQLite 特有错误

SQLite 错误包含 SQLite 的标准错误码和错误号:
错误处理示例:

数字与 BigInt

超出 53 位整数范围的数字将以字符串形式返回:

使用 BigInt 代替字符串

要将大数作为 BigInt 而不是字符串获取,请在创建 SQL 客户端时将 bigint 选项设置为 true

路线图

我们尚未完成的事项:
  • 使用 --db-preconnect Bun CLI 标志预加载连接
  • 列名转换(例如,将 snake_case 转换为 camelCase)。这主要受限于使用 WebKit 的 WTF::String 在 C++ 中实现支持 Unicode 的大小写转换。
  • 列类型转换

数据库特有功能

认证方式

MySQL 支持多种认证插件,自动协商:
  • mysql_native_password - 传统 MySQL 认证,兼容性好
  • caching_sha2_password - MySQL 8.0+ 默认,更安全,使用 RSA 密钥交换
  • sha256_password - 基于 SHA-256 的认证
客户端自动处理服务器请求切换认证插件,包括非 SSL 下的密码安全交换。

预处理语句与性能

MySQL 对所有参数化查询使用服务端预处理语句:

多结果集

MySQL 支持多语句查询返回多个结果集:

字符集与排序规则

Bun.SQL 对 MySQL 连接使用 utf8mb4 字符集,涵盖包括表情符号在内的所有 Unicode 字符。

连接属性

Bun 会向 MySQL 发送客户端信息以便进行监控:

类型处理

MySQL 类型会转换为 JavaScript 类型: DATETIMETIMESTAMP 值在传输过程中不包含时区,因此 Bun 会按 UTC 读取回来——你得到的 Date 会保留存储时相同的 UTC 墙上时间,而不受机器时区影响。这与值的写入方式一致(绑定的 Date 会存储其 UTC 分量)。PostgreSQL 的 timestamp(无时区)也是如此;timestamptz 则带有显式偏移,因此不受影响。

与 PostgreSQL 的差异

API 是统一的,但行为有所不同:
  1. 参数占位符:MySQL 内部使用 ?,Bun 自动转换 $1$2 风格占位符
  2. RETURNING 语法:MySQL 不支持 RETURNING,使用 result.lastInsertRowid 或单独 SELECT
  3. 数组类型:MySQL 无原生数组类型,区别于 PostgreSQL

MySQL 特有功能

我们尚未实现对 LOAD DATA INFILE 的支持。

PostgreSQL 特有功能

尚未实现:
  • COPY 支持
未实现的一些较少用特性:
  • GSSAPI 认证
  • SCRAM-SHA-256-PLUS 支持
  • Point 和 PostGIS 类型
  • 多维整数数组(仅支持部分类型)

常见模式与最佳实践

处理 MySQL 结果集

MySQL 错误处理

MySQL 性能优化建议

  1. 使用连接池:根据负载配置合理的 max 连接数
  2. 启用预处理语句:默认开启,提升性能
  3. 批量操作用事务包裹:关联查询放在同个事务里
  4. 合理建立索引:MySQL 查询性能依赖索引策略
  5. 使用 utf8mb4 字符集:默认启用,支持完整 Unicode 字符。

常见问题

最初的计划是添加更多数据库驱动。统一 API 现在支持 PostgreSQL、MySQL 和 SQLite。
适配器根据连接字符串自动识别:
  • mysql://mysql2:// 开头为 MySQL
  • 匹配 SQLite 格式(:memory:sqlite://file://)为 SQLite
  • 其他默认使用 PostgreSQL
支持,包括 OUT 参数和多个结果集:
当然,可以使用所有 MySQL 专属语法:

为什么不直接用已有库?

你也可以在 Bun 中使用 postgres.js、pg 和 node-postgres 等 npm 包。它们都是很好的选择。 但有两个原因:
  1. 我们认为,对于开发者来说,将数据库驱动内置到 Bun 中会更加简单。你花在挑选库上的时间,本可以用来构建应用。
  2. 我们利用了一些 JavaScriptCore 引擎内部机制来更快地创建对象,而这些机制很难在库中实现。

鸣谢

特别感谢 @porsagerpostgres.js 对 API 接口设计的启发。