Python(boto3)中的 DynamoDB GetItem

get_item完整主键取回一个项目。boto3 的低层 clientboto3.client("dynamodb"))双向都说 DynamoDB JSON,所以键要带着类型包装着传进去,项目也以同样的形式回来。它和 queryscan 的区别在基于项目的操作里有讲。

代码

import boto3

client = boto3.client("dynamodb")

response = client.get_item(
    TableName="Music",
    Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
    ProjectionExpression="#proj0, #proj1, #proj2, #proj3",
    ExpressionAttributeNames={"#proj0": "Artist", "#proj1": "SongTitle", "#proj2": "AlbumTitle", "#proj3": "Year"},
)

item = response.get("Item")
if item is None:
    print("Item not found")
else:
    print(item)

说明

没命中时,返回的响应里压根没有 Item 这个键。不是 None,也不是空字典。在同一张表上读一个不存在的键,响应的顶层键正好是:

['ResponseMetadata']

这就是代码片段用 response.get("Item") 的原因。在普通的「没找到」路径上,response["Item"] 会抛 KeyError,而这正是一行缺失的记录在 Web 处理函数里变成 500 的过程。你照样要为这次读取付费:AWS 的读容量页面写着,"if you perform a read operation on an item that doesn't exist, DynamoDB will still consume read throughput as outlined above"(2026-07-28 取回)。

Year 是保留字,这就是生成的代码片段给每个投影属性都起别名的原因。去掉那些 #proj 别名、直接传 ProjectionExpression="Year",引擎就会拒绝这次读取:

ValidationException: Invalid ProjectionExpression: Attribute name is a reserved keyword; reserved keyword: Year

无条件起别名不花任何代价,却能消掉一整类失败。完整清单有 573 个词;见 "Attribute name is a reserved keyword"

Key 写错的四种方式,三种不同的消息。它们值得分清楚,因为其中没有一个是大家预期的「provided key element does not match the schema」错误。在一张以 Artist(分区键)+ SongTitle(排序键)为键的 Music 表上复现:

你传了什么原样的 ValidationException 消息
{"Artist": …}——缺排序键The number of conditions on the keys is invalid
{"Artist": …, "SongTitle": …, "Extra": …}The number of conditions on the keys is invalid
{"Artist": …, "Song": …}——属性名写错One of the required keys was not given a value
{"Artist": {"N": "1"}, …}——类型写错One or more parameter values were invalid: Type mismatch for key

注意缺少一个键属性和多出一个键属性给出的是同一条消息,所以「number of conditions」的意思是「你交给我的不是恰好那套键模式」,而不是「你传得太少了」。

ProjectionExpression 削减的是载荷,不是账单。用 ReturnConsumedCapacity="TOTAL" 以三种方式读同一个约 15 KB 的项目:

full item, eventually consistent          CapacityUnits: 2.0
ProjectionExpression="#y" (Year only)     CapacityUnits: 2.0
ProjectionExpression="#y" + ConsistentRead  CapacityUnits: 4.0

投影把响应从约 15 KB 变成了一个数字,而成本一点没变。AWS 说得很直白:"The number of capacity units consumed will be the same whether you request all of the attributes (the default behavior) or just some of them (using a projection expression)"(Query API 参考文档,2026-07-28 取回)。ConsistentRead=True 是那张清单上唯一能动这个数字的开关,而且它把数字翻了一倍。投影到底是干什么用的,见投影表达式

资源 API 是另一份契约,不是同一件事的漂亮写法boto3.resource("dynamodb").Table("Music").get_item(...) 返回的是普通 Python,每个数字都是 decimal.Decimal

{'Artist': 'Arturo Sandoval', 'AlbumTitle': 'Danzon', 'Awards': Decimal('0'), 'Year': Decimal('1994'), 'SongTitle': 'Cubano Chant'}

这是把双刃剑。用同一套 API 拿 float 写回去,请求还没离开你的机器就会报错:

TypeError: Float types are not supported. Use Decimal types instead.

要是被这个咬到了,"Float types are not supported" 里有修法。真正的陷阱是在一个代码库里混用这两套 API:低层 client 会欣然接受 {"N": "1.5"},而资源 API 本来会拒绝它。

错误以 botocore 异常的形式到达,而 boto3 给了它们真正的类。在 1.43.58 上,条件失败时抛出的对象是 ConditionalCheckFailedException,它是 ClientError 的子类,所以 except ClientError 加上 err.response["Error"]["Code"] 判断,和 except client.exceptions.ConditionalCheckFailedException 两种写法都能用。用你代码库里已有的那种;不要拿 str(e) 去匹配。

用可视化的方式来做

在你动手起别名之前:免费的 DynamoDB 保留字检查器接收你的属性名,告诉你撞上了 573 个保留字中的哪些,并给出可直接粘贴的 ExpressionAttributeNames 映射。

要浏览表并针对你自己的数据运行 GetItem——键的表单、结果网格、把请求复制成 boto3 代码——请下载 DynoTable

相关指南

参考资料

2026-07-28 针对 DynamoDB Local(amazon/dynamodb-local,端口 9000)以 boto3 1.43.58 / botocore 1.43.58 复现。上方每一条消息和每一个容量数字都是引擎输出,原样照录。DynamoDB Local 不是线上服务;已知两者措辞不同的地方,我们会在对应的错误页上说明。

无需控制台即可使用 DynamoDB

一款快速的 DynamoDB 桌面客户端,可运行 DynamoDB 无法执行的真正 SQL——JOINs、GROUP BY、聚合——并支持可视化编辑和运行在你自己的 Bedrock 密钥上的 AI agent。

30 天免费试用,无需信用卡 — 之后为无时间限制的免费版。