入门阅读约 3 分钟

DynamoDB 复合主键:分区键与排序键详解

复合主键是两个属性:一个分区键和一个排序键。分区键决定一个项住在哪里;排序键给那个分区内部的各项排序。

从 SQL 过来,与其把它想成一个唯一的 id 列,不如把它想成 GROUP BY partition, ORDER BY sort 被烤进了表本身。

什么是 DynamoDB 复合主键?

DynamoDB 复合主键由两个属性组成:一个分区键和一个排序键。分区键决定一个项存放在哪个物理分区中;排序键给那个分区内部的各项排序。两者共同构成项的唯一标识,并让一次 Query 返回一个排好序的范围,而非单个项。

  • 两部分,两份活。 分区键把项路由到一个物理分区;排序键给共享那个分区键的每一个项排序。
  • 唯一性是这一对。 只要排序键不同,两个项就可以共享一个分区键值 —— 这就是一个分区如何容纳许多行。
  • 排序键是整个要点。 是它让一次 Query 返回一个范围(>=betweenbegins_with)而非一个项,且不需 Scan
  • 键必须是标量。 分区键和排序键只能是字符串、数字或二进制 —— 没有映射,没有列表(AWS 文档)。

简单键 vs 复合键

一个简单主键就只是一个分区键。它唯一地标识一个项,而你用 GetItem 把它读回来。仅此而已 —— 没有范围读取,没有“给我最新的 N 个”。

复合键加上排序键,而那单单一项添加,就是让 DynamoDB 感觉像个数据库、而非一张哈希表的东西。

简单键复合键
属性仅分区键分区键 + 排序键
唯一性分区键值这两个值组成的
每分区多项
Query 一个范围否(仅 GetItem是(begins_withbetween>
天然适配按 id 查找时间序列、一对多、历史

给传感器读数表建模

假设你从一批现场传感器收集温度采样。访问模式是“在一个时间窗口内,取某个设备的读数,最新优先”。那是教科书式的复合键。

用设备 id 作分区键、读数时间戳作排序键

deviceIdreadingTstempChumidity
DEV#a1b22026-06-23T08:00:00Z21.448
DEV#a1b22026-06-23T08:05:00Z21.747
DEV#a1b22026-06-23T08:10:00Z22.146
DEV#c9d82026-06-23T08:00:00Z19.855

全部三个 DEV#a1b2 读数都落在同一个分区,物理上存在一起,并按 readingTs 排序。

AWS 把分区键叫做 哈希属性、把排序键叫做 范围属性 —— 排序键是一个你能在其内部扫描的范围(AWS 文档)。

下面是各项如何在每个分区键之下坍缩成一个项集合:

分区:DEV#a1b2readingTs 08:00readingTs 08:05readingTs 08:10Query deviceId = DEV#a1b2

对分区键的一次 Query 读取那个设备的每一条读数,已经按时间戳排好序 —— 客户端无需排序,无需第二次往返。

该窗口的成本是多少

分区中每个 2 KB 的 10 个读数总计为 20 KB 计量。 DynamoDB轮每 4 KB 块读取一次,因此该窗口上最终一致的 Query 成本 3 读取请求单元(20 KB → 5 个 4 KB 块 × 每个 0.5 RCU)。一个同一窗口上的强一致性读取需要 5 单位(每个块一个 RCU)。使用十个单独的 GetItem 调用和最小一块来获取相同的十行规则适用于每个项目 → 5 单位最终一致,10 强烈一致——在计算额外的往返行程之前。

读取模式项目数据触及EC 读取单元
开始和结束之间有一个 QueryreadingTs1010 20 KB3
10 × GetItem 全复合键1010 20 KB5
Query 仅分区,在应用程序中过滤湿度1010 20 KB3
Scan表,过滤器deviceId+时间窗口全部整桌桌子大小

原型设计时通过ReturnConsumedCapacity: TOTAL;的 pricing calculator 将单位计数变为一旦您知道每秒的请求数,就会显示每月的订单项。

查询那个范围,别扫描它

因为 readingTs 是一个 ISO-8601 字符串,它按字典序排序的方式与按时间顺序排序的方式相同。所以一次时间窗口读取是一个键条件范围,而非一个筛选:

Query
deviceId  = "DEV#a1b2"
readingTs BETWEEN "2026-06-23T08:00:00Z" AND "2026-06-23T08:10:00Z"

那是一个 KeyConditionExpression —— 它在 DynamoDB 返回数据 之前 缩小读取,所以你只为窗口内的项付费。一个 FilterExpression 在读取 之后 运行,并就它扫过的一切向你计费;那是缩小版的 Scan 暗坑

表达式本身,连同占位符和带类型的值,手写起来很麻烦。用 DynamoDB 表达式构建器可视化地构建它,并把精确的 KeyConditionExpression 拷进你的 SDK 调用。

有意地设计排序键

排序键不是免费的元数据 —— 它是范围读取的唯一杠杆,所以把它塑造成贴合你的查询。

  • 用一个可排序的时间戳。 ISO-8601 字符串或补零的纪元数字排序正确;原始的本地化日期不行。
  • 给它加前缀以做一对多重载。 一个像 READING#2026-06-23T08:00:00Z 的排序键让你在一个分区下混合实体类型,并用 begins_with 切分它们。那是通向单表设计的接缝。
  • 把高基数维度放进分区键。 传感器 id 有数千个值,所以它均匀地分散写入。一个低基数分区键(比如 region)会造出一个热分区。

每秒 10,000 次写入时仅具有 50 个不同值的分区键在 DynamoDB 分割之前,流将每个逻辑分区集中约 200 个 WCU — 在原型规模上还好,在生产规模上却很痛苦。更喜欢这样的标识符通过粗略分组与您的设备群(设备 ID、租户 ID、会话 ID)一起增长除非你故意并置一个有界数据集。

复合键何时咬你

它是一项承诺,而非一项便利。陷阱:你挑了一个分区键、上线了,然后发现一个需要 不同 分组的访问模式 —— “整个机队里所有高于 30°C 的读数”。

基表回答不了那个;分区键是固定的。你的选项是一个带不同键的全局二级索引,或者重构。

在你提交键模式 之前 把你的读取枚举出来。改一个主键意味着一次表迁移,而非一次 ALTER TABLE

在 DynoTable 中,打开表浏览器并检查复合键表并排与其 GSI 并排 — 排序键上的排序顺序逐行可见,其中使相差一的时间戳格式在投入生产之前变得显而易见。

后续步骤

复合键是项集合、一对多关系和大多数有用的索引设计之下的根基 —— 接下来读单表设计GSI 对比 LSI,看它们通向何处。

DynamoDB 表达式构建器里勾勒你的 KeyConditionExpression,在查询构建器里生成完整的分页 Query,然后试用 DynoTable,去浏览你真实的分区,看排序顺序对着你自己的表对齐起来。

更新于