Bun

SQLite

Bun 本地实现了一个高性能的 SQLite3 驱动程序。要使用它,请从内置的 bun:sqlite 模块导入。

import { Database } from "bun:sqlite";

const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get(); // => { message: "Hello world" }

API 简单、同步且快速。感谢 better-sqlite3 及其贡献者为 bun:sqlite 的 API 提供灵感。

功能包括

  • 事务
  • 参数(命名和位置)
  • 预处理语句
  • 数据类型转换(BLOB 变成 Uint8Array
  • 所有 JavaScript SQLite 驱动程序中最快的性能

对于读取查询,bun:sqlite 模块比 better-sqlite3 快约 3-6 倍,比 deno.land/x/sqlite 快约 8-9 倍。每个驱动程序都针对 Northwind Traders 数据集进行了基准测试。查看并运行 基准源

SQLite benchmarks for Bun, better-sqlite3, and deno.land/x/sqlite
在运行 macOS 12.3.1 的 M1 MacBook Pro(64GB)上进行基准测试

数据库

要打开或创建 SQLite3 数据库

import { Database } from "bun:sqlite";

const db = new Database("mydb.sqlite");

打开内存数据库

import { Database } from "bun:sqlite";

// all of these do the same thing
const db = new Database(":memory:");
const db = new Database();
const db = new Database("");

只读模式打开

import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });

如果文件不存在,则创建数据库

import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });

通过 ES 模块导入加载

您还可以使用 import 属性来加载数据库。

import db from "./mydb.sqlite" with { "type": "sqlite" };

console.log(db.query("select * from users LIMIT 1").get());

这等效于以下内容

import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");

.close(throwOnError: boolean = false)

要关闭数据库连接,但允许现有查询完成,请调用 .close(false)

const db = new Database();
// ... do stuff
db.close(false);

要关闭数据库并在有任何待处理查询时抛出错误,请调用 .close(true)

const db = new Database();
// ... do stuff
db.close(true);

注意:当数据库被垃圾回收时,会自动调用 close(false)。多次调用是安全的,但在第一次之后没有效果。

using 语句

您可以使用 using 语句来确保在退出 using 块时关闭数据库连接。

import { Database } from "bun:sqlite";

{
  using db = new Database("mydb.sqlite");
  using query = db.query("select 'Hello world' as message;");
  console.log(query.get()); // => { message: "Hello world" }
}

.serialize()

bun:sqlite 支持 SQLite 的内置机制,用于将数据库序列化反序列化到内存中和从内存中。

const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);

在内部,.serialize() 调用sqlite3_serialize

.query()

在您的 Database 实例上使用 db.query() 方法来准备SQL 查询。结果是一个 Statement 实例,它将缓存在 Database 实例上。查询不会被执行。

const query = db.query(`select "Hello world" as message`);

注意 — 使用 .prepare() 方法准备查询,而不将其缓存在 Database 实例上。

// compile the prepared statement
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");

WAL 模式

SQLite 支持 预写式日志模式 (WAL),它显著提升了性能,尤其是在并发写入较多的情况下。对于大多数典型应用,强烈建议启用 WAL 模式。

若要启用 WAL 模式,请在应用程序开始时运行此 pragma 查询

db.exec("PRAGMA journal_mode = WAL;");

什么是 WAL 模式

语句

Statement 是一个已准备好的查询,这意味着它已被解析并编译成高效的二进制形式。它可以以高性能的方式多次执行。

使用 Database 实例上的 .query 方法创建语句。

const query = db.query(`select "Hello world" as message`);

查询可以包含参数。这些参数可以是数字(?1)或命名($param:param@param)。

const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);

在执行查询时,值将绑定到这些参数。Statement 可以使用多种不同的方法执行,每种方法以不同的形式返回结果。

.all()

使用 .all() 运行查询并以对象数组的形式获取结果。

const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
// => [{ message: "Hello world" }]

在内部,它调用 sqlite3_reset 并重复调用 sqlite3_step,直到它返回 SQLITE_DONE

.get()

使用 .get() 运行查询并以对象的形式获取第一个结果。

const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });
// => { $message: "Hello world" }

在内部,它调用 sqlite3_reset,然后调用 sqlite3_step,直到它不再返回 SQLITE_ROW。如果查询没有返回任何行,则返回 undefined

.run()

使用 .run() 运行查询并返回 undefined。这对于模式修改查询(例如 CREATE TABLE)或批量写入操作很有用。

const query = db.query(`create table foo;`);
query.run();
// => undefined

在内部,这会调用 sqlite3_reset 并调用 sqlite3_step 一次。当您不关心结果时,无需遍历所有行。

.values()

