使用 Turso Database for JavaScript:进程内 SQLite 兼容数据库的安装、查询、事务与 WebAssembly 实践
2026/9/12 16:22:05 网站建设 项目流程

使用 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/wasmpackages/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(...)返回包含changeslastInsertRowid的信息对象;
  • 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.pathdb.memorydb.readonlydb.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
  • 事务模式:包装函数暴露defaultdeferredconcurrentimmediateexclusive五个模式属性,分别对应不同的BEGIN锁定模式:
    await transaction.immediate([ { name: 'Alice', email: 'alice@example.com' }, ]);

六、批量执行:batch 与原子批处理

db.batch(statements, options)按顺序执行一组语句,返回与 libSQL 客户端一致的ResultSet数组(每个结果含columnscolumnTypesrowsrowsAffected,插入语句还带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/immediateBEGIN IMMEDIATE
read/deferredBEGIN DEFERRED
exclusiveBEGIN EXCLUSIVE
concurrentBEGIN CONCURRENT

需要留意两点:一是原子模式下批处理内的语句不允许包含BEGINCOMMITENDROLLBACKSAVEPOINTRELEASE等事务控制关键字,会在执行前被拒绝;二是当语句失败时,抛出的错误携带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):

选项类型说明
readonlyboolean以只读模式打开数据库
fileMustExistboolean文件必须存在,否则报错
timeoutnumberbusy timeout(毫秒)
defaultQueryTimeoutnumber默认查询超时(毫秒)
tracing'info' \| 'debug' \| 'trace'追踪日志级别
experimentalExperimentalFeature[]启用实验特性
encryptionEncryptionOpts本地数据库加密配置

实验特性列表来自源码 types.ts:viewsstrictencryptionindex_methodcustom_typesautovacuumvacuumtriggersattachgenerated_columnsmultiprocess_walwithout_rowid。用法示例:

const db = await connect('app.db', { experimental: ['views', 'triggers'], });

本地加密encryption需要指定cipherhexkey(十六进制编码的密钥)。支持的加密算法(见 types.ts)包括aes128gcmaes256gcmaegis256aegis256x2aegis128laegis128x2aegis128x4

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.rundb.getdb.alldb.iterate,内部自动 prepare 并在调用返回前 finalize(见 promise.ts)。文档 docs/API.md 明确标注:rungetalliterate是 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安全范围时很有用)。

注意rawpluck是互斥选项(见 docs/API.md 中对pluckraw的说明)。此外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_SLEEPsleepBeforeRetry(sleepMs),遇到STEP_DONE结束。同时,同一连接上的所有原生调用都通过AsyncLockexecLock)串行化,避免并发 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询