入门阅读约 2 分钟

DynamoDB 投影表达式

投影表达式是 DynamoDB 里的 SELECT col1, col2:一个逗号分隔的名列表,告诉 GetItemQueryScan 只返回那些属性,而不是整个条目。

DynamoDB 投影表达式能降低读取成本吗?

不能。ProjectionExpression 裁剪的是响应载荷,而不是你被计费的读取容量。DynamoDB 从存储里读取完整条目,按它在磁盘上的大小计量 ,然后在返回途中丢掉你没点名的属性。要真正削减读取成本,改用一个覆盖式

  • 它裁剪载荷,而不是读取成本。DynamoDB 从存储里读取(并计费)完整条目,然后在返回途中丢掉你没点名的属性。ProjectionExpression 是一种网络优化,而不是容量优化。
  • 它是你抓取一个公开子集的方式。点名调用方被允许看到的那几个属性;其余的永远不离开表。
  • 对任何可能是保留字的东西用 #name 占位符。表达式里的裸属性名会与 DynamoDB 约 570 个保留字冲突,导致请求失败。
  • 要真正省读取,改用一个覆盖式索引。一个只投影你所需列的 ,会以它自己(更小的)大小被读取。

它实际省下什么

从 SQL 过来,你会以为 SELECT a, bSELECT * 扫描得更少。在 DynamoDB 里这个直觉是错的。一次读取的容量单位是由条目在磁盘上的大小算出来的,向上取整到下一个 4 KB——而且是在应用投影_之前_。AWS 说得很明白:ProjectionExpression 不改变一个请求消耗的读取容量。1

所以投影为你省下两样东西,两样都是真的,但两样都是读取的下游:

  • 线路上的字节。一个 6 KB 的条目作为两个小属性返回,是个很小的响应。在一个返回上百个条目的 Query 上,这加起来很快就可观了。
  • 客户端的工作。更少的反序列化,更少的内存占用,更少意外泄漏进日志或 API 响应的东西。

省的是 RCU。这就是那个陷阱:人们伸手去用投影来削减账单,看到没变化,就断定 DynamoDB 坏了。它没坏——你量错了杠杆。

投影一个公开的用户资料

假设你运营一个用户目录。每份资料是一个条目,键设计得能按用户名抓一个人:

PK = "PROFILE#ada"      (partition key)
SK = "PROFILE#ada"      (sort key — single-item collection)

这个条目很胖。它既携带账户的公开面孔,又携带一堆私有和运营属性:

{
  "PK": "PROFILE#ada",
  "SK": "PROFILE#ada",
  "displayName": "Ada L.",
  "avatarUrl": "https://cdn.example.com/u/ada.png",
  "bio": "Builds things.",
  "emailAddress": "ada@example.com",
  "passwordResetToken": "…",
  "billingCustomerId": "cus_…",
  "lastLoginIp": "…",
  "internalRiskScore": 0.02
}

一张公开的资料卡片需要三个字段。抓取整个条目意味着 emailAddresslastLoginIpinternalRiskScore 会传到一个永远不该看到它们的上下文里。只点名那个公开子集:

GetItem  PK = "PROFILE#ada"  SK = "PROFILE#ada"
ProjectionExpression: displayName, avatarUrl, bio

响应携带三个属性。私有的那些留在表里——不是被你的应用在到达_之后_过滤掉,而是压根从未被序列化进响应。这就是安全上的胜利,而且它是那种一旦秘密已经越过边界就很难挽回的胜利。

你可以在 DynamoDB 表达式构建器里组装并复制这个确切的请求——名称、占位符,以及 SDK 调用——它会替你产出 ProjectionExpressionExpressionAttributeNames 映射。

在下面的预设里增删字段,看着 ProjectionExpression 变化——只有列出的属性会返回:

