Skip to main content
该接口设计简洁高效,使用标签模板字面量进行查询,提供连接池、事务和预处理语句等功能。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79db.ts
import { sql, SQL } from "bun";

// PostgreSQL(默认)
const users = await sql`
  SELECT * FROM users
  WHERE active = ${true}
  LIMIT ${10}
`;

// 使用 MySQL
const mysql = new SQL("mysql://user:pass@localhost:3306/mydb");
const mysqlResults = await mysql`
  SELECT * FROM users 
  WHERE active = ${true}
`;

// 使用 SQLite
const sqlite = new SQL("sqlite://myapp.db");
const sqliteResults = await sqlite`
  SELECT * FROM users 
  WHERE active = ${1}
`;

功能特性

  • 使用标签模板字面量防止 SQL 注入
  • 事务支持
  • 命名和位置参数
  • 连接池
  • 支持 BigInt
  • 支持 SASL 认证(SCRAM-SHA-256)、MD5 和明文认证
  • 连接超时设置
  • 查询结果可作为数据对象、数组的数组或 Buffer 返回
  • 支持二进制协议,性能更高
  • TLS 支持(和认证模式)
  • 环境变量自动配置

数据库支持

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

PostgreSQL

PostgreSQL 适用于以下情况:
  • 连接字符串不符合 SQLite 或 MySQL 格式(作为默认适配器)
  • 明确使用 postgres://postgresql:// 协议的连接字符串
  • 无连接字符串,但环境变量指向 PostgreSQL
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79db.ts
import { sql } from "bun";
// 当未设置 DATABASE_URL 或为 PostgreSQL URL 时使用 PostgreSQL
await sql`SELECT ...`;

import { SQL } from "bun";
const pg = new SQL("postgres://user:pass@localhost:5432/mydb");
await pg`SELECT ...`;

MySQL

Bun.SQL 内置 MySQL 支持,使用相同的标签模板字面量接口,兼容 MySQL 5.7+ 和 MySQL 8.0+:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79db.ts
import { SQL } from "bun";

// MySQL 连接
const mysql = new SQL("mysql://user:password@localhost:3306/database");
const mysql2 = new SQL("mysql2://user:password@localhost:3306/database"); // mysql2 协议同样支持

// 使用选项对象
const mysql3 = new SQL({
  adapter: "mysql",
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",
});

// 支持参数 — 会自动使用预处理语句
const users = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 事务和 PostgreSQL 使用方法相同
await mysql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = ${userId}`;
});

// 批量插入
const newUsers = [
  { name: "Alice", email: "[email protected]" },
  { name: "Bob", email: "[email protected]" },
];
await mysql`INSERT INTO users ${mysql(newUsers)}`;
MySQL 支持多种 URL 格式作为连接字符串:
// 标准 mysql:// 协议
new SQL("mysql://user:pass@localhost:3306/database");
new SQL("mysql://user:pass@localhost/database"); // 默认端口 3306

// mysql2:// 协议(兼容 mysql2 npm 包)
new SQL("mysql2://user:pass@localhost:3306/database");

// 含查询参数
new SQL("mysql://user:pass@localhost/db?ssl=true");

// Unix 套接字连接
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
MySQL 数据库支持:
  • 预处理语句:参数化查询自动创建且缓存语句
  • 二进制协议:提升预处理语句性能并支持准确类型处理
  • 多结果集:支持存储过程返回多个结果集
  • 认证插件:支持 mysql_native_password、caching_sha2_password(MySQL 8.0 默认)和 sha256_password
  • SSL/TLS 连接:SSL 模式配置类似 PostgreSQL
  • 连接属性:客户端信息发送到服务器便于监控
  • 查询流水线:无需等待响应即可执行多个预处理语句

SQLite

Bun.SQL 内置 SQLite 支持,使用相同标签模板字面量接口:
import { SQL } from "bun";

// 内存数据库
const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");

// 文件数据库
const sql1 = new SQL("sqlite://myapp.db");

// 使用选项对象
const sql2 = new SQL({
  adapter: "sqlite",
  filename: "./data/app.db",
});

// 简单文件名必须明确告知适配器
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });
SQLite 支持多种 URL 格式作为连接字符串:
// 标准 sqlite:// 协议
new SQL("sqlite://path/to/database.db");
new SQL("sqlite:path/to/database.db"); // 无斜杠

// file:// 协议(也视为 SQLite)
new SQL("file://path/to/database.db");
new SQL("file:path/to/database.db");

// 特殊内存数据库
new SQL(":memory:");
new SQL("sqlite://:memory:");
new SQL("file://:memory:");

// 相对和绝对路径
new SQL("sqlite://./local.db"); // 相对于当前目录
new SQL("sqlite://../parent/db.db"); // 上级目录
new SQL("sqlite:///absolute/path.db"); // 绝对路径

// 带参数
new SQL("sqlite://data.db?mode=ro"); // 只读
new SQL("sqlite://data.db?mode=rw"); // 读写无创建
new SQL("sqlite://data.db?mode=rwc"); // 读写并允许创建(默认)
简单文件名(如 "myapp.db")必须显式设置 { adapter: "sqlite" },以避免和 PostgreSQL 混淆。
SQLite 支持额外配置选项:
const sql = new SQL({
  adapter: "sqlite",
  filename: "app.db",

  // SQLite 特定选项
  readonly: false, // 只读打开
  create: true, // 不存在则创建数据库
  readwrite: true, // 读写访问

  // 额外 Bun:sqlite 选项
  strict: true, // 开启严格模式
  safeIntegers: false, // 使用 JS 数字处理整数
});
URL 中的查询参数会映射到这些配置:
  • ?mode=roreadonly: true
  • ?mode=rwreadonly: false, create: false
  • ?mode=rwcreadonly: false, create: true(默认)

插入数据

你可以直接将 JavaScript 值传入 SQL 模板字面量,框架会自动帮你转义。
import { sql } from "bun";

// 基础插入,直接传值
const [user] = await sql`
  INSERT INTO users (name, email) 
  VALUES (${name}, ${email})
  RETURNING *
