cloudflare Durable Object 存储用法

下面我将为您概述 Durable Objects 存储 API 的用法,并结合文档内容进行解释。

核心概述

Durable Objects 存储 API 为每个对象实例提供了一个私有的、事务性的、强一致性的存储空间。 这意味着数据是安全的、最新的,并且操作是原子的(要么全部成功,要么全部失败)。

根据文档,存储后端主要有两种:

  1. KV (键值对) 模式:这是传统的存储方式。

  2. SQLite 模式:这是 Cloudflare 推荐的新模式,它在对象内部嵌入了一个完整的 SQLite 数据库,不仅支持键值对操作,还支持功能强大的 SQL 查询。

无论使用哪种模式,你都通过 this.state.storage (在旧版 Worker 中) 或 this.ctx.storage (在新版模块 Worker 中) 来访问存储 API。


1. 键值对 (KV) API 用法

这些是最常用、最基础的操作,在 SQLite 和 KV 两种模式下都可用。

put() - 存储或更新数据

此方法用于存储一个或多个键值对。 如果键已存在,其值将被覆盖。

  • 单个存储:

    
    // 存储一个字符串值
    await this.ctx.storage.put("username", "alice");
    
    // 存储一个数字和对象
    await this.ctx.storage.put("loginCount", 15);
    await this.ctx.storage.put("profile", { email: "test@example.com", plan: "pro" });
    
  • 批量存储: 可以一次性存储多个键值对,最多支持 128 个。

    
    await this.ctx.storage.put({
      username: "bob",
      loginCount: 1,
      lastLogin: Date.now()
    });
    

get() - 读取数据

此方法用于读取一个或多个键的值。 如果键不存在,则返回 undefined

  • 单个读取:

    
    let username = await this.ctx.storage.get("username"); // 返回 "alice" 或 undefined
    let profile = await this.ctx.storage.get("profile"); // 返回存储的对象
    
  • 批量读取: 一次性读取多个键,返回一个 Map 对象。 任何不存在的键都不会出现在返回的 Map 中。 最多支持 128 个键。

    
    let data = await this.ctx.storage.get(["username", "loginCount", "nonexistentKey"]);
    // data 是一个 Map:
    // data.get("username") -> "bob"
    // data.get("loginCount") -> 1
    // data.has("nonexistentKey") -> false
    

delete() - 删除数据

删除一个或多个键值对。

  • 单个删除: 如果键存在并被成功删除,返回 true,否则返回 false

    
    let wasDeleted = await this.ctx.storage.delete("old-key");
    
  • 批量删除: 返回成功删除的键值对的数量。 最多支持 128 个键。

    
    let deletedCount = await this.ctx.storage.delete(["temp-key-1", "temp-key-2"]);
    

list() - 列出键值对

返回对象中存储的所有或部分键值对,结果是一个 Map

警告: 如果不加任何选项直接调用 list(),它会尝试将对象中的所有数据加载到内存中,这可能导致超出内存限制。

  • 常用选项:

    • prefix: 只列出以特定字符串开头的键。

    • limit: 限制返回的键值对数量。

    • start / startAfter: 从哪个键开始(或之后)列出。

    • reverse: true 表示降序排列。

  • 示例:

    
    // 获取所有以 "user:" 开头的键值对
    let userEntries = await this.ctx.storage.list({ prefix: "user:" });
    
    // 获取前 10 个键值对
    let firstTen = await this.ctx.storage.list({ limit: 10 });
    

deleteAll() - 删除所有数据

清空该 Durable Object 实例中的所有数据。 在 SQLite 模式下,此操作是原子的;在旧的 KV 模式下,如果中途失败可能只删除了一部分。

  
await this.ctx.storage.deleteAll();
  

2. SQL API 用法 (仅限 SQLite 模式)

这是 SQLite 模式独有的强大功能,允许你执行原生 SQL 查询。 通过 this.ctx.storage.sql 访问。

sql.exec() - 执行 SQL 查询

这是核心方法,用于执行任何 SQL 语句。

  • 参数:

    1. query: 包含 SQL 语句的字符串。 可以用 ? 作为参数占位符。

    2. ...bindings: 传递给 ? 占位符的实际值。

  • 返回值:

    返回一个可迭代的 cursor 对象,你可以用它来遍历查询结果。

  • 示例:

    
    // 在构造函数中初始化表结构
    constructor(ctx, env) {
      super(ctx, env);
      this.sql = this.ctx.storage.sql;
      // 如果表不存在,就创建它
      this.sql.exec(`
        CREATE TABLE IF NOT EXISTS users (
          id INTEGER PRIMARY KEY,
          name TEXT,
          email TEXT
        )`);
    }
    
    // --- 在其他方法中使用 ---
    
    // 插入数据 (使用参数绑定)
    this.sql.exec("INSERT INTO users (name, email) VALUES (?, ?)", "Charlie", "charlie@example.com");
    
    // 查询数据并遍历结果
    let cursor = this.sql.exec("SELECT * FROM users WHERE name LIKE ?", "C%");
    for (const row of cursor) {
      console.log(row.id, row.name); // 输出: 1 Charlie
    }
    
    // 将查询结果直接转为数组
    let allUsers = this.sql.exec("SELECT * FROM users").toArray();
    // allUsers 是一个对象数组: [{ id: 1, name: 'Charlie', ... }]
    

3. 事务 (Transactions) 的重要概念

Durable Objects 的一个关键优势是其内置的事务性保证。

  • 自动事务: 文档指出,显式的 transaction() API 在很多情况下已不再是必需的。 系统会自动将操作打包处理:

    • 自动写入合并: 如果你连续调用多个 put()delete() 而中间没有 await,它们会被自动合并成一个原子操作提交。

    • 读写原子性: 当你执行一个 await 读取操作(如 get())时,系统会自动阻止其他事件并发执行,直到读取完成。 因此,“先读后写”的模式(await get(), 然后 put())天然就是事务性的和安全的。

这个特性极大地简化了代码,让你无需手动管理锁和事务就能编写出正确、无竞态条件的代码。