DynamoDB 实战入门:我为什么放弃了 MySQL 做这个项目

DynamoDB 实战入门:我为什么放弃了 MySQL 做这个项目 DynamoDB 实战入门我为什么放弃了 MySQL 做这个项目做一个用户行为日志系统一开始用 MySQL后来发现 DynamoDB 更适合。这里记录下选型思路和踩坑过程。需求背景我们有个内部工具需要记录用户的操作日志——谁在什么时候做了什么。霂求很简单写多读少每秒几百到几千条写入偶尔查询查询模式固定按用户 ID 查最近 7 天的操作不需要复杂关联单表就够了弹性要求高活动期间写入量会暴增 10 倍一开始用 RDS MySQL跑了一段发现问题写入高峰期 CPU 飘到 90%需要手动扩容日志数据增长快定期要清理和分区为了抗住高峰得开一个比较大的实例但平时利用率不到 10%后来试了 DynamoDB——Serverless、按需计费、自动扩缩。正好解决这些痛点。DynamoDB 基础概念用关系数据库的思维理解 DynamoDB大概是这样的对应关系MySQLDynamoDBDatabase无直接建表TableTableRowItemColumnAttributePRIMARY KEYPartition Key Sort Key但本质区别很大DynamoDB 是 Key-Value 存储不支持 JOIN没有 Schema除了主键查询必须走主键或索引。表设计操作日志表主键设计表名: user_actions Partition Key (PK): user_id (String) Sort Key (SK): timestamp#action_id (String)Partition Key 决定数据分布在哪个分区Sort Key 决定分区内的排序。为什么 Sort Key 用timestamp#action_idtimestamp让同一用户的操作按时间排序加上action_id防止同一秒多条记录冲突用 Python 操作 DynamoDB建表importboto3 dynamodbboto3.resource(dynamodb,region_nameap-northeast-1)tabledynamodb.create_table(TableNameuser_actions,KeySchema[{AttributeName:user_id,KeyType:HASH},{AttributeName:sk,KeyType:RANGE}],AttributeDefinitions[{AttributeName:user_id,AttributeType:S},{AttributeName:sk,AttributeType:S}],BillingModePAY_PER_REQUEST# 按需模式)print(f表状态:{table.table_status})BillingModePAY_PER_REQUEST是关键——不用预估容量写多少算多少。写入数据importtime,uuid tabledynamodb.Table(user_actions)table.put_item(Item{user_id:u_12345,sk:f{int(time.time())}#{uuid.uuid4().hex[:8]},action:click_button,page:/dashboard,ip:192.168.1.100,details:{button_id:submit,form:login}})DynamoDB 的 Item 可以嵌套 Map 和 List不用提前定义 Schema。查询某用户最近操作fromboto3.dynamodb.conditionsimportKey responsetable.query(KeyConditionExpressionKey(user_id).eq(u_12345)Key(sk).begins_with(17),ScanIndexDirectionFalse,# 倒序Limit20)foriteminresponse[Items]:print(f{item[sk]}-{item[action]}-{item[page]})Query 走的是主键索引速度稳定在个位数毫秒。批量写入高峰期要批量写入用batch_writerwithtable.batch_writer()asbatch:foreventinevents:batch.put_item(Item{user_id:event[user_id],sk:f{event[timestamp]}#{uuid.uuid4().hex[:8]},action:event[action],page:event[page]})batch_writer自动分批每批最多 25 条自动处理限流重试。踩坑记录坑 1热分区如果所有写入都集中在一个 Partition Key比如系统用户system这个分区会成为热点导致限流。解决给高频 Partition Key 加随机后缀# 把 system 分散到 system_0, system_1, ..., system_9shardrandom.randint(0,9)pkfsystem_{shard}查询时需要并行查 10 个分片但写入不会被限流了。坑 2Scan 很贵千万别用table.scan()做日常查询。Scan 会扫描全表200GB 数据扫一遍要几分钟而且按读取量计费。原则所有查询都走 Query主键或 GSI永远不在生产环境用 Scan。坑 3GSI 的最终一致性Global Secondary IndexGSI的数据是异步复制的存在毫秒级延迟。如果写入后立刻用 GSI 查询可能查不到刚写的数据。解决写入后需要立刻读取的场景用主表 Query 而不是 GSI。坑 4Item 大小限制单个 Item 最大 400KB。如果操作日志里包含大段文本比如请求 body要注意这个限制。超过的话把大字段存到 S3DynamoDB 里存 S3 地址。成本对比跑了 3 个月的实际数据方案月成本RDS db.r5.large之前的方案~$190DynamoDB 按需模式~$25省了 87%。主要原因是 RDS 要 24/7 付费DynamoDB 只按实际读写量计费。什么时候不该用 DynamoDB说实话DynamoDB 不是万能的需要复杂 SQL 查询JOIN、子查询、聚合→ 用 RDS/Aurora数据关系复杂多对多关联很多→ 关系数据库更合适查询模式不确定今天按 A 查明天按 B 查→ DynamoDB 需要提前设计索引不够灵活单条记录很大 400KB→ 考虑 S3 或其他存储总结DynamoDB 适合写入密集、查询模式固定、弹性要求高的场景。操作日志、用户画像、IoT 数据、游戏排行榜这类需求用 DynamoDB 比关系数据库省心省钱。核心要记住的表设计的关键是选好 Partition Key 和 Sort Key所有查询都走 Query别用 Scan按需模式省钱但要注意热分区Item 最大 400KB大数据放 S3以上代码基于亚马逊云科技 DynamoDB Python boto3 验证通过。 DynamoDB 免费套餐包含每月 25GB 存储和足够的读写容量入门阶段不花钱。