`;

// 使用对象辅助,语法更简洁
const userData = {
  name: "Alice",
  email: "[email protected]",
};

const [newUser] = await sql`
  INSERT INTO users ${sql(userData)}
  RETURNING *
`;
// 展开为:INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')

批量插入

你也可以传入对象数组,自动展开成 INSERT INTO ... VALUES ... 语句:
const users = [
  { name: "Alice", email: "[email protected]" },
  { name: "Bob", email: "[email protected]" },
  { name: "Charlie", email: "[email protected]" },
];

await sql`INSERT INTO users ${sql(users)}`;

选择插入列

可通过 sql(object, ...string) 指定插入哪些列,指定的列必须都在对象中:
const user = {
  name: "Alice",
  email: "[email protected]",
  age: 25,
};

await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// 仅插入 name 和 email 列,其余字段忽略

查询结果

默认情况下,Bun 的 SQL 客户端会返回以列名为键的对象数组,每个对象代表一行。若需要不同格式,客户端还支持另外两种方法返回结果。

sql``.values() 格式

.values() 返回二维数组,每行是值数组,顺序与查询列相同:
const rows = await sql`SELECT * FROM users`.values();
console.log(rows);
结果类似:
[
  ["Alice", "[email protected]"],
  ["Bob", "[email protected]"],
];
当查询中存在重复列名时,使用 .values() 特别有用;对象格式会覆盖前面同名列,而 .values() 会保留所有列,可通过下标访问。

sql``.raw() 格式

.raw() 返回 Buffer 类型数据的二维数组,适用于处理二进制数据或追求性能场景:
const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]

SQL 片段

数据库应用常需要根据运行时条件动态构建查询,Bun 提供安全构造方法防止 SQL 注入。

动态表名

需要动态引用表或模式名时,使用 sql() 辅助以正确转义:
// 安全动态引用表名
await sql`SELECT * FROM ${sql("users")}`;

// 带模式限定
await sql`SELECT * FROM ${sql("public.users")}`;

条件查询

使用 sql() 动态构造条件子句,实现灵活查询:
// 可选 WHERE 子句
const filterAge = true;
const minAge = 21;
const ageFilter = sql`AND age > ${minAge}`;
await sql`
  SELECT * FROM users
  WHERE active = ${true}
  ${filterAge ? ageFilter : sql``}
