DynamoDB实战:从命令行建表到读懂每次读取的成本

通过命令行实验理解DynamoDB分区键如何同时决定写入位置与读取成本。
文章以笔记应用为背景,通过AWS CLI在DynamoDB上完成建表、写入、读取和删除的完整实验。核心论点是:建表时选择的分区键同时决定了每次写入落在哪条记录上,以及每次读取需要做多少工作。文章演示了带类型JSON的写入方式、同key重复put的静默覆盖行为,并对比了GetItem、Scan和Query三种读取路径。关键结论是:GetItem按key直达单条记录;Scan读遍全表且过滤表达式不减少读取量,ScannedCount与Count的对比是识别低效读取的直接信号;按需容量按请求计费,强一致性读成本翻倍。
DynamoDB实战:从命令行建表到读懂每次读取的成本
一个笔记应用把数据放在服务器本地磁盘上,机器一换,数据就没了。于是你想用一张托管表来持久化数据——然后立刻遇到两个问题:第一条命令该敲什么?以及,如何判断一次读取是便宜还是昂贵?
这篇动手实验用 Amazon DynamoDB 回答这两个问题。它完全通过 API 访问,全部操作都可以在终端完成。贯穿全文的核心论点只有一句:建表时选择的partition key(分区键),同时决定了每次写入落在哪一条记录上,以及每次读取要做多少工作。 按key读取直达单条记录;Scan则会读遍所有记录,而且这个差别直接体现在命令返回的响应里。

