DynamoDB是AWS推出的全托管NoSQL数据库服务,在Python生态中操作它主要依赖boto3这个官方SDK。boto3提供了两种访问DynamoDB的风格:一种是贴近API原始结构的client,另一种是面向对象的resource。resource接口把表、item都抽象成了Python对象,写起来更自然,但背后隐藏的加载机制和格式差异常常让初学者踩坑。本文围绕boto3.resource('dynamodb')这一入口,系统地讲解它的用法和注意事项。

一、创建resource对象并获取表
使用resource的第一步是创建一个DynamoDB资源对象。resource是一个服务级的工厂对象,通过它可以拿到具体的表对象、执行批量操作等。创建方式非常简单:
import boto3
# 创建DynamoDB资源对象,region按实际情况填写
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-1')
# 通过资源对象获取表,参数是表名
table = dynamodb.Table('UserTable')
# 查看表是否存在(注意:这一步会真正发起一次DescribeTable调用)
print(table.creation_date_time)这里有一个非常重要的细节:dynamodb.Table('UserTable')本身并不会立刻发起网络请求,它只是构造了一个Table对象的引用。真正触发API调用的是你第一次访问表的元数据属性时,比如上面的creation_date_time。这种延迟加载(lazy loading)机制意味着,如果表不存在,Table()这一步不会报错,报错会推迟到后续操作。所以不要用try: dynamodb.Table('xxx')来判断表是否存在,这是无效的,应该通过dynamodb.tables.all()遍历或者用client的describe_table配合异常捕获来判断。
另外,如果本机配置了AWS CLI的凭证(位于C:\Users\你的用户名\.aws\credentials),boto3会自动读取,无需在代码里硬编码密钥。生产环境更推荐使用IAM角色或环境变量的方式传递凭证,避免密钥泄露。
二、使用Table对象完成增删改查
拿到Table对象后,日常的增删改查都可以直接调用它的方法。先看写入和读取:
table = dynamodb.Table('UserTable')
# 写入一条item,主键user_id是分区键
table.put_item(
Item={
'user_id': 'u1001',
'name': '张三',
'age': 28,
'email': 'zhangsan@ipipp.com'
}
)
# 按主键读取一条item
resp = table.get_item(
Key={'user_id': 'u1001'},
ConsistentRead=True # 强一致读
)
item = resp.get('Item') # 不存在时Item键不存在,返回None
print(item)resource接口最大的好处之一就是数据格式的简化。如果用client的get_item,返回的item会被DynamoDB的类型描述符包裹,比如字符串会变成{'S': '张三'}这样的结构,你必须手动解包。而resource会自动完成类型映射,直接返回普通的Python字典,字符串就是字符串,数字就是数字(Decimal类型),处理起来省心很多。
再看更新和删除。更新推荐使用UpdateExpression,表达式语法比直接覆盖整个item更高效也更安全:
from decimal import Decimal
# 只更新age字段,不影响其他属性
table.update_item(
Key={'user_id': 'u1001'},
UpdateExpression='SET age = :newAge, #n = :newName',
ExpressionAttributeNames={
'#n': 'name' # name是保留字,必须用占位符
},
ExpressionAttributeValues={
':newAge': Decimal(29),
':newName': '张三丰'
},
ReturnValues='UPDATED_NEW'
)
# 删除一条item
table.delete_item(Key={'user_id': 'u1001'})这里有两个高频踩坑点。第一,DynamoDB的更新表达式中有大量保留字,比如name、status、size等,直接写在表达式里会报错,必须像上面那样用#n占位符并在ExpressionAttributeNames中映射。第二,数字类型必须使用decimal.Decimal而不是Python原生的float,因为DynamoDB为了精度要求不支持浮点数,直接传float会抛出TypeError,这是几乎所有初学者都会遇到的一个错误。
条件写入也是常见需求,比如实现“不存在才插入”的语义,可以借助ConditionExpression:
from boto3.dynamodb.conditions import Key, Attr
try:
table.put_item(
Item={'user_id': 'u1002', 'name': '李四'},
ConditionExpression=Attr('user_id').not_exists()
)
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
print('该user_id已存在,插入被拒绝')boto3.dynamodb.conditions模块提供的Key和Attr类可以让你用Python运算符的方式构建表达式,比手写字符串更不容易出错,条件不满足时通过捕获ConditionalCheckFailedException来处理。
三、查询与扫描:query、scan的正确姿势
query和scan是两种读取方式,前者必须指定分区键,效率高;后者全表遍历,消耗大,一般只用于后台任务或小表。resource接口对这两个方法做了增强,支持分页迭代器:
from boto3.dynamodb.conditions import Key
# 查询某个分区键下的所有数据
resp = table.query(
KeyConditionExpression=Key('user_id').eq('u1001')
)
items = resp['Items']
# 使用Paginator自动处理分页,数据量大时必须用
paginator = dynamodb.meta.client.get_paginator('query')
for page in paginator.paginate(
TableName='UserTable',
KeyConditionExpression='#k = :v',
ExpressionAttributeNames={'#k': 'user_id'},
ExpressionAttributeValues={':v': {'S': 'u1001'}}
):
for item in page['Items']:
print(item)注意一个容易忽略的问题:单次query最多返回1MB数据,如果结果集超过这个限制,响应里会带LastEvaluatedKey,需要循环发起请求。很多人第一次用query时发现数据“少了”,就是因为没有处理分页。resource层面的table.query()不会自动帮你翻页,最省事的做法是像上面那样借助paginator,或者自己判断LastEvaluatedKey并传入ExclusiveStartKey继续查询。
scan的用法类似,同样有1MB分页限制。如果必须scan大表,建议加上FilterExpression减少返回数据量,并优先使用并行scan提高吞吐,但在业务高峰期要谨慎,scan会迅速吃光表的读容量。
四、批量操作与resource、client的选择
resource提供了batch_writer,这是它相对client的一大亮点。batch_writer会自动处理批量写入的25条限制、自动重试失败请求,写大量数据时非常方便:
with table.batch_writer() as batch:
for i in range(1000):
batch.put_item(
Item={
'user_id': f'u{i}',
'name': f'用户{i}',
'age': 20 + (i % 30)
}
)
# 退出with块时自动提交并重试,无需手动分批对比之下,client的batch_write_item需要你自己切分每批25条、检查UnprocessedItems并手动重试,代码量多且容易写错。所以在纯写入场景,batch_writer几乎是首选。
那什么时候应该退回client呢?主要有几种情况:需要调用resource没有封装的高级特性(如事务API transact_write_items、全局表的副本配置、流相关操作);需要精确控制请求参数和响应格式;或者你的代码需要与非Python服务保持一致的API调用日志。事实上resource和client可以混用,通过table.meta.client就能拿到当前表绑定的client实例,无需重新创建连接,两者共享同一套凭证和HTTP连接池。
还有一个性能层面的建议:resource对象和Table对象都应该复用而不是每次请求都新建。boto3创建resource本身有一定开销,且每个resource持有独立的连接池。在Web服务中,正确做法是在应用启动时创建一次dynamodb = boto3.resource('dynamodb'),模块级共享,线程安全性由boto3内部保证。如果在每个请求处理函数里都调用一次boto3.resource,高并发下会明显拖慢响应速度,这是Lambda函数冷启动优化和Web服务中都要注意的点。
总结一下:boto3.resource('dynamodb')适合绝大多数日常开发场景,代码简洁、类型自动解包、批量写入省心;而client适合需要完整API能力或精细控制的场合。理解resource的延迟加载、Decimal类型、表达式占位符和分页机制这几个关键点,就能避开绝大多数常见错误,写出稳定高效的DynamoDB操作代码。
DynamoDBboto3dynamodb resource修改时间:2026-09-05 21:20:56