跳转到主要内容
在本页上

交易

Deno KV 存储使用乐观并发控制事务,而不是像 PostgreSQL 或 MySQL 等许多 SQL 系统那样的交互式事务。这种方法采用版本戳(表示给定键的值的当前版本)来管理对共享资源的并发访问,而无需使用锁。当发生读取操作时,系统除了返回值之外,还会返回关联键的版本戳。

要执行事务,需要执行一个原子操作,该操作可以包含多个修改操作(例如设置或删除)。除了这些操作之外,还会提供键值对作为事务成功的条件。乐观并发控制事务仅在指定的版本戳与数据库中相应键的值的当前版本匹配时才会提交。此事务模型可确保数据一致性和完整性,同时允许在 Deno KV 存储中进行并发交互。

由于 OCC 事务是乐观的,因此它们在提交时可能会失败,因为违反了原子操作中指定的版本约束。当代理在读取和提交之间更新事务中使用的键时,就会发生这种情况。发生这种情况时,执行事务的代理必须重试该事务。

为了说明如何将 OCC 事务与 Deno KV 一起使用,此示例演示了如何为账户分类账实现 transferFunds(from: string, to: string, amount: number) 函数。账户分类账将每个账户的余额存储在键值存储中。键以 "account" 为前缀,后跟账户标识符:["account", "alice"]。为每个键存储的值是一个表示账户余额的数字。

以下是实现此 transferFunds 函数的逐步示例

async function transferFunds(sender: string, receiver: string, amount: number) {
  if (amount <= 0) throw new Error("Amount must be positive");

  // Construct the KV keys for the sender and receiver accounts.
  const senderKey = ["account", sender];
  const receiverKey = ["account", receiver];

  // Retry the transaction until it succeeds.
  let res = { ok: false };
  while (!res.ok) {
    // Read the current balance of both accounts.
    const [senderRes, receiverRes] = await kv.getMany([senderKey, receiverKey]);
    if (senderRes.value === null) {
      throw new Error(`Account ${sender} not found`);
    }
    if (receiverRes.value === null) {
      throw new Error(`Account ${receiver} not found`);
    }

    const senderBalance = senderRes.value;
    const receiverBalance = receiverRes.value;

    // Ensure the sender has a sufficient balance to complete the transfer.
    if (senderBalance < amount) {
      throw new Error(
        `Insufficient funds to transfer ${amount} from ${sender}`,
      );
    }

    // Perform the transfer.
    const newSenderBalance = senderBalance - amount;
    const newReceiverBalance = receiverBalance + amount;

    // Attempt to commit the transaction. `res` returns an object with
    // `ok: false` if the transaction fails to commit due to a check failure
    // (i.e. the versionstamp for a key has changed)
    res = await kv.atomic()
      .check(senderRes) // Ensure the sender's balance hasn't changed.
      .check(receiverRes) // Ensure the receiver's balance hasn't changed.
      .set(senderKey, newSenderBalance) // Update the sender's balance.
      .set(receiverKey, newReceiverBalance) // Update the receiver's balance.
      .commit();
  }
}

在本例中,transferFunds 函数读取两个账户的余额和版本戳,计算转账后的新余额,并检查账户 A 是否有足够的资金。然后,它执行一个原子操作,根据版本戳约束设置新余额。如果交易成功,循环退出。如果违反了版本约束,交易将失败,循环将重试交易,直到成功为止。

限制 跳转到标题

除了最大键大小为 2 KiB 和最大值大小为 64 KiB 之外,Deno KV 事务 API 还有一些限制。

  • 每次 kv.getMany() 的最大键数: 10
  • 每次 kv.list() 的最大批次大小: 1000
  • 原子操作中的最大检查次数: 100
  • 原子操作中的最大修改次数: 1000
  • 原子操作的最大总大小:800 KiB。这包括检查和修改中的所有键和值,编码开销也计入此限制。
  • 键的最大总大小:90 KiB。这包括检查和修改中的所有键,编码开销也计入此限制。
  • 每次 kv.watch() 的最大监听键数:10