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 连接字符串格式
MySQL 连接字符串格式
MySQL 支持多种 URL 格式作为连接字符串:
MySQL 特有功能
MySQL 特有功能
MySQL 数据库支持:
- 预处理语句:参数化查询自动创建且缓存语句
- 二进制协议:提升预处理语句性能并支持准确类型处理
- 多结果集:支持存储过程返回多个结果集
- 认证插件:支持 mysql_native_password、caching_sha2_password(MySQL 8.0 默认)和 sha256_password
- SSL/TLS 连接:SSL 模式配置类似 PostgreSQL
- 连接属性:客户端信息发送到服务器便于监控
- 查询流水线:无需等待响应即可执行多个预处理语句
MySQL 8 在明文 TCP 上进行身份验证(allowPublicKeyRetrieval)
MySQL 8 在明文 TCP 上进行身份验证(allowPublicKeyRetrieval)
MySQL 8 账户默认使用
caching_sha2_password 插件。用户的首次连接需要完整身份验证:在未加密的连接上,客户端必须下载服务器的 RSA 公钥来加密密码。Bun 默认拒绝这样做(与 mysql2 和 Connector/J 的默认设置相同),因为网络路径上的攻击者可能替换自己的密钥并获取密码。连接会失败,并显示 ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED。请使用 TLS 连接;如果你信任网络环境(例如本地开发数据库),也可以显式选择启用:SQLite
SQLite 内置于Bun.SQL 中,使用相同的标签模板字面量接口:
SQLite 连接字符串格式
SQLite 连接字符串格式
SQLite 支持多种 URL 格式作为连接字符串:
简单文件名(如
"myapp.db")必须显式设置 { adapter: "sqlite" },以避免和 PostgreSQL 混淆。SQLite 特定选项
SQLite 特定选项
SQLite 支持额外配置选项:URL 中的查询参数会映射到这些配置:
?mode=ro→readonly: true?mode=rw→readonly: false, create: false?mode=rwc→readonly: 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 之类的占位符,可以向查询传递参数。不传递参数时,文件可以包含多个命令。
:name、$name 或 @name 占位符),形式与下文 sql.unsafe 接受的对象相同。
不安全查询
sql.unsafe 执行原始 SQL 字符串。请谨慎使用:它不会转义用户输入。不传递参数时,字符串可以包含多个命令。
: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 连接:PostgreSQL 环境变量
以下环境变量用于定义 PostgreSQL 连接:
如果未提供连接 URL,Bun 会检查以下单独的参数:
SQLite 环境变量
当DATABASE_URL 包含兼容 SQLite 的 URL 时,可以使用它配置 SQLite 连接:
POSTGRES_URL 和 PGHOST 等 PostgreSQL 专用环境变量会被忽略。
运行时预连接
Bun 可以在应用程序代码运行之前,于启动时预先连接到 PostgreSQL,因此首次查询无需承担连接延迟。--sql-preconnect 标志会在启动时使用您配置的环境变量建立 PostgreSQL 连接。如果连接失败,错误会在不导致应用程序崩溃的情况下得到处理。
连接选项
你可以通过向SQL 构造函数传递选项来手动配置连接。选项因适配器而异:
MySQL 选项
PostgreSQL 选项
SQLite 选项
SQLite 连接注意事项
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())
错误处理
客户端为不同的失败场景提供了类型化错误。错误具有数据库特定性,并继承自基础错误类:错误类示例
SQLite 特有错误
SQLite 错误包含 SQLite 的标准错误码和错误号:常见 SQLite 错误码
常见 SQLite 错误码
错误处理示例:
数字与 BigInt
超出 53 位整数范围的数字将以字符串形式返回:使用 BigInt 代替字符串
要将大数作为BigInt 而不是字符串获取,请在创建 SQL 客户端时将 bigint 选项设置为 true:
路线图
我们尚未完成的事项:- 使用
--db-preconnectBun 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 的认证
预处理语句与性能
MySQL 对所有参数化查询使用服务端预处理语句:多结果集
MySQL 支持多语句查询返回多个结果集:字符集与排序规则
Bun.SQL 对 MySQL 连接使用 utf8mb4 字符集,涵盖包括表情符号在内的所有 Unicode 字符。
连接属性
Bun 会向 MySQL 发送客户端信息以便进行监控:类型处理
MySQL 类型会转换为 JavaScript 类型:DATETIME 和 TIMESTAMP 值在传输过程中不包含时区,因此 Bun 会按 UTC 读取回来——你得到的 Date 会保留存储时相同的 UTC 墙上时间,而不受机器时区影响。这与值的写入方式一致(绑定的 Date 会存储其 UTC 分量)。PostgreSQL 的 timestamp(无时区)也是如此;timestamptz 则带有显式偏移,因此不受影响。
与 PostgreSQL 的差异
API 是统一的,但行为有所不同:- 参数占位符:MySQL 内部使用
?,Bun 自动转换$1、$2风格占位符 - RETURNING 语法:MySQL 不支持 RETURNING,使用
result.lastInsertRowid或单独 SELECT - 数组类型:MySQL 无原生数组类型,区别于 PostgreSQL
MySQL 特有功能
我们尚未实现对LOAD DATA INFILE 的支持。
PostgreSQL 特有功能
尚未实现:COPY支持
- GSSAPI 认证
SCRAM-SHA-256-PLUS支持- Point 和 PostGIS 类型
- 多维整数数组(仅支持部分类型)
常见模式与最佳实践
处理 MySQL 结果集
MySQL 错误处理
MySQL 性能优化建议
- 使用连接池:根据负载配置合理的
max连接数 - 启用预处理语句:默认开启,提升性能
- 批量操作用事务包裹:关联查询放在同个事务里
- 合理建立索引:MySQL 查询性能依赖索引策略
- 使用
utf8mb4字符集:默认启用,支持完整 Unicode 字符。
常见问题
为什么是这个 `Bun.sql`,而不是 `Bun.postgres`?
为什么是这个 `Bun.sql`,而不是 `Bun.postgres`?
最初的计划是添加更多数据库驱动。统一 API 现在支持 PostgreSQL、MySQL 和 SQLite。
如何知道当前使用的是哪个数据库适配器?
如何知道当前使用的是哪个数据库适配器?
适配器根据连接字符串自动识别:
- 以
mysql://或mysql2://开头为 MySQL - 匹配 SQLite 格式(
:memory:、sqlite://、file://)为 SQLite - 其他默认使用 PostgreSQL
支持 MySQL 存储过程吗?
支持 MySQL 存储过程吗?
支持,包括 OUT 参数和多个结果集:
可以使用 MySQL 特有语法吗?
可以使用 MySQL 特有语法吗?
当然,可以使用所有 MySQL 专属语法:
为什么不直接用已有库?
你也可以在 Bun 中使用 postgres.js、pg 和 node-postgres 等 npm 包。它们都是很好的选择。 但有两个原因:- 我们认为,对于开发者来说,将数据库驱动内置到 Bun 中会更加简单。你花在挑选库上的时间,本可以用来构建应用。
- 我们利用了一些 JavaScriptCore 引擎内部机制来更快地创建对象,而这些机制很难在库中实现。