使用 Turso Database for JavaScript:进程内 SQLite 兼容数据库的安装、查询、事务与 WebAssembly 实践
【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso
@tursodatabase/database是 Turso 官方提供的 JavaScript 数据库绑定库,它把用 Rust 编写的 SQLite 兼容数据库引擎直接嵌入 Node.js 进程,无需网络开销即可创建内存或文件型数据库。读完本文,你将掌握该包的安装方式、内存/文件数据库的建表与增删改查、基于transactionAsync的原子事务、batch批量执行、查询超时控制、连接选项(含加密与实验特性)以及浏览器 WebAssembly 用法,并了解其底层异步步进执行模型。
一、Turso Database for JavaScript 是什么
@tursodatabase/database是 Turso 的进程内(in-process)JavaScript 数据库库。与需要连接远程服务的驱动不同,它把数据库引擎直接加载进你的 Node.js 进程,SQL 执行不经过任何网络层。官方 README 列举的核心特性如下:
- SQLite 兼容:支持 SQLite 查询语言与文件格式,兼容性状态可参考仓库根目录的 COMPAT.md;
- 进程内运行:零网络开销,直接在 Node.js 进程中执行;
- TypeScript 支持:内置完整的 TypeScript 类型定义;
- 跨平台:支持 Linux(x86 与 arm64)、macOS、Windows,以及通过 WebAssembly 支持浏览器运行。
需要说明的是,该项目尚未发布 1.0 正式版(当前仓库中 package.json 的版本号为0.8.0-pre.10),官方明确建议在生产环境保持备份。
二、安装与包结构
在 Node.js 项目中安装非常简单:
npm install @tursodatabase/database从仓库源码结构看,bindings/javascript是一个 npm workspaces 管理的 monorepo(见 package.json),核心工作区包括:
packages/common:TypeScript 公共层,提供Database/Statement/Transaction的 Promise 式高层 API(promise.ts)以及与 better-sqlite3 风格对齐的兼容 API(compat.ts);packages/native:基于 N-API(由 NAPI-RS 生成类型声明 index.d.ts)的原生绑定,直接调用 Rust 引擎;packages/wasm与packages/wasm-common:WebAssembly 构建,用于浏览器环境;sync/packages/...:与 Turso Cloud 双向同步相关的三件套(common / native / wasm)。
底层数据库引擎由 Rust 实现(见 bindings/javascript/src 与 Cargo.toml),通过 N-API 暴露给 JavaScript 层,这正是其“进程内、低开销”的来源。
三、快速上手:内存数据库
通过connect(':memory:')即可创建一个完全驻留内存的数据库,适合测试、缓存与临时计算场景:
import { connect } from '@tursodatabase/database'; // Create an in-memory database const db = await connect(':memory:'); // Create a table await db.exec( 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)' ); // Insert data const insert = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)'); await insert.run('Alice', 'alice@example.com'); await insert.run('Bob', 'bob@example.com'); // Query data const users = await db.prepare('SELECT * FROM users').all(); console.log(users); // Output: [ // { id: 1, name: 'Alice', email: 'alice@example.com' }, // { id: 2, name: 'Bob', email: 'bob@example.com' } // ]关键点:
db.exec(sql)可执行包含多条 SQL 语句的字符串(源码中exec通过executor逐步驱动执行,见 promise.ts);db.prepare(sql)返回预编译的Statement,可重复绑定不同参数执行;stmt.run(...)返回包含changes与lastInsertRowid的信息对象;stmt.all()返回全部行,默认每行是形如{ id: 1, name: 'Alice' }的对象。
四、文件型数据库
传入一个文件路径即可创建或打开磁盘数据库。文件不存在时会自动创建(见 docs/API.md):
import { connect } from '@tursodatabase/database'; // Create or open a database file const db = await connect('my-database.db'); // Create a table await db.exec(` CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); // Insert a post const insertPost = db.prepare('INSERT INTO posts (title, content) VALUES (?, ?)'); const result = await insertPost.run('Hello World', 'This is my first blog post!'); console.log(`Inserted post with ID: ${result.lastInsertRowid}`);通过result.lastInsertRowid可以拿到自增主键 ID。此外,connect还支持查询db.path、db.memory、db.readonly、db.open等属性(见 index.d.ts),便于运行时判断数据库状态。
五、事务:transactionAsync 的正确用法
官方推荐用db.transactionAsync(fn)包装事务逻辑,回调的第一个参数是一个Transaction句柄,后续参数是调用包装函数时传入的参数:
import { connect } from '@tursodatabase/database'; const db = await connect('transactions.db'); // Using transactions for atomic operations const transaction = db.transactionAsync(async (txn, users) => { const insert = await txn.prepare('INSERT INTO users (name, email) VALUES (?, ?)'); for (const user of users) { await insert.run(user.name, user.email); } }); // Execute transaction await transaction([ { name: 'Alice', email: 'alice@example.com' }, { name: 'Bob', email: 'bob@example.com' } ]);从 promise.ts 的实现看,transactionAsync有几个重要行为:
- 包装器独占连接:在发出
BEGIN之前获取数据库的执行锁execLock,直到COMMIT/ROLLBACK之后才释放,期间其他语句/事务无法插入到该事务窗口内; - 回调内 SQL 必须走
Transaction句柄:若回调内直接调用db或数据库预编译的语句,会因为等待本事务持有的锁而死锁;assertTransactionCallback(promise.ts)会拒绝未声明句柄参数的回调(如fn.length === 0的旧式签名); - 自动回滚:回调抛出异常时自动执行
ROLLBACK,成功后执行COMMIT; - 事务模式:包装函数暴露
default、deferred、concurrent、immediate、exclusive五个模式属性,分别对应不同的BEGIN锁定模式:await transaction.immediate([ { name: 'Alice', email: 'alice@example.com' }, ]);
六、批量执行:batch 与原子批处理
db.batch(statements, options)按顺序执行一组语句,返回与 libSQL 客户端一致的ResultSet数组(每个结果含columns、columnTypes、rows、rowsAffected,插入语句还带lastInsertRowid):
// 纯 SQL 字符串(默认非原子:每条语句独立自动提交) await db.batch([ "INSERT INTO users(name) VALUES ('Alice')", "INSERT INTO users(name) VALUES ('Bob')", ]); // 支持位置参数 ? 与命名参数 :name await db.batch([ { sql: "INSERT INTO users(name, email) VALUES (?, ?)", args: ["Carol", "carol@example.net"] }, { sql: "INSERT INTO users(name, email) VALUES (:name, :email)", args: { name: "Dave", email: "dave@example.net" } }, ]); // 通过 mode 参数实现原子批处理:整体包在 BEGIN IMMEDIATE / COMMIT 中,失败自动 ROLLBACK await db.batch([ { sql: "INSERT INTO users(name) VALUES (?)", args: ["Eve"] }, { sql: "INSERT INTO users(name) VALUES (?)", args: ["Frank"] }, ], "immediate");mode的取值映射见 promise.ts 的normalizeBatchMode:
| mode 值 | 实际 SQL 模式 |
|---|---|
write/immediate | BEGIN IMMEDIATE |
read/deferred | BEGIN DEFERRED |
exclusive | BEGIN EXCLUSIVE |
concurrent | BEGIN CONCURRENT |
需要留意两点:一是原子模式下批处理内的语句不允许包含BEGIN、COMMIT、END、ROLLBACK、SAVEPOINT、RELEASE等事务控制关键字,会在执行前被拒绝;二是当语句失败时,抛出的错误携带batchIndex(失败语句的零基下标)与batchResults(每条语句一个结果,失败及未执行的为null),方便定位问题。
七、查询超时:timeout、defaultQueryTimeout 与 queryOptions
数据库引擎默认提供三类时间相关选项(定义见 types.ts):
timeout:busy timeout,单位毫秒,用于等待锁释放;defaultQueryTimeout:默认的查询执行超时(毫秒),超时后中断执行;queryOptions.queryTimeout:单次查询级别的超时覆盖,优先级最高,例如:
await db.exec('SELECT 1', { queryTimeout: 100 }); const stmt = await db.prepare('SELECT * FROM big_table'); await stmt.get(undefined, { queryTimeout: 100 });Statement上也提供setQueryTimeout(queryOptions)方法(见 index.d.ts),可以在准备语句后单独设置超时。
八、连接选项 DatabaseOpts 详解
connect(path, options)的第二个参数支持以下字段(完整定义见 types.ts):
| 选项 | 类型 | 说明 |
|---|---|---|
readonly | boolean | 以只读模式打开数据库 |
fileMustExist | boolean | 文件必须存在,否则报错 |
timeout | number | busy timeout(毫秒) |
defaultQueryTimeout | number | 默认查询超时(毫秒) |
tracing | 'info' \| 'debug' \| 'trace' | 追踪日志级别 |
experimental | ExperimentalFeature[] | 启用实验特性 |
encryption | EncryptionOpts | 本地数据库加密配置 |
实验特性列表来自源码 types.ts:views、strict、encryption、index_method、custom_types、autovacuum、vacuum、triggers、attach、generated_columns、multiprocess_wal、without_rowid。用法示例:
const db = await connect('app.db', { experimental: ['views', 'triggers'], });本地加密:encryption需要指定cipher与hexkey(十六进制编码的密钥)。支持的加密算法(见 types.ts)包括aes128gcm、aes256gcm、aegis256、aegis256x2、aegis128l、aegis128x2、aegis128x4:
const db = await connect('encrypted.db', { encryption: { cipher: 'aes256gcm', hexkey: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }, });九、Statement API:run / get / all / iterate 与行展示模式
预编译语句Statement提供四种执行方式:
stmt.run(...):执行写操作,返回{ changes, lastInsertRowid };stmt.get(...):返回第一行,无匹配时返回undefined;stmt.all(...):返回全部行组成的数组;stmt.iterate(...):返回异步迭代器,逐行消费,适合大结果集。
Database上也提供了同名便捷方法db.run、db.get、db.all、db.iterate,内部自动 prepare 并在调用返回前 finalize(见 promise.ts)。文档 docs/API.md 明确标注:run、get、all、iterate是 libSQL 的扩展 API,better-sqlite3 中没有对应实现。
行展示模式(与 better-sqlite3 对齐):
stmt.raw(true):返回数组而非对象,raw()不带参默认开启,raw(false)关闭;stmt.pluck(true):只返回第一列的值,pluck()不带参默认开启;safeIntegers(true)/db.defaultSafeIntegers(true):以 BigInt 安全返回 64 位整数(SQLite 的整数超出 JSNumber安全范围时很有用)。
注意raw与pluck是互斥选项(见 docs/API.md 中对pluck、raw的说明)。此外Statement还提供parameterCount()、parameterName(index)(1 起始下标)、columns()(返回列名与类型)等反射方法,以及reset()与finalize()生命周期管理。
十、浏览器与 WebAssembly 支持
README 提到浏览器可通过 WebAssembly 运行。仓库中对应实现位于packages/wasm(含 worker.ts、wasm-inline.ts 以及针对 Vite、Turbopack 的入口适配),packages/wasm-common提供跨构建的公共逻辑。
从 promise.ts 的io()实现注释可以看到浏览器与 Node.js 的差异:浏览器 WASM 构建中,I/O 由 OPFS Worker 异步完成,ioStep等待 I/O 通知器解析的 Promise;而内存/Node.js 构建中 I/O 是同步的,ioStep为空操作。这正是“同一套 API,双端运行”的架构基础。
十一、底层原理:异步步进执行模型
理解执行模型有助于写出高效代码。绑定层不采用“一条 SQL 同步跑完”的模式,而是把每条语句拆成步进循环(step loop),每次stepSync()返回[step, sleepMs]二元组,其中step取值为(常量定义见 types.ts):
STEP_ROW(1):取到一行数据;STEP_DONE(2):语句执行完毕;STEP_IO(3):引擎需要等待异步 I/O 完成,驱动层调用io()挂起等待;STEP_SLEEP(4):引擎要求延迟sleepMs毫秒后重试(如 busy-handler 退避),驱动层用定时器setTimeout等待后继续。
db.exec的实现(promise.ts)就是围绕这套步进常量的循环:遇到STEP_IO就 await I/O,遇到STEP_SLEEP就sleepBeforeRetry(sleepMs),遇到STEP_DONE结束。同时,同一连接上的所有原生调用都通过AsyncLock(execLock)串行化,避免并发 step 循环在共享连接上交错执行——这也是transactionAsync必须通过Transaction句柄访问 SQL 的原因(见 promise.ts)。
这套设计使引擎能够实现协作式调度与真正的异步 I/O:在浏览器端配合 OPFS Worker 不阻塞主线程,在 Node.js 端又能以极小开销直接执行。原生层还暴露了classifySql(返回read/write/begin/commit/rollback)、changes()、totalChanges()、inTransaction()、ioLoopSync()/ioLoopAsync()等底层能力(见 index.d.ts)。
十二、相关生态包与许可
官方 README 还推荐了两个同 API 生态的包:
- @tursodatabase/serverless);
- @tursodatabase/sync)。
本文介绍的@tursodatabase/database采用 MIT 许可。想进一步了解完整的类与方法签名,可阅读仓库内的 API 文档;SQLite 兼容性状态见 COMPAT.md;绑定层构建与发布脚本可参考 Makefile 与 Cargo.toml。
【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考