`;

更新时动态列

sql(object, ...string) 可指定更新哪些列,指定的列必须在对象内。若不指定列,将使用对象所有键更新:
await sql`UPDATE users SET ${sql(user, "name", "email")} WHERE id = ${user.id}`;
// 使用对象所有键更新
await sql`UPDATE users SET ${sql(user)} WHERE id = ${user.id}`;

动态值和 WHERE IN

值列表也可动态创建,方便构造 WHERE IN 查询。也可传对象数组并指定键名:
await sql`SELECT * FROM users WHERE id IN ${sql([1, 2, 3])}`;

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
  { id: 3, name: "Charlie" },
];
await sql`SELECT * FROM users WHERE id IN ${sql(users, "id")}`;

sql.array 辅助

sql.array 用于将 JS 数组转换为 PostgreSQL 数组字面量:
// PostgreSQL 数组字面量
await sql`INSERT INTO tags (items) VALUES (${sql.array(["red", "blue", "green"])})`;
// 生成:INSERT INTO tags (items) VALUES (ARRAY['red', 'blue', 'green'])

// 也适用于数值数组
await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// 生成:SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])
sql.array 仅支持 PostgreSQL,多维数组和 NULL 元素可能暂不支持。

sql``.simple()

PostgreSQL 协议支持两类查询:“简单”和“扩展”查询。简单查询支持多条语句但不支持参数,扩展查询支持参数但只能一条语句。 要在一次查询中执行多条语句,使用 sql``.simple()
// 一次执行多条语句
await sql`
  SELECT 1;
  SELECT 2;
`.simple();
简单查询常用于数据库迁移和初始化脚本。 注意,简单查询不支持参数(${value}),如需参数,必须拆分查询语句。

文件查询

可以用 sql.file 读取文件执行查询,文件中如果含有 11、2 等参数,可传入参数数组。若无参数,文件可包含多条命令:
const result = await sql.file("query.sql", [1, 2, 3]);

不安全查询

sql.unsafe 执行原始 SQL 字符串。此方法不做转义,须谨慎使用。无参数时允许多条命令执行。
// 多条命令无参数
const result = await sql.unsafe(`
  SELECT ${userColumns} FROM users;
  SELECT ${accountColumns} FROM accounts;