环境准备:一个身份、一个区域、一种计费模式
你需要一个AWS账户(使用非root的日常管理员身份)、版本2的AWS CLI,并以该身份登录。实验使用亚太(孟买)区域 ap-south-1,可用以下命令设置:
aws configure set region ap-south-1
用 aws sts get-caller-identity 确认当前身份,输出中的 Account 字段就是你的12位账户ID。CLI文档指出该命令不需要任何权限,因此无论用什么身份都能跑通。
计费模式选择按需容量(on-demand capacity),即按读写请求付费、无需预先规划容量。官方文档称其为默认且推荐模式,并明确说明:如果表上没有流量,按需模式下不收取任何吞吐费用,存储则单独计费。截至2026-10-02,定价页面列出了每区域25 GB的免费存储额度,以及25个免费读容量单位和25个免费写容量单位——但后者只适用于预置(provisioned)模式,不适用于按需请求。本实验的几十次请求会按请求计费。
Read Capacity Unit(RCU)与 Write Capacity Unit(WCU 是 DynamoDB 计量的基本单位。在预置模式下,1 个 RCU 表示每秒可对 4 KB 的项目做一次强一致性读,或每秒两次最终一致性读;1 个 WCU 表示每秒可写 1 KB。当项目大于 4 KB(读)或 1 KB(写)时,会向上取整消耗多个单位。在按需模式下,你不再预购这些单位,但请求仍按同样的单位换算成费用。理解这一点有助于把命令返回的 ConsumedCapacity 数字换算成实际金额:容量单位越小、项目越大,单次请求越贵。
分区键是第一个决策:它就是记录的身份
一张表(table)是项目(item)的集合,类似行;每个项目是一组属性(attribute),类似字段。每个项目都必须有一个不与任何其他项目重复的主键。最简单的情况下,主键就是一个属性——分区键。DynamoDB把分区键的值送入内部哈希函数,哈希结果决定项目存储在哪里。
可以把它想象成一墙编号储物柜:表是那面墙,分区键是每个柜门上的编号。取东西时,报出编号,直接走到那个柜子前。
本实验的表叫 hoc-notes,分区键为字符串类型 noteId,没有排序键:
aws dynamodb create-table \
--table-name hoc-notes \
--attribute-definitions AttributeName=noteId,AttributeType=S \
--key-schema AttributeName=noteId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--tags Key=project,Value=hands-on-cloud
这条命令里有三个教学点:
--attribute-definitions只列出key。你从不声明title或body。除key之外,表是schemaless的,每条记录可以携带不同属性。KeyType=HASH把noteId标记为分区键(分区键的旧称是"hash attribute",源自哈希函数)。排序键则用RANGE。key属性必须是字符串、数字或二进制。PAY_PER_REQUEST选择按需容量。
为什么选 noteId 而不是 title?因为应用是从链接或API路径里按id取笔记的,而且key必须唯一——标题会重复。后文会展示:共享同一key的两条笔记无法共存。
预期结果: 命令立刻返回 "TableStatus": "CREATING"。用 describe-table 反复检查,直到状态变为 "ACTIVE",表就可以写入了。

哈希函数在这里是把任意长度的分区键值映射成一个固定范围的整数,DynamoDB 据此决定项目落在哪个内部物理分区上。分区键值分布越均匀,项目越能分散到不同分区,单分区热点和吞吐瓶颈就越少。如果大量记录共享同一个分区键值,它们会集中到同一分区,受该分区吞吐上限约束。选择分区键时除了保证唯一性,还应尽量让取值分散,比如用用户ID或随机后缀,而不是状态、日期等低基数字段。
用带类型的JSON写项目,重复的key会静默覆盖
CLI用**带类型的JSON(typed JSON)**写入项目:每个值都包裹在类型中。字符串是 {"S": "Buy milk"},数字2则是 {"N": "2"}。
注意那个数字在引号里。第一次看会觉得不对,但它是对的:N 表示这是个数字,值本身以字符串形式传输。写 {"N": 2} 才是要警惕的错误。
写入几条笔记:
aws dynamodb put-item --table-name hoc-notes \
--item '{"noteId": {"S": "n-001"}, "title": {"S": "Buy milk"}, "body": {"S": "Two litres"}}'
aws dynamodb put-item --table-name hoc-notes \
--item '{"noteId": {"S": "n-002"}, "title": {"S": "Read chapter 3"}, "priority": {"N": "2"}}'
再多写两三条,各用不同id。注意 n-002 有 priority 属性而 n-001 没有,这正是schemaless存储的体现——就像手机通讯录:有的联系人有生日,有的有邮箱,只有ID是必需的。
现在故意制造一次失败:用不同内容再次写入 n-001。你会看到:什么都没有。 没有报错,没有警告。读回来,它变成了 Call home,而且根本没有 body 属性。文档对put的描述很精确:它"创建新项目,或用新项目替换现有项目"。同一key的两次写入,是同一条记录的两个版本,第二次会连同属性一起完全替换第一次。储物柜比喻里,你并没有加第二个柜子,而是清空了原来的柜子重新装满。

**Schemaless(无模式)**并不意味着没有约束:除了主键必须在写入前声明、类型固定之外,每个项目可以自由增减属性,且同一表中不同项目的属性可以完全不同。但代价是数据库不会帮你校验非key属性的类型或存在性,应用层需要自行保证一致性。另外,单个项目有大小上限(目前为 400 KB),且嵌套属性层数也有限制。设计时若发现某类数据可能无限增长,应考虑拆分为多个项目,而不是堆在一条记录里。
GetItem只读一条记录,因为它直奔key
笔记应用有两种不同的工作:打开某条笔记的链接时,应用知道key;展示首页时,应用必须列出所有笔记、不知道key。两种工作对应两种读取方式,所以都要测量。
GetItem 接收完整主键,返回那一条记录,或什么都不返回。让它报告消耗了多少容量:
aws dynamodb get-item --table-name hoc-notes \
--key '{"noteId": {"S": "n-002"}}' \
--return-consumed-capacity TOTAL
你会看到该记录,外加一个 ConsumedCapacity 块,显示这次请求用掉多少读容量。记下这个数字,它是你的基线。
读取默认是最终一致性的。加上 --consistent-read 就变成强一致性读,能反映此前所有已完成的写入,但成本翻倍。把两条命令各跑一次,对比两个 ConsumedCapacity 数字,就能看到差别。
Scan读遍所有记录,过滤表达式不会让它更便宜
首页没有key可以直达,你得打开墙上每一个柜子、依次查看——这就是 Scan:
aws dynamodb scan --table-name hoc-notes --return-consumed-capacity TOTAL
你会看到所有笔记、一个 ConsumedCapacity 块,以及两个计数:ScannedCount 是读过的记录数,Count 是返回的记录数。无过滤的Scan中两者相等。
现在改用过滤表达式只取一条笔记:
aws dynamodb scan --table-name hoc-notes \
--filter-expression "title = :t" \
--expression-attribute-values '{":t": {"S": "Call home"}}' \
--return-consumed-capacity TOTAL
你会看到 Count 降到了1,但 ScannedCount 仍等于笔记总数。文档把原因讲得很直白:无论是否存在过滤表达式,Scan消耗的读容量相同。过滤发生在读取之后——它缩小的是答案,不是工作量,就像打开每一个柜子、只留下红色袋子。
Scan还分页执行:每次请求最多读1 MB,CLI会持续请求下一页,直到读完整张表。
大 ScannedCount 配小 Count,就是低效读取的标志。五条笔记时浪费看不出来;一百万条笔记时,为找一个标题而Scan要读一百万条记录,而按key的GetItem仍然只读一条。按需容量按实际读取量计费,所以这既是性能问题,也是成本问题。

我的经验规则很直接:当你确实需要全部记录且表很小时(比如列出练习应用的笔记)用Scan;只要你能报出key,就用GetItem。
最终一致性读与强一致性读的区别在于:最终一致性读可能返回稍旧的数据,但成本只有强一致性读的一半;强一致性读保证读到此前已成功写入的最新值,但消耗双倍读容量。DynamoDB 的读默认是最终一致性的,因为跨可用区的数据复制需要时间。对于笔记应用这类对实时性要求不高的场景,默认读取即可;只有在“写入后立刻读取并必须看到最新值”的场景(如账户余额校验)才值得付出双倍成本。
Query在此处与GetItem等价,靠排序键才显价值
第三种读取是 Query,返回共享同一分区键值的所有记录。它的key条件表达式必须对分区键做等值判断,值通过以冒号开头的占位符传入:
aws dynamodb query --table-name hoc-notes \
--key-condition-expression "noteId = :id" \
--expression-attribute-values '{":id": {"S": "n-002"}}' \
--return-consumed-capacity TOTAL
你会看到一条记录。在只有分区键的表里,任意两条记录都不能共享该值,所以Query最多返回一条,和GetItem返回的是同一条。
Query的真正价值出现在表还带有**排序键(sort key)**时——那是可选的第二个key属性。共享同一分区键的记录存储在一起,按排序键有序排列。想象一张以艺人+歌名为主键的音乐表:查询某位艺人就像拉出写着他名字的抽屉,每首歌按歌名顺序出来,其他抽屉一概不碰。

清理之后不再产生任何账单
空闲的按需表不收吞吐费,但存储数据仍会计费。实验结束后删除表:
aws dynamodb delete-table --table-name hoc-notes
你会看到表的描述,状态变为 DELETING;此后 describe-table 会报告找不到该表。创建时加的 project = hands-on-cloud 标签,方便你确认实验没有留下任何残留。
核心要点
- 建表时只声明key,其余一切schemaless,各条记录可以不同。
- 分区键要按数据的取用方式和唯一性来选,比如id而绝不要用标题。用已存在的key执行put会静默替换整条记录。
- 用带类型的JSON写值,数字要加引号:
{"N": "2"}。 - 在任何读取命令上加
--return-consumed-capacity TOTAL,就能看到这次读取花了多少。每次Scan都要对比ScannedCount和Count。 - GetItem只读一条;Scan读全部,过滤表达式不减少读取量。
- 按需容量下你为请求和存储付费,强一致性读的成本是默认读取的两倍。
这个实验只在少量记录上测量了读取。它没有解决更丰富的访问模式下如何设计key、如何用条件写防止覆盖、以及如何把应用权限限制到这一张表——每一个都值得单独展开。
相关推荐

前端转AI Agent开发:一线架构师解析职业路径与红利
一线AI架构师解析前端转AI Agent开发的行业变化与落地路径:纯前端岗位为何消失、Agent+全栈为何成为招聘主流,以及Agent开发工程师、FDE、远程接单三条转型路线和对应学习策略。

AI Agent开发课程怎么选?五条避坑指南
机械转行开发者踩坑两年总结的AI Agent开发课程筛选清单:完整交付链路、可写进简历的项目、真实开发视角、更新频率、陪跑反馈五个维度,附具体判断方法和避坑要点。

扣子(Coze)智能体搭建入门:零代码开发AI Agent实战指南
扣子(Coze)是字节跳动推出的一站式AI应用开发平台,支持零代码拖拽搭建AI智能体。本文基于B站教程梳理扣子的核心概念、三大优势、国内版与国际版差异,以及智能体与应用的区别,帮助初学者快速入门AI Agent开发。