高级阅读约 3 分钟

DynamoDB 存储内部原理如何工作

DynamoDB 是一张哈希表,它的值是被排序的树,散布在三个可用区的机器上。两个数据结构干了 几乎所有的活儿:partition key 上的一个哈希挑机器,sort key 上的一棵 B 树给里面的 item 排序。

DynamoDB 如何存储数据?

DynamoDB 将你的数据存储为一张巨大的分布式哈希表,其值是有序的 B 树。partition key 上的哈希选定一个存储节点;B 树则按 sort key 在节点内部给 item 排序。每次写入在确认前都会同步复制到跨三个可用区的三个节点。

  • partition key 是被哈希的,不是被搜索的。 DynamoDB 在你的 PK 上跑一个哈希函数,找到 持有那个分区的唯一存储节点——一次 O(1) 跳转,与表大小无关。
  • sort key 住在一棵 B 树里。 在一个分区内部,item 存在一棵按 SK 字典序排序的 B 树里, 这就是为什么范围读取(begins_withbetween)便宜而 Scan 不便宜。
  • 每次写入在确认前命中三个节点。 一次写入去一个 leader,它同步复制到另外两个可用区的 peer——在你的 PutItem 返回 之前 就买下了持久性。
  • 这就是访问模式规则存在的原因。 先哈希再走树只在你按 key 读时才快。没有 key,就没有 快路径——你退回到扫描那棵树。

从数据结构开始,不是从 API

从 SQL 过来,你把表想象成磁盘上的行加一个挑索引的查询规划器。DynamoDB 没有规划器。存储布局 就是 那个合约——什么快、什么是自坑,两者都直接从两个结构里掉出来。

想象一张巨大的分布式 map。key 是你 partition key 的哈希。值不是单个 item——它是一整棵 共享那个 partition key 的 item 的 B 树,按 sort key 排序。

别的一切——query 语义、那些 10 GB 警告、为什么一个缺失的 key 逼出一个 Scan——都是那一句话 的后果。

哈希 partition key 来找节点

当一个请求到来,DynamoDB 对 partition key 值施加一个内部哈希函数。哈希确定性地映射到一个 存储节点——拥有那些 item 的物理分区。AWS 把这记录为常数时间 key 查找背后的机制,与表 大小无关。

那是那个 O(1) 步骤。一张 10 TB 的表和一张 10 KB 的表 定位 起来花同样的代价:哈希、跳转、 完成。没有索引扫描去找节点,没有统计信息,没有计划。

要害是反面。如果你不提供 partition key,DynamoDB 就没有节点可跳——它得走每一个分区。那是一个 Scan,是 O(1) 和读取整张表之间的差别。

PutItem / GetItemPK=DEVICE#a91Hash(PK)存储节点(一个分区)SK 上的 BREADING#... 已排序item

请求哈希到恰好一个分区,然后下降那个分区的 sort key B 树到 item——是两个便宜的步骤,而不是 一次表遍历。

在每分区 B 树里给 item 排序

在单个分区内部,item 不是一个堆。它们被持在一棵 以 sort key 为 key 的 B 树 里,按字典序 排序。一次 B 树搜索是 O(log n),而且关键是那个 n一个 分区里的 item,不是整张表。

这就是 sort key 范围读取便宜的全部原因。拿一张车队遥测表,每台设备的读数住在一个 partition key 下:

PKSK
PK = DEVICE#a91SK = READING#2026-06-23T08:00Z
PK = DEVICE#a91SK = READING#2026-06-23T08:05Z
PK = DEVICE#a91SK = READING#2026-06-23T08:10Z

因为 B 树是排序的,"08:00 到 09:00 之间的所有读数"是一次到起始值的树下降加一次顺序行走—— 不是一次对那台设备曾发过的每条读数的过滤。你只读匹配的那个范围。

那个排序也是为什么一个 begins_with(SK, "READING#2026-06-23") query 快而对一个非 key 属性 过滤不快。树能按 SK 寻位;它不能按别的任何东西寻位。要安全地组合那些 key 条件,在 DynamoDB 表达式构建器里构建它们,而不是手工拼接字符串:

KeyConditionExpression  PK = :pk AND begins_with(SK, :day)

把每次写入复制到三个可用区

一个分区不是一台机器。每一个都跨 三个可用区的三个节点 复制——一个可以追溯到 2007 年 Amazon Dynamo 论文的复制模型的设计血脉。

一个节点是这个分区的 leader。一次写入去 leader,它在本地写并复制到它的两个 peer。一旦 一个持久的法定多数节点拥有它,leader 就确认这次写入——所以跨可用区的持久性是在你的 PutItem 返回 之前 付清的。

读取有得选。一次 强一致 读取去 leader,看到最新已提交的写入。一次 最终一致 读取可以 由三个节点中的任一个服务,其中一个可能落后几毫秒——那就是你为更便宜、更可用的读取换来的滞后。

最终一致强一致
由谁服务三个节点中的任一个仅 leader 节点
看到最新写入也许(小滞后)总是
RCU 成本一半全额
可用性更高更低(单节点)

同样的异步传播思想正是为什么一个 GSI 读取可能陈旧——见 GSI 是最终一致的

从结构里读出规则

几乎每一条 DynamoDB"规则"都只是存储物理。

  • 总是提供 partition key。 没有 key,没有哈希目标——你在扫描整张 map。这是 Query vs Scan的核心。
  • 把你一起读的东西放在一处,放在一个 partition key 下,这样一次哈希加一次树行走就返回 整个 item 集合。那是 单表设计的根基。
  • 让分区保持有界。 一个分区是有限节点集上的一棵 B 树;一个失控的热 key 或一个 LSI 的 10 GB 上限都是那个物理分区的限制。

一旦你看清这个先哈希再 B 树的形态,访问模式纪律就不再感觉随意了——你只是在让每一次读取都待 在快路径上。

下一步

sort key 策略单表设计把你的 key 建模得匹配这个结构,然后在 DynamoDB 表达式构建器里组装实际的表达式。 试用 DynoTable,看着这些读取对你自己的表运行,并精确看到一个 key 条件拉回哪些 item。

更新于