`);

// 带参数(只支持单条命令)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);

执行和取消查询

Bun 的 SQL 操作是惰性的,仅在调用 await.execute() 时报真正执行。 你可以调用查询对象的 cancel() 方法取消当前执行的查询:
const query = sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;

数据库环境变量

sql 连接参数可通过环境变量配置,客户端按优先级检测变量并根据连接字符串格式自动识别数据库类型。

自动数据库检测

使用 Bun.sql() 无参或 new SQL() 传连接字符串时,适配器自动根据 URL 格式检测:

MySQL 自动检测

连接字符串匹配以下格式时自动使用 MySQL:
  • mysql://... — MySQL 协议
  • mysql2://... — MySQL2 协议(兼容别名)
// 以下均自动使用 MySQL(无需指定适配器)
const sql1 = new SQL("mysql://user:pass@localhost/mydb");
const sql2 = new SQL("mysql2://user:pass@localhost:3306/mydb");

// 也支持通过环境变量 DATABASE_URL
DATABASE_URL="mysql://user:pass@localhost/mydb" bun run app.js
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb" bun run app.js

SQLite 自动检测

连接字符串匹配以下格式时自动使用 SQLite:
  • :memory: — 内存数据库
  • sqlite://... — SQLite 协议
  • sqlite:... — 无斜杠的 SQLite 协议
  • file://... — 文件协议
  • file:... — 无斜杠的文件协议
// 以下均自动使用 SQLite
const sql1 = new SQL(":memory:");
const sql2 = new SQL("sqlite://app.db");
const sql3 = new SQL("file://./database.db");

// 也支持环境变量 DATABASE_URL
DATABASE_URL=":memory:" bun run app.js
DATABASE_URL="sqlite://myapp.db" bun run app.js
DATABASE_URL="file://./data/app.db" bun run app.js

PostgreSQL 自动检测

不匹配 MySQL 或 SQLite 格式时,默认使用 PostgreSQL:
# PostgreSQL 检测示例
DATABASE_URL="postgres://user:pass@localhost:5432/mydb" bun run app.js
DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" bun run app.js

# 或者任何其他不匹配 MySQL 和 SQLite 的 URL
DATABASE_URL="localhost:5432/mydb" bun run app.js

MySQL 环境变量

MySQL 连接可通过环境变量配置:
# 优先使用的连接 URL
MYSQL_URL="mysql://user:pass@localhost:3306/mydb"

# 兼容 DATABASE_URL 格式(MySQL 协议)
DATABASE_URL="mysql://user:pass@localhost:3306/mydb"
DATABASE_URL="mysql2://user:pass@localhost:3306/mydb"
无连接 URL 时,MySQL 支持如下独立参数:
环境变量默认值说明
MYSQL_HOSTlocalhost数据库主机
MYSQL_PORT3306端口
MYSQL_USERroot用户名
MYSQL_PASSWORD空字符串密码
MYSQL_DATABASEmysql数据库名
MYSQL_URL空字符串MySQL 主要连接 URL
TLS_MYSQL_DATABASE_URL空字符串启用 SSL/TLS 的连接 URL

PostgreSQL 环境变量

以下变量可用于定义 PostgreSQL 连接:
环境变量说明
POSTGRES_URLPostgreSQL 主要连接 URL
DATABASE_URL兼容的另一个连接 URL
PGURL兼容连接 URL
PG_URL兼容连接 URL
TLS_POSTGRES_DATABASE_URL启用 SSL/TLS 的连接 URL
TLS_DATABASE_URL另一个启用 SSL/TLS 的连接 URL
无 URL 时,检测以下单独参数:
环境变量备用变量默认值说明
PGHOST-localhost主机
PGPORT-5432端口
PGUSERNAMEPGUSER, USER, USERNAMEpostgres用户名
PGPASSWORD-空字符串密码
PGDATABASE-当前用户名数据库名

SQLite 环境变量

当连接字符串是 SQLite 兼容时,使用 DATABASE_URL 配置:
# SQLite 连接示例
DATABASE_URL=":memory:"
DATABASE_URL="sqlite://./app.db"
DATABASE_URL="file:///absolute/path/to/db.sqlite"
注意: 使用 SQLite 时,会忽略 PostgreSQL 专用环境变量(如 POSTGRES_URLPGHOST 等)。

运行时预连接

Bun 可在启动时预先连接 PostgreSQL,减少首次查询的连接延迟,提高性能。
# 启用 PostgreSQL 预连接
bun --sql-preconnect index.js

# 配合 DATABASE_URL 环境变量使用
DATABASE_URL=postgres://user:pass@localhost:5432/db bun --sql-preconnect index.js

# 可与其他运行时参数结合
bun --sql-preconnect --hot index.js
--sql-preconnect 会自动使用环境变量配置建立连接。连接失败不会导致程序崩溃,错误会被安全处理。

连接选项

可以通过传入参数手动配置数据库连接。选项根据适配器不同而异:

MySQL 选项

import { SQL } from "bun";

const sql = new SQL({
  // MySQL 必须指定
  adapter: "mysql",

  // 连接配置
  hostname: "localhost",
  port: 3306,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // Unix 套接字连接(替代 hostname/port)
  // socket: "/var/run/mysqld/mysqld.sock",

  // 连接池设置
  max: 20, // 最大连接数(默认10)
  idleTimeout: 30, // 空闲连接 30 秒后关闭
  maxLifetime: 0, // 连接最大存活时间(秒),0 表示无限
  connectionTimeout: 30, // 建立连接超时(秒)

  // SSL/TLS 选项
  ssl: "prefer", // 可设 "disable", "require", "verify-ca", "verify-full"
  // tls: {
  //   rejectUnauthorized: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  // },

  // 回调函数
  onconnect: client => {
    console.log("已连接到 MySQL");
  },
  onclose: (client, err) => {
    if (err) {
      console.error("MySQL 连接错误:", err);
    } else {
      console.log("MySQL 连接关闭");
    }
  },
});

PostgreSQL 选项

import { SQL } from "bun";

const sql = new SQL({
  // 连接配置(适配器自动默认为 PostgreSQL)
  url: "postgres://user:pass@localhost:5432/dbname",

  // 可替代配置
  hostname: "localhost",
  port: 5432,
  database: "myapp",
  username: "dbuser",
  password: "secretpass",

  // 连接池设置
  max: 20, // 最大连接数
  idleTimeout: 30, // 空闲连接 30 秒关闭
  maxLifetime: 0, // 连接最大存活时间,0 表示无限
  connectionTimeout: 30, // 建立连接超时

  // SSL/TLS 选项
  tls: true,
  // tls: {
  //   rejectUnauthorized: true,
  //   requestCert: true,
  //   ca: "path/to/ca.pem",
  //   key: "path/to/key.pem",
  //   cert: "path/to/cert.pem",
  //   checkServerIdentity(hostname, cert) {
  //     ...
  //   },
  // },

  // 回调函数
  onconnect: client => {
    console.log("已连接到 PostgreSQL");
  },
  onclose: client => {
    console.log("PostgreSQL 连接关闭");
  },
});

SQLite 选项

import { SQL } from "bun";

const sql = new SQL({
  // 必须指定适配器
  adapter: "sqlite",
  filename: "./data/app.db", // 或 ":memory:" 表示内存数据库

  // SQLite 文件访问模式
  readonly: false, // 只读打开
  create: true, // 不存在则创建
  readwrite: true, // 支持读写

  // SQLite 数据处理设置
  strict: true, // 开启严格模式提升类型安全
  safeIntegers: false, // 超出 JS 数字范围的整数使用 BigInt

  // 回调函数
  onconnect: client => {
    console.log("SQLite 数据库已打开");
  },
  onclose: client => {
    console.log("SQLite 数据库已关闭");
  },
});
  • 连接池:SQLite 是文件型数据库,不使用连接池,每个 SQL 实例对应单一连接。
  • 事务:SQLite 支持通过保存点实现嵌套事务,类似 PostgreSQL。
  • 并发访问:SQLite 通过文件锁控制并发,建议使用 WAL 日志模式以提升并发能力。
  • 内存数据库:memory: 创建临时数据库,仅存活于连接生命周期内。

动态密码

当客户端需要用访问令牌或轮换密码等动态认证时,可提供同步或异步函数动态获取密码:
import { SQL } from "bun";

const sql = new SQL(url, {
  // 其他连接配置
  ...
  // 用于获取数据库用户密码的函数
  password: async () => await signer.getAuthToken(),
});

SQLite 特有功能

查询执行

SQLite 查询是同步执行的,但 API 依然返回 Promise 保持一致:
const sqlite = new SQL("sqlite://app.db");

// 使用体验和 PostgreSQL 一样,但底层同步执行
const users = await sqlite`SELECT * FROM users`;

// 参数传递方式一致
const user = await sqlite`SELECT * FROM users WHERE id = ${userId}`;

SQLite PRAGMA 语句

可使用 PRAGMA 配置 SQLite 行为:
const sqlite = new SQL("sqlite://app.db");

// 启用外键支持
await sqlite`PRAGMA foreign_keys = ON`;

// 设置 WAL 日志模式提升并发
await sqlite`PRAGMA journal_mode = WAL`;

// 校验数据库完整性
const integrity = await sqlite`PRAGMA integrity_check`;

数据类型差异

SQLite 类型系统比 PostgreSQL 灵活:
// SQLite 存储 5 种基础类型:NULL、INTEGER、REAL、TEXT 和 BLOB
const sqlite = new SQL("sqlite://app.db");

// SQLite 类型宽松
await sqlite`
  CREATE TABLE flexible (
    id INTEGER PRIMARY KEY,
    data TEXT,        -- 数字能存为字符串
    value NUMERIC,    -- 可以是整数、浮点或文本
    blob BLOB         -- 二进制数据
  )
