db.ts
功能特性
- 带标签的模板字面量,可防止 SQL 注入
- 事务
- 命名参数和位置参数
- 连接池
BigInt支持- SASL(SCRAM-SHA-256)、MD5 和明文身份验证
- 连接超时
- 将行作为数据对象、数组的数组或 Buffer 返回
- 二进制协议支持,可提升速度
- TLS 支持(以及身份验证模式)
- 使用环境变量自动配置
- PostgreSQL
LISTEN/NOTIFY
数据库支持
Bun.SQL 提供多数据库系统的统一 API:
PostgreSQL
Bun uses PostgreSQL when:- 连接字符串不符合 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 支持额外配置选项:Bun parses query parameters in the URL to set these options:
?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() 辅助函数构建包含条件子句的查询:
更新时动态列
Usesql(object, ...string) to pick which columns to update. Each column must be defined on the object. If you don’t list any columns, Bun uses all keys on the object.
动态值和 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 自动检测
Bun selects MySQL when the connection string matches these patterns:mysql://...— MySQL 协议mysql2://...— MySQL2 协议(兼容别名)
SQLite 自动检测
Bun selects SQLite when the connection string matches these patterns::memory:— 内存数据库sqlite://...— SQLite 协议sqlite:...— 无斜杠的 SQLite 协议file://...— 文件协议file:...— 无斜杠的文件协议
PostgreSQL 自动检测
当不匹配 MySQL 或 SQLite 格式时,默认使用 PostgreSQL:MySQL 环境变量
You can configure MySQL connections with environment variables:PostgreSQL 环境变量
以下环境变量用于定义 PostgreSQL 连接:
Without a connection URL, Bun checks these individual parameters:
SQLite 环境变量
You can configure SQLite connections withDATABASE_URL when it contains a SQLite-compatible URL:
POSTGRES_URL and PGHOST when you use SQLite.
运行时预连接
Bun 可以在应用程序代码运行之前,于启动时预先连接到 PostgreSQL,因此首次查询无需承担连接延迟。--sql-preconnect flag establishes a PostgreSQL connection at startup using your configured environment variables. If the connection fails, Bun handles the error without crashing your application.
连接选项
你可以通过向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 则在单一连接上开始事务。
Bun sends the BEGIN command automatically, including any optional configurations you specify. If an error occurs during the transaction, Bun issues a ROLLBACK.
基本事务
保存点(Savepoints)
保存点会在事务中创建中间检查点,因此可以回滚其中一部分,而不会中止整个事务。分布式事务
Two-Phase Commit (2PC) is a distributed transaction protocol. In phase 1, the coordinator prepares each node, making sure its data is written and ready to commit. In phase 2, the nodes commit or roll back based on the coordinator’s decision. 在 PostgreSQL 和 MySQL 中,分布式事务会在其原始会话结束后继续存在,因此具有相应权限的用户或协调者可以在之后提交或回滚这些事务。PostgreSQL 将其实现为预备事务;MySQL 使用 XA 事务。 分布式事务期间未捕获的异常会回滚所有更改。否则,你可以在之后提交或回滚该事务。认证
Bun supports SCRAM-SHA-256 (SASL), MD5, and Clear Text authentication. We recommend SASL for better security. See Postgres SASL Authentication.SSL 模式概述
PostgreSQL 的 SSL/TLS 模式控制是否需要安全连接,以及执行何种程度的证书验证。连接字符串中设置
你也可以在连接字符串中设置 SSL 模式:连接池
Bun’s SQL client manages a connection pool. The pool reuses database connections across queries instead of opening and closing one for each query, and it caps the number of concurrent connections.保留连接
sql.reserve() 从连接池中获取一个连接,并返回一个封装该连接的客户端,以便你可以在隔离的连接上运行查询。
reserve() waits until one frees up. Pass an AbortSignal to stop waiting: the promise rejects with signal.reason and no connection is taken from the pool. Aborting after the promise resolved has no effect, so a connection you received must still be released.
LISTEN / NOTIFY(PostgreSQL)
sql.listen() 订阅 PostgreSQL 通知频道,sql.notify() 向频道发布通知。共享数据库的进程可以将其用作轻量级消息总线:缓存失效、在插入行时唤醒工作进程(通常由调用 pg_notify 的触发器完成)、分发小型事件。
listen() 会在 PostgreSQL 确认 LISTEN 后完成,因此在它完成后发出的 notify() 都能够送达。它所完成得到的订阅对象也是异步可释放对象:
工作原理
- All subscriptions on a client share one dedicated connection. The first
listen()opens it, and removing the last subscription closes it. As a result, a client that never listens never pays for the connection, and unlistening everything lets the process exit withoutsql.close(). - While anything is subscribed, that connection keeps the process alive, like a listening server.
- If the connection drops, Bun re-establishes it with exponential backoff (250ms doubling to 32s, with jitter) and re-subscribes every channel. PostgreSQL only delivers to connected listeners, so notifications sent in between are lost. The optional third argument to
listen()runs on the initial subscribe and after every reconnect, which is the place to catch up:
- Every
listen()call is its own subscription. Several on one channel share a single server-sideLISTEN. Each callback receives every notification, and each handle’sunlisten()removes only what its own call registered. A callback (either argument) that throws is reported as an uncaught exception and stays subscribed. - Bun quotes channel names as identifiers for you. Like any PostgreSQL identifier, they are limited to 63 bytes, and Bun rejects longer names rather than silently truncating them. PostgreSQL limits payloads to 8000 bytes by default.
notify()
notify() is an ordinary query (SELECT pg_notify($1, $2)) on whichever handle you call it through. On sql it uses the pool. Inside sql.begin() it runs in the transaction, so PostgreSQL delivers it on COMMIT and drops it on ROLLBACK. Calling notify() inside a transaction is how to announce a change only once it is visible:
sql.notify("cache-invalidated") 就是 PostgreSQL 的裸 NOTIFY。保留连接和事务句柄也具有 listen();它始终使用客户端共享的监听连接。
LISTEN/NOTIFY 仅支持 PostgreSQL;在 MySQL 和 SQLite 上调用这些方法会被拒绝。
预处理语句
By default, Bun’s SQL client creates named prepared statements for queries it can infer are static, which is faster. To disable named prepared statements, setprepare: false in the connection options:
prepare: false 后:
查询仍使用“扩展”协议,但会作为未命名预处理语句运行。未命名预处理语句只会持续到下一条指定将未命名语句作为目标的 Parse 语句发出为止。
- Parameter binding is still safe against SQL injection
- The server parses and plans each query from scratch
- Queries are not pipelined
-
Only one command per query is supported (unless you use
sql``.simple()) - 使用 PGBouncer 事务模式(1.21.0 以后的版本如果配置正确已支持命名语句)
- 调试查询执行计划
- 动态 SQL 需频繁重新规划查询
-
不支持多条语句(除非用
.simple()) - Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)
- Debugging query execution plans
- Working with dynamic SQL where query plans need to be regenerated frequently
错误处理
客户端为不同的失败场景提供了类型化错误。错误具有数据库特定性,并继承自基础错误类:错误类示例
PostgreSQL 特定错误码
PostgreSQL 特定错误码
PostgreSQL 连接错误
认证错误
查询错误
数据类型错误
The client raises these errors while it decodes a result, after the server ran the query. Only that query rejects. The connection stays open, and the other queries on it continue. An
INSERT, UPDATE or DELETE ... RETURNING can already be stored at that point, so do not retry a write blindly. To read the value, cast the column to text or use .raw().Protocol Errors
事务错误
SQLite 特有错误
SQLite 错误包含 SQLite 的标准错误码和错误号:常见 SQLite 错误码
常见 SQLite 错误码
错误处理示例:
数字与 BigInt
Bun returns PostgreSQLbigint (int8) values, and MySQL BIGINT values outside the 32-bit range, as strings:
使用 BigInt 代替字符串
要将大数作为BigInt 而不是字符串获取,请在创建 SQL 客户端时将 bigint 选项设置为 true:
路线图
我们尚未完成的事项:- Column name transforms (for example,
snake_casetocamelCase). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit’sWTF::String. - Column type transforms
数据库特有功能
认证方式
MySQL 支持多种认证插件,自动协商: MySQL supports multiple authentication plugins, which the client negotiates automatically: 客户端自动处理服务器请求切换认证插件,包括非 SSL 下的密码安全交换。 The client automatically handles authentication plugin switching when the server requests it. MySQL 对所有参数化查询使用服务端预处理语句:多结果集
MySQL 支持多语句查询返回多个结果集:字符集与排序规则
Bun.SQL 对 MySQL 连接使用 utf8mb4 字符集,涵盖包括表情符号在内的所有 Unicode 字符。
连接属性
Bun 会向 MySQL 发送客户端信息以便进行监控:类型处理
Bun converts MySQL types to JavaScript types:DATETIME and TIMESTAMP values have no timezone on the wire, so Bun reads them back as UTC. The Date you get has the same UTC wall-clock that was stored, regardless of the machine’s timezone. Reading as UTC matches how Bun writes values (a bound Date stores its UTC components). The same applies to PostgreSQL’s timestamp (without time zone); timestamptz carries an explicit offset and is unaffected.
Bun also sets time_zone = '+00:00' on every MySQL connection it opens. The server converts TIMESTAMP columns through the session time zone, so this keeps the stored instant equal to the Date you bound, and it makes server-side time functions such as NOW() and CURRENT_TIMESTAMP evaluate in UTC. There is no option to pick a different session time zone yet: a manual SET time_zone = ... reaches only the one pooled connection that runs it (#40254 tracks a per-connection option).
与 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。
How do I know which database adapter is being used?
How do I know which database adapter is being used?
Bun detects the adapter automatically from the connection string:
- 以
mysql://或mysql2://开头为 MySQL - 匹配 SQLite 格式(
:memory:、sqlite://、file://)为 SQLite - 其他默认使用 PostgreSQL
支持 MySQL 存储过程吗?
支持 MySQL 存储过程吗?
支持,包括 OUT 参数和多个结果集:
可以使用 MySQL 特有语法吗?
可以使用 MySQL 特有语法吗?
当然,可以使用所有 MySQL 专属语法:
为什么不直接用已有库?
你也可以在 Bun 中使用 postgres.js、pg 和 node-postgres 等 npm 包。它们都是很好的选择。 但有两个原因:- 我们认为,对于开发者来说,将数据库驱动内置到 Bun 中会更加简单。你花在挑选库上的时间,本可以用来构建应用。
- 我们利用了一些 JavaScriptCore 引擎内部机制来更快地创建对象,而这些机制很难在库中实现。