DynamoDB 并行扫描
并行扫描把一次 Scan 拆成 N 个独立的 Scan 请求,每个认领表的一个 Segment,从而让多个工作进程同时读取它。它是 Scan API 提供的、唯一一种读取整张表能快过单个分区吞吐量所允许速度的办法。
什么是 DynamoDB 并行扫描?
DynamoDB 并行扫描把一次 Scan 拆成 N 个独立请求,每个通过 Segment 和 TotalSegments 认领表的一个 Segment,从而让多个工作进程并发读取它。它是 Scan API 提供的、唯一一种读取整张表能快过单个分区吞吐量所允许速度的办法——但它仍然是一次完整读取,所以你要为扫描到的每一个条目付费。
- 顺序
Scan一次读取一个分区——它的速度被限制在单个分区的吞吐量上,无论表有多大。 Segment+TotalSegments把读取分片给TotalSegments个工作进程;每个工作进程并行扫描自己那一片。- DynamoDB 对做哈希来分配段,所以各片可能一头沉——工作进程更多并不总意味着更快。
- 它仍然是一次
Scan:你要为读取每一个条目付费,而一次庞大的并行扫描会把表的吞吐量从你实时流量脚下抽干。
为什么顺序 Scan 慢
从 SQL 过来,整表读取感觉像是一次流式操作。在 DynamoDB 里并非如此。表的数据分布在许多物理分区上,但单次 Scan 是一次一个地走过它们,每页 1 MB。
这意味着一次普通的 Scan 在任一时刻只能从一个分区的吞吐量预算里拉取——即便这张表铺在几十个有空闲容量的分区上。表越大,它爬得越久。(AWS:并行扫描)
用 Segment 和 TotalSegments 拆分读取
并行扫描修复了这个瓶颈。你挑一个工作进程数量,把 TotalSegments 设成那个数,再给每个工作进程一个不同的、从零开始的 Segment。每个工作进程发出自己的 Scan;DynamoDB 并发地为它们服务。
Worker 0 → Scan Segment=0 TotalSegments=4
Worker 1 → Scan Segment=1 TotalSegments=4
Worker 2 → Scan Segment=2 TotalSegments=4
Worker 3 → Scan Segment=3 TotalSegments=4
每个工作进程仍然用 LastEvaluatedKey 独立分页——它从第一页到最后一页都拥有自己的段。应用把这四条流重新拼接在一起。你现在一次读取四个分区的吞吐量,而不是一个。
一个实算示例:夜间导出
假设你运营一张遥测表 sensor-readings。每个条目是来自某个现场设备的一次读数:
PK = "DEVICE#a83f" (partition key — the device id)
SK = "TS#2026-06-22T03:14" (sort key — ISO timestamp)
batteryMv = 3120
tempC = 41.8
firmwareTag = "fw-7.2.1"每晚一个 cron 作业把整张表转储到 S3,供分析数仓使用。对 80 GB 做一次顺序 Scan 要花好几个小时,却几乎没动用你预置的读取容量。于是你把它铺开到八个工作进程上:
Scan sensor-readings Segment=0 TotalSegments=8 ConsistentRead=false
…
Scan sensor-readings Segment=7 TotalSegments=8 ConsistentRead=false
八个工作进程,八个段,一次整表读取大约快了八倍。如果你只需要最近的读数,加一个 FilterExpression,在行到达线路之前先丢掉旧的时间戳——在表达式构建器里构造并检查那个表达式:
FilterExpression: begins_with(SK, :today)DynamoDB 如何把条目分配给段
这里是让人栽跟头的部分。DynamoDB 通过对分区键做哈希把每个条目分配给某个段——不是按行数,也不是按字节数。
所以共享同一个 PK 的每个条目都落在同一个段里。在 sensor-readings 里,DEVICE#a83f 的所有读数都归一个工作进程,无论那个设备有多少个时间戳、或它的有多大。(AWS:并行扫描)
各段不均匀。一个工作进程可能拥有三台话痨设备、上百万条读数;另一个可能抽到一片空的。把 TotalSegments 调更高也帮不上忙,如果你的分区键扎堆的话——你只是添了一些闲着的工作进程,干等着那个热的。均匀的键分布才是让这种扇出真正划算的东西。
运行之前先看清读取成本
并行扫描是一次吞吐量事件,不是免费午餐。诚实的问题是"我正要读取这整张表的多大一部分?"——在 DynoTable 运行整表读取之前,它会用一个确认对话框拦住你,显示表的近似大小和条目数量以及一条读取容量警告,然后在读取进行时流式呈现实时的"已扫描条目数"进度,好让那个夜间作业不会给你惊喜。
陷阱,以及何时别费这个劲
- 吞吐量悬崖。一次高
TotalSegments的扫描能在几秒内吃光表的全部读取容量,把实时流量饿死。在一张服务用户的表上,用Limit参数给每个工作进程限速,或在非高峰时段扫描。(AWS:并行扫描) - 对某个访问模式来说它仍然是错的工具。并行扫描是用于刻意的整表作业的——导出、回填、迁移。如果你伸手去用它来回答一个反复出现的查询,那是一个建模信号:加一个 GSI,把它变成一次 Query。
- PartiQL 里的
SELECT *是同一次扫描换了层皮。它会编译成一次顺序Scan。当你确实需要跨条目分析时——一个GROUP BY、一个JOIN、一次聚合——DynoTable 的 SQL Workbench 会在一个有界的结果集上于客户端运行这些,而不是猛捶那张表。 - 强一致性会让账单翻倍。
Scan默认使用的读取。对于导出,除非每一页都必须反映最新的写入,否则就让ConsistentRead=false——并且要注意,即便一次强一致的扫描也横跨几分钟,所以它不是一个时间点快照(要那个就用 PITR/Export)。
后续步骤
对键建模,好让日常读取永远不需要扫描——从单表设计和 Query vs Scan 开始。当一次整表作业确实是正确的选择时,试试 DynoTable,用一个预先的大小与成本警告和实时扫描进度来运行整表读取。