`;

// JS 值会自动转换
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;

事务

sql.begin 启动事务,支持 PostgreSQL 和 SQLite。PostgreSQL 会从连接池中保留连接,SQLite 则在单一连接上开始事务。 开始时自动发送 BEGIN 命令,支持传入可选参数。出现错误会自动回退(ROLLBACK),保证程序继续执行。

基本事务

await sql.begin(async tx => {
  // 该函数内所有查询都运行于事务中
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
  await tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`;

  // 没有抛错则自动提交
  // 抛错则回滚
});
通过返回查询数组,也可以让事务实现流水线执行:
await sql.begin(async tx => {
  return [
    tx`INSERT INTO users (name) VALUES (${"Alice"})`,
    tx`UPDATE accounts SET balance = balance - 100 WHERE user_id = 1`,
  ];
});

保存点(Savepoints)

保存点允许在事务中设置检查点,支持部分回滚,不影响整体事务。适合复杂事务。
await sql.begin(async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;

  await tx.savepoint(async sp => {
    // 此处段落可独立回滚
    await sp`UPDATE users SET status = 'active'`;
    if (someCondition) {
      throw new Error("Rollback to savepoint");
    }
  });

  // 保存点回滚后继续事务
  await tx`INSERT INTO audit_log (action) VALUES ('user_created')`;
});

分布式事务

两阶段提交(2PC)协议允许协调多个节点,保证跨节点持久化数据一致性和锁管理。 PostgreSQL 和 MySQL 支持分布式事务,可供管理员或协调者后续提交或回滚。 如果分布式事务中有未捕获异常,系统会自动回滚。正常时,可以延迟提交或回滚。
// 开始分布式事务
await sql.beginDistributed("tx1", async tx => {
  await tx`INSERT INTO users (name) VALUES (${"Alice"})`;
});

// 后续提交或回滚
await sql.commitDistributed("tx1");
// 或
await sql.rollbackDistributed("tx1");

认证

Bun 支持 SCRAM-SHA-256(SASL)、MD5 和明文认证。推荐使用 SASL 增强安全性。详见 Postgres SASL 认证

