DynamoDB 的 TTL 机制本质上是让表自动清理带有过期时间戳的条目。启用后,你只需要在写入数据时给每条记录附带一个数值属性,用来存放 Unix epoch 秒级时间戳。DynamoDB 的后台进程会定期扫描这些属性,发现当前时间已经超过时间戳,就把对应记录标记为过期并删除。这个过程不需要应用参与,也不消耗表预置或按需写入容量。但要注意 TTL 删除不是精确到秒的实时动作,存在一定延迟,因此不能拿它当强一致的定时任务用。理解这个基础对后续配置很有帮助。

TTL 的工作机制与先决条件
TTL 并不要求你在建表时就规划好过期字段,表创建完成后也可以随时启用或关闭。这个机制依赖一个单一的数值属性,DynamoDB 会把这个属性当作过期时间的判断依据。属性类型必须是 Number,内容必须是以秒为单位的 Unix epoch 时间戳。比如 1700000000 对应 2024 年 11 月 14 日,这样的十进制数字才能被识别。如果你写入的是毫秒级时间戳、ISO 8601 字符串或布尔值,TTL 进程会直接忽略,数据不会被自动删除。
从内部实现看,DynamoDB 的存储节点会持续扫描各个分区的 TTL 属性。发现某条记录的过期时间早于当前时间后,系统会先将其标记为过期,再通过后台任务删除。删除操作不属于用户写入请求,因此不会消耗 WCU;同理,它也不会消耗数据读取容量。不过这并不意味着零成本:如果启用了 DynamoDB Streams,每次 TTL 删除都会产生一条流记录,Lambda 触发器就会被调用;如果表上有全局二级索引,索引中的投影数据也会被一并清理,但会有传播延迟。
启用 TTL 前有几点必须确认。一是过期属性名不能与主键冲突,也尽量不要和业务字段混用;二是要确认该属性已经存在或有写入计划,否则启用后老数据永远不过期;三是需要拥有 dynamodb:UpdateTimeToLive 权限才能修改 TTL 配置,查看状态则对应 dynamodb:DescribeTimeToLive。把这些先决条件理清后,再进行控制台或 API 操作会顺利很多。
启用 TTL 的完整操作步骤
对于大多数场景,最快的方式是通过 AWS 管理控制台操作。进入 DynamoDB 表列表,选择目标表,在操作或表设置里找到 TTL 管理入口,点击启用并输入属性名即可。控制台会显示 TTL 当前状态、属性名称以及是否已启用。如果属性名拼错,TTL 不会报错,只是之后没有记录会过期,所以建议复制代码中的属性名而不是手打。
自动化和脚本场景更推荐 AWS CLI。下面这条命令会在 SessionTable 表上启用 TTL,并指定 expires_at 属性作为过期时间戳字段:
aws dynamodb update-time-to-live \
--table-name SessionTable \
--time-to-live-specification "Enabled=true, AttributeName=expires_at"命令执行成功后会返回 TimeToLiveSpecification 信息,其中 Enabled 为 true、AttributeName 为 expires_at。如果属性在表结构中还从未使用过,也可以先执行这条命令,因为 TTL 只是标记哪个属性被扫描,并不会检查属性是否已经存在。之后再写入带该属性的数据即可。
Python 应用侧可以通过 boto3 执行同样的操作。与其在控制台手动点击,不如让部署脚本自动完成:
import boto3
client = boto3.client('dynamodb')
client.update_time_to_live(
TableName='SessionTable',
TimeToLiveSpecification={
'Enabled': True,
'AttributeName': 'expires_at'
}
)
print('TTL enabled on SessionTable')启用 TTL 后,写入数据时就要保证每条需要自动过期的记录都带有 expires_at 属性。下面示例写入一条 7 天后过期的会话记录:
import time
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('SessionTable')
expires_at = int(time.time()) + 7 * 24 * 60 * 60
table.put_item(
Item={
'session_id': 'abc123',
'user_id': 'u-1001',
'expires_at': expires_at
}
)
print('Item written with TTL attribute')在应用写入路径中,最好把过期时间的计算封装成一个公共函数,避免不同模块分别使用毫秒或秒造成混淆。对于不需要过期的数据,则不要设置 expires_at,TTL 只会处理存在该属性的记录。
处理删除延迟与索引流影响
TTL 的删除语义是最终一致的,DynamoDB 不承诺在过期时间到达的瞬间删除数据。官方文档给出的上限一般是 48 小时,实际往往几分钟内完成,但在分区热点或大规模过期场景下,部分记录可能更久。因此如果你的业务逻辑强依赖过期后立刻不可见,不能只靠 TTL。正确的做法是在查询时增加过滤条件,比如读取前判断 expires_at 是否已经小于当前时间,或者使用 KeyConditionExpression 或 FilterExpression 过滤过期项。这样即使后台尚未删除,也不会把过期数据返回给用户。
全局二级索引与 TTL 的关系也值得留意。TTL 删除主表记录后,包含该记录的 GSI 投影也会被异步清理。清理时间同样存在延迟,所以通过 GSI 查询时也可能短暂读到已过期数据。解决办法与主表相同,在查询语句中过滤 expires_at,或者在应用层做二次校验。如果索引投影里没有 expires_at 字段,建议将该字段加入投影,否则查询端无法过滤,只能等 TTL 和索引清理完成。
当表启用 DynamoDB Streams 时,TTL 删除会产生一条 REMOVE 事件。与其他用户删除不同,这类事件的 userIdentity 通常是服务身份,type 为 Service,principalId 为 dynamodb.amazonaws.com。下面是一个简化后的流记录示例:
{
"eventID": "1",
"eventName": "REMOVE",
"userIdentity": {
"type": "Service",
"principalId": "dynamodb.amazonaws.com"
},
"dynamodb": {
"Keys": {
"session_id": { "S": "abc123" }
},
"OldImage": {
"session_id": { "S": "abc123" },
"expires_at": { "N": "1700000000" }
}
}
}如果你的 Lambda 消费流用于审计、复制或触发业务动作,需要识别这类删除事件。例如订单过期删除不应触发用户通知,就可以在 Lambda 里先判断 userIdentity.principalId 是否为 dynamodb.amazonaws.com,再决定是否继续处理。这样可以避免把系统清理行为误当成用户操作。
TTL 设置的常见错误与调优
实际项目中容易踩的第一个坑是时间戳单位错误。Unix epoch 秒级时间戳和毫秒级时间戳相差三个数量级,如果误用毫秒,TTL 会认为数据要到很久以后才过期,结果永远清理不掉。另一个高频问题是属性名不一致:写入时用 expireAt,启用 TTL 时填 expires_at,或者大小写不一致,都会导致扫描不到数据。因此在配置和写入代码里最好统一使用同一个常量。
还有一个隐蔽问题是老数据回填。启用 TTL 只影响之后写入的记录,如果表里已经存在几百万条历史数据且没有 expires_at 属性,这些数据不会被自动删除。可以写一个批量回填脚本,扫描全表并为需要过期的记录补上时间戳。回填本身会消耗写入容量,需要在低峰期执行,并注意限速避免影响在线业务。
调优方面,建议监控 CloudWatch 中的 TimeToLiveDeletedItemCount 指标。这个指标反映 TTL 实际删除的条目数量,可以帮助判断删除任务是否在推进。另外,如果 TTL 删除触发了大量流事件,Lambda 并发可能短时升高,需要提前做好并发和重试策略。写入数据时也可以利用条件表达式避免写入已经过期或缺失过期时间的记录,例如:
from boto3.dynamodb.conditions import Attr
table.put_item(
Item={'session_id': 'abc123', 'expires_at': expires_at},
ConditionExpression='attribute_not_exists(session_id) AND expires_at > :now',
ExpressionAttributeValues={':now': int(time.time())}
)这个示例中的大于号在 DynamoDB 条件表达式中是合法操作符,读者替换成自己的字段名即可。通过监控、回填和条件写入配合,TTL 能为临时数据清理省下大量维护成本。