构建你的请求
生成的代码
new QueryCommand({
  "TableName": "AuditLog",
  "KeyConditionExpression": "#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
  "ProjectionExpression": "#proj0, #proj1, #proj2",
  "ExpressionAttributeNames": {
    "#hashKey": "pk",
    "#rangeKey": "sk",
    "#proj0": "action",
    "#proj1": "actor",
    "#proj2": "createdAt"
  },
  "ExpressionAttributeValues": {
    ":hashKeyValue": {
      "S": "TENANT#acme"
    },
    ":rangeKeyValue": {
      "S": "EVENT#"
    }
  }
})

# 占位符转义保留字

这里就是一个干净的投影会炸掉的地方。DynamoDB 保留了一长串词——namestatuscommentsizetimestamp,还有上百个。2如果你正在投影的某个属性是其中之一,表达式里的裸名就会被拒绝。

假设这份资料还有一个 status 属性("active""suspended")。这会失败:

ProjectionExpression   displayName, status

status 是保留字。修复方法是一个表达式属性名——一个 # 前缀的占位符,映射到真实名称:

ProjectionExpression       displayName, #s
ExpressionAttributeNames   { "#s": "status" }

同样的机制能伸进嵌套属性。要从一个 map 里取出单个字段,或从一个 list 里取一个元素,用文档路径语法——并且给每一段都加占位符,因为其中任何一段都可能是保留字:

ProjectionExpression       #addr.#city, tags[0]
ExpressionAttributeNames   { "#addr": "address", "#city": "city" }

一条实用规则:给一切都加占位符。你就永远不必记住你正踩在那约 570 个保留字里的哪一个上,而且表达式两种写法读起来都一样。而如果你更想知道究竟是哪些名称出了问题,把它们粘贴进保留字检查器——它会标记冲突,并输出 ExpressionAttributeNames 别名映射。

覆盖式索引何时胜过投影

如果你真心需要削减读取成本——不只是载荷——那个杠杆是一个只投影你所读属性的全局二级索引。GSI 是数据的一份单独副本;你为它的投影选择 KEYS_ONLYINCLUDEALL3一个 KEYS_ONLY 或窄 INCLUDE 的索引每条目在物理上更小,所以针对它的 Query 会以那个更小的大小被计量。

这就是覆盖式索引:查询完全从索引里得到回答,不用回基表跑一趟。当一个热读取模式永远只需要大条目里的几个属性时用它。

ProjectionExpression覆盖式 GSI
裁剪载荷
削减读取成本——以索引的大小读取
额外存储被投影字段的第二份副本
额外写入成本写入会传播到索引
最适合隐藏私有字段;小赢从大条目里热读取几个字段

索引花你存储和写入容量来省下读取容量。对于从一个重条目里频繁读取一个薄切片,值得;为了省下一次性的 GetItem,不值得。参见 GSI vs LSI 来挑选索引类型,并在你把索引放到热路径上之前看看GSI 读取何时可能是陈旧的

陷阱与后续步骤

  • 别指望账单变小。光靠投影永远不改变 RCU。如果数字没动,那是有据可查的行为,不是 bug。
  • 给保留字加占位符。表达式里一个裸的 namestatus 会让请求失败——用 # 映射它。
  • 总是包含键属性——它们几乎不增加载荷,还让你能分页或重新抓取条目。
  • 只在一个热模式从大条目里读取几个字段时才伸手去用覆盖式索引;先权衡写入/存储成本。

表达式构建器里构造 ProjectionExpression 及其属性名映射,然后试试 DynoTable,对你自己的表运行这些投影,看着响应缩小。


  1. AWS DynamoDB 开发者指南,《Using projection expressions in DynamoDB》——读取容量基于应用任何 ProjectionExpression 之前的条目大小。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html
  2. AWS DynamoDB 开发者指南,《Reserved Words in DynamoDB》。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html
  3. AWS DynamoDB 开发者指南,《Attribute Projections》(KEYS_ONLY / INCLUDE / ALL)。https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html

更新于