SSL 模式概述

PostgreSQL 支持多种 SSL/TLS 模式,决定连接时安全策略和证书验证级别。
const sql = new SQL({
  hostname: "localhost",
  username: "user",
  password: "password",
  ssl: "disable", // 可选 "prefer" | "require" | "verify-ca" | "verify-full"
});
SSL 模式说明
disable不使用 SSL/TLS。如服务端要求 SSL,连接将失败
prefer优先使用 SSL,失败则降级为非 SSL。默认模式
require必须启用 SSL,但不验证证书。无法建立 SSL 连接则失败
verify-ca验证服务器证书由受信任 CA 签发,验证失败则失败
verify-full最安全模式,验证证书且验证域名匹配,防止恶意证书和中间人攻击

连接字符串中设置

SSL 模式也可直接在连接字符串中指定:
// prefer 模式
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");

// verify-full 模式
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");

连接池

Bun SQL 自动管理连接池,复用连接执行多条查询,减少建立关闭连接开销,且管理并发连接数。
const sql = new SQL({
  max: 20, // 最大连接数 20
  idleTimeout: 30, // 空闲 30 秒关闭连接
  maxLifetime: 3600, // 连接最大存活 1 小时
  connectionTimeout: 10, // 连接超时 10 秒
});
在第一次查询时连接池才会启动:
const sql = Bun.SQL(); // 未连接

await sql`...`; // 连接池启动,最多创建 max 连接,复用空闲连接
await sql`...`; // 复用已有连接

// 并行使用多个连接
await Promise.all([
  sql`INSERT INTO users ${sql({ name: "Alice" })}`,
  sql`UPDATE users SET name = ${user.name} WHERE id = ${user.id}`,
]);

await sql.close(); // 等待所有查询完成并关闭连接池
await sql.close({ timeout: 5 }); // 最多等待 5 秒关闭
await sql.close({ timeout: 0 }); // 立即关闭所有连接

保留连接

Bun 允许保留连接池中的单个连接,返回包裹该连接的客户端,用于执行隔离查询。
// 保留独占连接
const reserved = await sql.reserve();

try {
  await reserved`INSERT INTO users (name) VALUES (${"Alice"})`;
} finally {
  // 必须释放连接回池
  reserved.release();
}

// 或使用 Symbol.dispose 自动释放
{
  using reserved = await sql.reserve();
  await reserved`SELECT 1`;
} // 自动释放

预处理语句

默认情况下,Bun 会自动为可判断为静态的查询创建命名预处理语句,提高性能。你可以在连接配置中设置 prepare: false 关闭此行为:
const sql = new SQL({
  // 其他配置...
  prepare: false, // 禁用服务端持久命名预处理语句
});
设置 prepare: false 后:
  • 查询仍使用扩展协议,但使用未命名预处理语句,未命名预处理语句只存续到下一条解析未命名语句之前。
  • 参数绑定仍然防止 SQL 注入
  • 服务器每次都重新解析和规划查询
  • 不支持查询流水线执行
