进阶阅读约 1 分钟

DynamoDB ReturnValues:从一次写入拿回旧项或新项

默认情况下,一次 DynamoDB 写入除了成功之外什么都不返回。但你常常需要写入前后的数据 —— 改动之前的值,或改动之后的新值。最直白的办法是再做一次 GetItem,这既是一次额外的往返, 是一次竞态:别人可以在中间写入。DynamoDB 用 ReturnValues 参数避开了这两点,它作为写入 本身的一部分,原子地把旧项或新项交还给你。

DynamoDB 中的 ReturnValues 有什么用?

ReturnValues 告诉 DynamoDB 写入操作在同一次调用中把项交还给你,从而跳过第二次 GetItem 及其带来的竞态。PutItemDeleteItem 接受 NONEALL_OLDUpdateItem 接受全部五个选项(NONEALL_OLDUPDATED_OLDALL_NEWUPDATED_NEW),以原子方式返回旧值或新值。

  • ReturnValues 把项作为写入的一部分返回 —— 没有第二次读取,没有竞态。
  • NONE(默认)—— 不返回任何东西。
  • ALL_OLD —— 写入之前的整个项。
  • UPDATED_OLD —— 仅更新所改动的那些属性的之前值。
  • ALL_NEW —— 写入之后的整个项。
  • UPDATED_NEW —— 仅改动过的属性的之后值。
  • PutItem/DeleteItem 只接受 NONEALL_OLD UpdateItem 接受全部五个。

问题所在:你需要你刚刚覆盖掉的那个值

假设你运营一个客服台,一位客服把一张工单的状态从 open 改成 pending。你的审计日志需要记录 改动之前的状态是什么。没有 ReturnValues 的话你会:

  1. GetItem 读取当前状态,
  2. UpdateItem 设置新状态。

在第 1 步和第 2 步之间,另一位客服可能改了状态 —— 现在你的审计日志记下的是一个陈旧的「之前」值。 更糟的是,这是一个逻辑操作用了两次调用。ReturnValues 把它收拢成单次原子的 UpdateItem, 返回写入时刻实际的旧状态。

五个选项,以及各自何时该用

UpdateItem 支持全套;选择的是你需要项的哪一部分以及写入的哪一侧

ReturnValues返回何时使用
NONE什么都不返回你不需要拿回项(默认)
ALL_OLD写入前的整个项审计 /「我刚替换掉了什么?」
UPDATED_OLD写入前改动的属性你只关心你动过的字段
ALL_NEW写入后的整个项你需要把新鲜的完整项返回给调用方
UPDATED_NEW写入后改动的属性读回一个你刚自增的计数器/值

UPDATED_NEW 是日常的主力:用一个更新表达式自增一个 计数器,并在同一次调用里读回新的总数,没有竞态。对于客服工单的审计,ALL_OLD(若你只记录状态 字段则用 UPDATED_OLD)能原子地捕获改动前的状态。

注意这种不对称:PutItemDeleteItem 只支持 NONEALL_OLD —— 删除没有「新」值 可返回,而 put 的新值就是你发送的内容。只有原地修改的 UpdateItem 才提供全部五个。 AWS 文档 给出了确切的对照表。

在 DynoTable 中编写更新

DynamoDB 表达式构建器可视化地组装 UpdateItem 及其 更新表达式 —— 它会生成 SET/ADD 子句以及属性名和值映射。在应用里,当一次暂存的写入被提交后, DynoTable 会显示生成的项,于是你能直接看到新状态。

在 DynoTable 中审阅一个项的暂存变更 —— 提交更新前的旧值与新值。
在 DynoTable 中审阅一个项的暂存变更 —— 提交更新前的旧值与新值。

陷阱与下一步

  • 别用 GetItem-然后-写入来读取一次改动前后的值 —— 那是一次往返加一次竞态;用 ReturnValues
  • UPDATED_* 只返回动过的属性 —— 若你需要整个项,用 ALL_*
  • PutItem/DeleteItem 无法返回新值 —— 只能用 NONE/ALL_OLD
  • ReturnValues 不是条件的替代品 —— 要守卫一次写入,加一个 条件表达式;要读回它的效果,用 ReturnValues。 它们可以组合使用。
  • 相关: 更新表达式原子计数器

想做编辑并看到改动前后,而不用脚本写两次调用吗?下载 DynoTable,直接编辑你的项。

更新于