Turso / libSQL 模式
快速指南: 所有 Turso 資料庫存取都使用
@libsql/client。使用execute()進行單一查詢,使用batch()進行原子性的多語句操作(優先於互動式交易),只有在後續查詢依賴於先前結果時才使用transaction()。對於沒有檔案系統存取權限的邊緣/無伺服器運行時,請從@libsql/client/web匯入。若要實現零延遲讀取,請使用本地檔案 URL 加上syncUrl配置嵌入式副本。所有寫入都會轉發到主資料庫 -- 設計時請考慮 15-50 毫秒的寫入延遲。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 將 SQLite 帶到邊緣,它將 libSQL(SQLite 的一個分支)作為一項託管服務,並具備多區域複寫能力。@libsql/client 驅動程式提供了一個統一的 API,無論你是連接到遠端 Turso 資料庫、本地 SQLite 檔案、記憶體資料庫,還是從遠端主資料庫同步的嵌入式副本,它的使用方式都完全相同。
核心原則:
- 批次優先於交易 --
batch()在單次往返中發送所有語句,並在隱含交易中執行。互動式transaction()需要多次往返並持有資料庫鎖(5 秒逾時)。除非需要在查詢之間使用條件邏輯,否則請使用batch()。 - 寫入永遠打到主資料庫 -- 即使使用嵌入式副本,寫入也會被轉發到遠端主資料庫。寫入延遲取決於與主區域的距離,約 15-50 毫秒。為此設計:樂觀 UI、背景同步、避免寫入密集型熱路徑。
- 嵌入式副本用於讀取 -- 從遠端主資料庫同步的本地 SQLite 檔案。讀取時間為微秒級。寫入則轉發到遠端。成功寫入後本地檔案會更新(讀取自己寫入的語意)。
- 兩種匯入路徑 --
@libsql/client包含適用於 Node.js 的原生 SQLite 綁定,並支援file:URL。@libsql/client/web是用於邊緣運行時(Cloudflare Workers、Vercel Edge Functions)的純 JS/WASM,且無法開啟本地檔案。 - 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 },
});
回傳 ResultSet,包含 rows (Array<Row>)、columns、rowsAffected、lastInsertRowid (bigint)。有關型別化結果映射和錯誤範例,請參閱 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-50 毫秒)。
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(),讀取是本地的,寫入轉發到遠端並具有讀取自己寫入的語意。
使用時機: VM、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 / VM / 容器
| +-- 需要嵌入式副本(本地檔案)?
| | +-- 是 --> 使用 @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-- 基礎套件捆綁的原生 SQLite 綁定會在 Cloudflare Workers、Vercel Edge Functions 及類似環境中失敗。請改用@libsql/client/web。 - SQL 中的字串插值 --
execute(\SELECT * FROM users WHERE id = '${id}'`)是 SQL 注入漏洞。請始終使用帶有args` 的參數化查詢。 - 直接存取嵌入式副本檔案 -- 在 libSQL 客戶端執行時,用另一個 SQLite 用戶端開啟本地的
.db檔案會導致資料損毀。僅能透過客戶端存取。
中等優先級問題:
- 在
batch()足以勝任時使用transaction()-- 互動式交易會持有資料庫鎖並有 5 秒逾時,需要多次往返,並會封鎖其他寫入者。對於預先確定的語句組,請使用batch()。 - 未呼叫
client.close()-- 在短期程序中(CLI 腳本、測試清理),忘記關閉客戶端會留下開放的連線和檔案控制代碼。 - 忽略嵌入式副本的寫入延遲 -- 讀取是微秒級(本地),但寫入需要 15-50 毫秒(轉發到遠端主資料庫)。請相應地設計 -- 避免緊湊的寫入迴圈。
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()中的語句共用一個交易,但「並非」平行執行 -- 它們會順序執行。
</red_flags>