使用 values() 运行查询并将所有结果作为数组数组返回。

const query = db.query(`select $message;`);
query.values({ $message: "Hello world" });

query.values(2);
// [
//   [ "Iron Man", 2008 ],
//   [ "The Avengers", 2012 ],
//   [ "Ant-Man: Quantumania", 2023 ],
// ]

在内部,它调用 sqlite3_reset 并重复调用 sqlite3_step,直到它返回 SQLITE_DONE

.finalize()

使用 .finalize() 销毁 Statement 并释放与之关联的任何资源。一旦完成,Statement 就无法再次执行。通常,垃圾回收器会为您执行此操作,但在对性能敏感的应用程序中,显式完成可能很有用。

const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();

.toString()

Statement 实例调用 toString() 会打印展开的 SQL 查询。这对于调试很有用。

import { Database } from "bun:sqlite";

// setup
const query = db.query("SELECT $param;");

console.log(query.toString()); // => "SELECT NULL"

query.run(42);
console.log(query.toString()); // => "SELECT 42"

query.run(365);
console.log(query.toString()); // => "SELECT 365"

在内部,这会调用 sqlite3_expanded_sql。使用最近绑定的值展开参数。

参数

查询可以包含参数。它们可以是数字(?1)或命名($param:param@param)。在执行查询时将值绑定到这些参数

查询
结果
查询
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
  $bar: "bar",
});
结果
[
  { "$bar": "bar" }
]

带编号(位置)的参数也适用

查询
结果
查询
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");
结果
[
  {
    "?1": "hello",
    "?2": "goodbye"
  }
]

事务

事务是一种以原子方式执行多个查询的机制;也就是说,所有查询都成功或都不成功。使用 db.transaction() 方法创建事务

const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insertCat.run(cat);
});

在此阶段,我们还没有插入任何猫!对 db.transaction() 的调用返回一个新函数(insertCats),该函数包装执行查询的函数。

要执行事务,请调用此函数。所有参数都将传递给包装函数;包装函数的返回值将由事务函数返回。包装函数还可以访问在执行事务时定义的 this 上下文。

const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
  for (const cat of cats) insert.run(cat);
  return cats.length;
});

const count = insertCats([
  { $name: "Keanu" },
  { $name: "Salem" },
  { $name: "Crookshanks" },
]);

console.log(`Inserted ${count} cats`);

当调用 insertCats 时,驱动程序将自动begin一个事务,并在包装函数返回时commit它。如果抛出异常,事务将回滚。异常将按通常方式传播;它不会被捕获。

嵌套事务 — 事务函数可以从其他事务函数内部调用。这样做时,内部事务将变为保存点

查看嵌套事务示例

事务还带有 deferredimmediateexclusive 版本。

insertCats(cats); // uses "BEGIN"
insertCats.deferred(cats); // uses "BEGIN DEFERRED"
insertCats.immediate(cats); // uses "BEGIN IMMEDIATE"
insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"

.loadExtension()

要加载SQLite 扩展,请在 Database 实例上调用 .loadExtension(name)

import { Database } from "bun:sqlite";

const db = new Database();
db.loadExtension("myext");

适用于 macOS 用户

.fileControl(cmd: number, value: any)

要使用高级 sqlite3_file_control API,请在 Database 实例上调用 .fileControl(cmd, value)

import { Database, constants } from "bun:sqlite";

const db = new Database();
// Ensure WAL mode is NOT persistent
// this prevents wal files from lingering after the database is closed
db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);

value 可以是

  • 数字
  • TypedArray
  • undefinednull

参考

class Database {
  constructor(
    filename: string,
    options?:
      | number
      | {
          readonly?: boolean;
          create?: boolean;
          readwrite?: boolean;
        },
  );

  query<Params, ReturnType>(sql: string): Statement<Params, ReturnType>;
}

class Statement<Params, ReturnType> {
  all(params: Params): ReturnType[];
  get(params: Params): ReturnType | undefined;
  run(params: Params): void;
  values(params: Params): unknown[][];

  finalize(): void; // destroy statement and clean up resources
  toString(): string; // serialize to SQL

  columnNames: string[]; // the column names of the result set
  paramsCount: number; // the number of parameters expected by the statement
  native: any; // the native object representing the statement
}

type SQLQueryBindings =
  | string
  | bigint
  | TypedArray
  | number
  | boolean
  | null
  | Record<string, string | bigint | TypedArray | number | boolean | null>;

数据类型

JavaScript 类型SQLite 类型
stringTEXT
数字INTEGERDECIMAL
booleanINTEGER(1 或 0)
Uint8ArrayBLOB
BufferBLOB
bigintINTEGER
nullNULL