应用场景:
  • 使用 PGBouncer 事务模式(1.21.0 以后的版本如果配置正确已支持命名语句)
  • 调试查询执行计划
  • 动态 SQL 需频繁重新规划查询
  • 不支持多条语句(除非用 .simple()
禁止预处理语句会导致频繁执行时性能下降,因为服务器每次都要重新解析查询。

错误处理

客户端提供数据库特定的错误类型,继承自基础错误类:

错误类示例

import { SQL } from "bun";

try {
  await sql`SELECT * FROM users`;
} catch (error) {
  if (error instanceof SQL.PostgresError) {
    // PostgreSQL 专用错误
    console.log(error.code); // PostgreSQL 错误码
    console.log(error.detail); // 详细错误信息
    console.log(error.hint); // 提示
  } else if (error instanceof SQL.SQLiteError) {
    // SQLite 专用错误
    console.log(error.code); // SQLite 错误码(如 "SQLITE_CONSTRAINT")
    console.log(error.errno); // SQLite 错误号
    console.log(error.byteOffset); // SQL 语句中的字节偏移(若有)
  } else if (error instanceof SQL.SQLError) {
    // 通用 SQL 错误(基类)
    console.log(error.message);
  }
}

PostgreSQL 连接错误

错误码说明
ERR_POSTGRES_CONNECTION_CLOSED连接被关闭或未建立
ERR_POSTGRES_CONNECTION_TIMEOUT连接超时未建立
ERR_POSTGRES_IDLE_TIMEOUT因空闲超时断开连接
ERR_POSTGRES_LIFETIME_TIMEOUT连接存活时间超长
ERR_POSTGRES_TLS_NOT_AVAILABLESSL/TLS 不可用
ERR_POSTGRES_TLS_UPGRADE_FAILEDSSL/TLS 升级失败

认证错误

错误码说明
ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2密码认证失败
ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD服务器请求未知认证方法
ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD服务器请求不支持的认证方式
ERR_POSTGRES_INVALID_SERVER_KEY认证时服务器密钥无效
ERR_POSTGRES_INVALID_SERVER_SIGNATURE服务器签名无效
ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64SASL 签名 Base64 编码错误
ERR_POSTGRES_SASL_SIGNATURE_MISMATCHSASL 签名验证失败

查询错误

错误码说明
ERR_POSTGRES_SYNTAX_ERRORSQL 语法错误(继承自 SyntaxError)
ERR_POSTGRES_SERVER_ERRORPostgreSQL 服务器中的一般错误
ERR_POSTGRES_INVALID_QUERY_BINDING参数绑定错误
ERR_POSTGRES_QUERY_CANCELLED查询被取消
ERR_POSTGRES_NOT_TAGGED_CALL查询未使用标签调用

数据类型错误

错误码说明
ERR_POSTGRES_INVALID_BINARY_DATA二进制数据格式无效
ERR_POSTGRES_INVALID_BYTE_SEQUENCE字节序列无效
ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING编码错误
ERR_POSTGRES_INVALID_CHARACTER无效字符
ERR_POSTGRES_OVERFLOW数值溢出
ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT不支持的二进制格式
ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE整数大小不支持
ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET不支持多维数组
ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET不支持数组中有 NULL

协议错误

错误码说明
ERR_POSTGRES_EXPECTED_REQUEST期望客户端请求
ERR_POSTGRES_EXPECTED_STATEMENT期望预处理语句
ERR_POSTGRES_INVALID_BACKEND_KEY_DATA后端键数据无效
ERR_POSTGRES_INVALID_MESSAGE协议消息无效
ERR_POSTGRES_INVALID_MESSAGE_LENGTH消息长度无效
ERR_POSTGRES_UNEXPECTED_MESSAGE意外消息类型

事务错误

错误码说明
ERR_POSTGRES_UNSAFE_TRANSACTION发现不安全的事务操作
ERR_POSTGRES_INVALID_TRANSACTION_STATE事务状态非法

SQLite 特有错误

SQLite 错误包含错误码和错误编号,参考 SQLite 标准错误码:
错误码errno说明
SQLITE_CONSTRAINT19约束冲突(唯一、检查、非空等)
SQLITE_BUSY5数据库正被锁定
SQLITE_LOCKED6表被锁
SQLITE_READONLY8试图写入只读数据库
SQLITE_IOERR10磁盘 I/O 错误
SQLITE_CORRUPT11数据库文件损坏
SQLITE_FULL13磁盘空间或数据库满
SQLITE_CANTOPEN14无法打开数据库文件
SQLITE_PROTOCOL15数据库锁协议错误
SQLITE_SCHEMA17数据库结构改变
SQLITE_TOOBIG18字符串或 BLOB 超长
SQLITE_MISMATCH20数据类型不匹配
SQLITE_MISUSE21错误使用 SQLite 库
SQLITE_AUTH23访问权限拒绝
错误处理示例:
const sqlite = new SQL("sqlite://app.db");

try {
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Alice')`;
  await sqlite`INSERT INTO users (id, name) VALUES (1, 'Bob')`; // 主键重复
} catch (error) {
  if (error instanceof SQL.SQLiteError) {
    if (error.code === "SQLITE_CONSTRAINT") {
      console.log("约束冲突:", error.message);
      // 处理唯一约束冲突
    }
  }
}

数字与 BigInt

Bun SQL 客户端特别处理超出 53 位整数范围的大数值。示例如下:
import { sql } from "bun";

const [{ x, y }] = await sql`SELECT 9223372036854777 as x, 12345 as y`;

console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345

使用 BigInt 代替字符串

如需返回大整数为 BigInt 类型,可初始化时开启 bigint 选项:
const sql = new SQL({
  bigint: true,
});

const [{ x }] = await sql`SELECT 9223372036854777 as x`;

console.log(typeof x, x); // "bigint" 9223372036854777n

路线图

尚未完成的功能:
  • 通过 Bun CLI --db-preconnect 标志实现连接预加载
  • 列名转换(比如 snake_casecamelCase),受限于 C++ 中 WebKit WTF::String 对 unicode 字符转换支持
  • 列类型转换

数据库特有功能

认证方式

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

预处理语句与性能

MySQL 对所有参数化查询使用服务端预处理语句:
// 自动创建服务端预处理语句
const user = await mysql`SELECT * FROM users WHERE id = ${userId}`;

// 预处理语句缓存并重复使用
for (const id of userIds) {
  await mysql`SELECT * FROM users WHERE id = ${id}`; // 重用同一预处理语句
}

// 查询流水线 - 并行发送多个语句
const [users, orders, products] = await Promise.all([
  mysql`SELECT * FROM users WHERE active = ${true}`,
  mysql`SELECT * FROM orders WHERE status = ${"pending"}`,
  mysql`SELECT * FROM products WHERE in_stock = ${true}`,
]);

多结果集

MySQL 支持多语句查询返回多个结果集:
const mysql = new SQL("mysql://user:pass@localhost/mydb");

// 使用 simple() 多语句查询
const multiResults = await mysql`
  SELECT * FROM users WHERE id = 1;
  SELECT * FROM orders WHERE user_id = 1;
`.simple();

字符集与排序规则

Bun.SQL 自动使用 MySQL utf8mb4 字符集,支持完整 Unicode(含 Emoji),为现代应用推荐。

连接属性

Bun 自动向 MySQL 发送客户端信息,方便监控:
// 自动发送以下属性:
// _client_name: "Bun"
// _client_version: <bun 版本号>
// 可以在 MySQL performance_schema.session_connect_attrs 中查看

类型处理

MySQL 类型映射到 JS 类型如下:
MySQL 类型JS 类型备注
INT、TINYINT、MEDIUMINTnumber在安全整数范围内
BIGINTstring、number 或 BigInt在 i32/u32 范围内为 number,否则为 string 或 BigInt,视 bigint 选项而定
DECIMAL、NUMERICstring保持精度
FLOAT、DOUBLEnumber
DATEDateJS Date 对象
DATETIME、TIMESTAMPDate支持时区处理
TIMEnumber以微秒计数
YEARnumber
CHAR、VARCHAR、VARSTRING、STRINGstring
TINYTEXT、MEDIUMTEXT、TEXT、LONGTEXTstring
TINYBLOB、MEDIUMBLOB、BLOB、LONGBLOBstringBLOB 等价于 TEXT 类型
JSONobject/array自动解析
BIT(1)booleanMySQL 中的 BIT(1) 类型
GEOMETRYstring几何数据

与 PostgreSQL 差异

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

MySQL 特有功能

暂未实现 LOAD DATA INFILE 支持。

PostgreSQL 特有功能

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

常见模式与最佳实践

处理 MySQL 结果集

// 获取插入 ID
const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL LAST_INSERT_ID()

// 受影响的行数
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // 更新行数

// 使用 MySQL 专用函数
const now = await mysql`SELECT NOW() as current_time`;
const uuid = await mysql`SELECT UUID() as id`;

MySQL 错误处理

try {
  await mysql`INSERT INTO users (email) VALUES (${"[email protected]"})`;
} catch (error) {
  if (error.code === "ER_DUP_ENTRY") {
    console.log("检测到重复条目");
  } else if (error.code === "ER_ACCESS_DENIED_ERROR") {
    console.log("访问被拒绝");
  } else if (error.code === "ER_BAD_DB_ERROR") {
    console.log("数据库不存在");
  }
  // MySQL 错误码与 mysql/mysql2 包兼容
}

MySQL 性能优化建议

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

常见问题

计划支持更多数据库驱动。现在已支持 MySQL,统一 API 支持 PostgreSQL、MySQL 和 SQLite。
适配器根据连接字符串自动识别:
  • mysql://mysql2:// 开头为 MySQL
  • 匹配 SQLite 格式(:memory:sqlite://file://)为 SQLite
  • 其他默认使用 PostgreSQL
支持,包含 OUT 参数和多结果集:
// 调用存储过程
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;

// 获取 OUT 参数
const outParam = await mysql`SELECT @total_orders as total`;
当然,可以使用所有 MySQL 专属语法:
// MySQL 特殊语法示例
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;

为什么不直接用已有库?

npm 包如 postgres.js、pg 和 node-postgres 也可用于 Bun,都是不错的选择。 但有两个原因:
  1. 我们认为集成内置数据库驱动对开发者更简单,减少寻找库的时间,让开发更高效。
  2. 我们利用 JavaScriptCore 引擎内部特性,实现更快的对象创建,这在普通库中难以做到。

鸣谢

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