libSQL 模式 是做什么的?
快速指南: 对于所有 Turso 数据库访问,请使用
@libsql/client。对于单个查询使用execute(),对于原子化的多语句操作使用batch()(优于交互式事务),仅当后续查询依赖于先前结果时才使用transaction()。对于没有文件系统访问权限的边缘/无服务器运行时,请从@libsql/client/web导入。为了实现零延迟读取,请使用本地文件 URL 和syncUrl配置嵌入式副本。所有写入都会转发到主数据库——设计时需考虑 15-50ms 的写入延迟。Turso 底层是 SQLite:单写入者模型,不支持ALTER TABLE ... ADD CONSTRAINT,没有存储过程。
<critical_requirements>
关键:使用此技能前必须了解
所有代码必须遵循 CLAUDE.md 中的项目约定 (kebab-case、具名导出、导入顺序、
import type、具名常量)
(对于多语句原子操作,你必须使用带有事务模式的 batch()——它比交互式 transaction() 更快、更安全,因为它在单次往返中执行)
(在缺乏文件系统访问的边缘/无服务器运行时(Cloudflare Workers、Vercel Edge Functions)中,你必须从 @libsql/client/web 导入——基础的 @libsql/client 导入会拉取原生绑定,这些绑定在这些环境中会失败)
(你必须将事务模式("write"、"read" 或 "deferred")作为第二个参数传递给 batch() 和 transaction()——默认为 "deferred",这会在尝试 INSERT/UPDATE/DELETE 时静默地无法获取写入锁)
(当客户端在短生命周期的进程中不再需要时,你必须调用 client.close()——打开的客户端会持有连接和文件句柄)
(当客户端正在运行时,你绝对不能直接访问本地嵌入式副本数据库文件——并发访问会导致数据损坏)
</critical_requirements>
自动检测: Turso、libSQL、@libsql/client、createClient、turso.io、嵌入式副本、syncUrl、syncInterval、turso db、turso group、libsql、.turso.io、TURSO_DATABASE_URL、TURSO_AUTH_TOKEN
使用时机:
- 从任何运行时(Node.js、边缘、无服务器)查询 Turso 托管的 SQLite 数据库
- 设置嵌入式副本,以实现从远程主数据库同步的零延迟本地读取
- 每个租户一个数据库的多租户 SaaS(Turso 支持数百万个数据库)
- 需要数据库但无需处理连接池复杂性的无服务器/边缘函数
- 使用
batch()或交互式transaction()运行原子化的多语句操作 - 通过 Turso CLI 管理数据库组和多区域部署
不宜使用的时机:
- 需要强多写入者一致性的写入密集型工作负载(Turso 是单写入者,写入转发到主数据库)
- 需要 PostgreSQL 功能的复杂关系查询(带有修改子查询的 CTE、存储过程、高级约束)
- 跨多个数据库的复杂分布式事务
- 大型分析数据集(受 SQLite 行大小和并发限制)
详细资源:
- examples/core.md -- 客户端设置、execute、batch、事务、导入路径
- examples/embedded-replicas.md -- 本地副本、同步、离线模式、加密
- reference.md -- 决策框架、类型定义、CLI 命令、查找表
理念
Turso 通过托管 libSQL(SQLite 的一个分支)作为具有多区域复制功能的托管服务,将 SQLite 带到边缘。@libsql/client 驱动提供了一个统一的 API,无论您连接的是远程 Turso 数据库、本地 SQLite 文件、内存数据库还是从远程主数据库同步的嵌入式副本,其工作方式都相同。
核心原则:
- batch 优于 transaction --
batch()在单次往返中发送所有语句,并在隐式事务中执行它们。交互式transaction()需要多次往返并持有数据库锁(超时时间 5 秒)。除非您需要在查询之间进行条件逻辑判断,否则请使用batch()。 - 写入始终命中主数据库 -- 即使使用嵌入式副本,写入也会转发到远程主数据库。写入延迟取决于到主数据库区域的距离,为 15-50ms。设计时应考虑这一点:乐观 UI、后台同步、避免写入密集型的热路径。
- 读取使用嵌入式副本 -- 从远程主数据库同步的本地 SQLite 文件。读取是微秒级的。写入转发到远程。成功写入后本地文件会更新(读你所写语义)。
- 两个导入路径 --
@libsql/client包含用于 Node.js 的原生 SQLite 绑定,并支持file:URL。@libsql/client/web是纯 JS/WASM,用于边缘运行时(Cloudflare Workers、Vercel Edge Functions),无法打开本地文件。 - SQLite 语义 -- Turso 就是 SQLite。不支持
ADD CONSTRAINT、没有存储过程、没有LISTEN/NOTIFY、单写入者 WAL 模式。在选择 Turso 之前,请了解 SQLite 的限制。
核心模式
模式 1:客户端设置
使用 createClient() 创建客户端。url 决定了连接类型:libsql:// 用于远程 Turso,file: 用于本地 SQLite(仅限 Node.js),:memory: 用于内存数据库(测试用)。始终对 authToken 使用环境变量——切勿硬编码凭据。
有关完整的设置模式,包括单例模块和错误示例,请参阅 examples/core.md。
模式 2:执行查询
execute() 运行单个 SQL 语句。始终使用带 args 的参数化查询——切勿使用字符串插值。
// 位置参数:args 为数组
await client.execute({
sql: "SELECT * FROM users WHERE id = ?",
args: [userId],
});
// 命名参数:args 为对象(裸名称匹配 SQL 中的 :name、@name、$name)
await client.execute({
sql: "INSERT INTO users (name, email) VALUES (:name, :email)",
args: { name, email },
});
返回包含 rows (Array<Row>)、columns、rowsAffected、lastInsertRowid (bigint) 的 ResultSet。有关类型化结果映射和错误示例,请参阅 examples/core.md。
模式 3:批处理操作
batch() 在单次往返中原子化地执行多条语句。全部成功或全部回滚。始终将事务模式指定为第二个参数。
const results = await client.batch(
[
{ sql: "INSERT INTO users (name) VALUES (?)", args: ["Alice"] },
{
sql: "INSERT INTO audit_log (action, entity_id) VALUES (?, last_insert_rowid())",
args: ["user_created"],
},
],
"write", // 对于 INSERT/UPDATE/DELETE 是必需的
);
对于变更操作使用 "write",对于仅 SELECT 操作使用 "read"(允许并行执行),使用 "deferred" 以只读模式开始并在需要时升级。last_insert_rowid() 在同一个批处理中的语句之间有效。有关多行插入和只读批处理模式,请参阅 examples/core.md;有关事务模式比较表,请参阅 reference.md。
模式 4:交互式事务
仅当后续查询依赖于前面查询的结果时,才使用 transaction()。它会持有数据库锁(空闲超时时间 5 秒)并需要多次往返。始终使用 try/catch/finally 和 close()。
const tx = await client.transaction("write");
try {
const { rows } = await tx.execute({
sql: "SELECT balance FROM accounts WHERE id = ?",
args: [fromId],
});
// ... 基于结果的条件逻辑 ...
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
} finally {
tx.close();
}
如果所有语句都是预先确定的,没有条件逻辑,请改用 batch()。有关完整的带库存检查的购买示例,请参阅 examples/core.md。
模式 5:不同运行时的导入路径
// Node.js、Bun、Deno(具有文件系统访问权限、原生绑定)
import { createClient } from "@libsql/client";
// 没有文件系统的边缘/无服务器运行时(Cloudflare Workers、Vercel Edge)
import { createClient } from "@libsql/client/web";
基础的 @libsql/client 捆绑了在边缘运行时中会失败的原生 SQLite 绑定。@libsql/client/web 是纯 JS/WASM,但无法打开本地 file: URL。有关完整的 Cloudflare Worker 示例,请参阅 examples/core.md。
模式 6:嵌入式副本
从远程 Turso 主数据库同步的本地 SQLite 文件。读取是本地的(微秒级),写入转发到远程(15-50ms)。
const client = createClient({
url: "file:local-replica.db",
syncUrl: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
syncInterval: 60, // 每 60 秒自动同步
});
await client.sync(); // 在首次读取前填充本地副本
关键点:file: URL 用于本地副本,syncUrl 用于远程主数据库,启动时调用 sync(),读取是本地的,写入转发到远程并具有读你所写语义。
使用时机: 虚拟机、VPS、容器或任何具有文件系统访问权限的长时间运行进程。在无文件系统的无服务器/边缘运行时中不可用。有关完整模式,包括手动同步、离线模式和加密,请参阅 examples/embedded-replicas.md。
模式 7:数据库组和多区域
Turso 将数据库组织成组。每个组有一个主区域和可选的副本位置。组中的所有数据库都继承其位置的设置。
无论哪个副本处理读取,所有写入都会发往主区域。添加更多位置可以改善全局的读取延迟,但不会减少写入延迟。写入延迟取决于到主区域的距离。
有关完整的 Turso CLI 命令参考(turso db create、turso group create、turso db shell 等),请参阅 reference.md。
<decision_framework>
决策框架
batch() 与 transaction()
所有 SQL 语句是否都是预先确定的(语句之间没有条件逻辑)?
+-- 是 --> 使用 batch()(单次往返,隐式事务,首选)
+-- 否 --> 后面的语句是否依赖于前面语句的结果?
+-- 是 --> 使用 transaction()(交互式,多次往返,持有锁)
+-- 否 --> 使用 batch()
导入路径选择
使用什么运行时环境?
+-- Node.js / Bun / Deno / 虚拟机 / 容器
| +-- 需要嵌入式副本(本地文件)?
| | +-- 是 --> @libsql/client 配合 file: URL + syncUrl
| | +-- 否 --> @libsql/client 配合 libsql:// URL
+-- Cloudflare Workers / Vercel Edge / 浏览器 / 无文件系统的无服务器环境
+-- @libsql/client/web(仅远程连接,不支持 file: URL)
嵌入式副本与纯远程连接
进程是否是长期运行的并具有文件系统访问权限?
+-- 是 --> 是否需要亚毫秒级读取延迟?
| +-- 是 --> 嵌入式副本(file: URL + syncUrl)
| +-- 否 --> 纯远程连接更简单(libsql:// URL)
+-- 否(无服务器、边缘、短生命周期)
+-- 纯远程连接(@libsql/client/web,libsql:// URL)
事务模式选择
批处理/事务将执行哪些操作?
+-- 仅 SELECT 查询 --> "read"(允许在副本上并行执行)
+-- 任何 INSERT / UPDATE / DELETE --> "write"(获取排他锁)
+-- 调用时不确定 --> "deferred"(以只读开始,必要时升级)
</decision_framework>
<red_flags>
红旗警告
高优先级问题:
- 在
batch()/transaction()中缺少事务模式 -- 省略第二个参数默认为"deferred",这以只读模式开始,可能静默地无法获取 INSERT/UPDATE/DELETE 的写入锁。始终显式指定模式。 - 在边缘运行时中使用
@libsql/client-- 基础包捆绑了在 Cloudflare Workers、Vercel Edge Functions 及类似环境中会失败的原生 SQLite 绑定。应改用@libsql/client/web。 - SQL 中的字符串插值 --
execute(\SELECT * FROM users WHERE id = '${id}'`)是一种 SQL 注入漏洞。始终使用带args` 的参数化查询。 - 直接访问嵌入式副本文件 -- 在 libSQL 客户端运行时使用另一个 SQLite 客户端打开本地
.db文件会导致数据损坏。只能通过客户端访问。
中等优先级问题:
- 在
batch()足够时使用transaction()-- 交互式事务会持有数据库锁(超时时间 5 秒),需要多次往返,并会阻塞其他写入者。对于预定的语句集,请使用batch()。 - 未调用
client.close()-- 在短生命周期的进程(CLI 脚本、测试清理)中,忘记关闭客户端会留下打开的连接和文件句柄。 - 忽略嵌入式副本的写入延迟 -- 读取是微秒级的(本地),但写入是 15-50ms(转发到远程主数据库)。请相应地进行设计——避免紧密的写入循环。
syncInterval设置过低 -- 每次同步都会拉取所有更改的帧(每帧 4KB)。在写入密集型数据库上,亚秒级的间隔会产生显著的网络和 I/O 开销。
常见错误:
- 错误的包名 -- 包名是
@libsql/client,而不是libsql-client、@turso/client或turso-client。 - args 对象中的命名参数前缀 -- Args 使用裸名称:
{ name: "Alice" }匹配 SQL 中的:name、@name和$name。不要包含前缀:{ ":name": "Alice" }将无法匹配。 - 期望
lastInsertRowid是数字 -- 它是bigint | undefined。如果您需要数字,请显式转换:Number(result.lastInsertRowid),但请注意非常大的 rowid 的精度损失。 - 对原子操作使用
executeMultiple()--executeMultiple()运行原始 SQL 文本(分号分隔),没有参数化,也没有隐式事务。对于原子化的参数化操作,请使用batch()。
陷阱与边缘案例:
batch()语句共享一个事务但 NOT 并行 -- 它们按顺序执行。last_insert_rowid从批处理中的前一条语句获取正确的值。changes()类似地仅在第一个语句后是正确的。SQLite 的1 AS sorting_order技巧可以工作,但不保证跨批处理。
</red_flags>