入门阅读约 5 分钟

如何把 DynamoDB 表导出为 CSV(4 种方法)

DynamoDB 没有原生的"导出为 CSV"按钮。每个值回来时都裹在 DynamoDB 的 marshalled JSON里——{"S": "..."}{"N": "123"}{"M": {...}}——而一张表可以装着嵌套的 map、list 和 set,没有一目了然的扁平列表示。所以"把 DynamoDB 导出为 CSV"其实是两个问题:把项取出来,然后把带类型的 JSON 压平成行。无论控制台还是托管导出,都不会替你做第二步。

本指南先讲两步都替你做完的方式,再覆盖三条 AWS 工具链路线,以及各自何时才是正确选择。

如何把一张 DynamoDB 表导出为 CSV?

最快的方式是在 DynoTable 里打开表,筛选到你要的行,一键把结果导出为 CSV——类型描述符已解开、嵌套值已替你压平(方法 1)。改用 AWS 工具自己搭的话:小表用 CLI 扫描再用 jq 重塑(方法 2),大表用托管的 S3 导出(方法 3),需要自定义整形时写一段短脚本(方法 4)。

  • 筛选过 / 整形过的 CSV(一部分列、只要某些项):图形界面导出(方法 1)或脚本。托管的 S3 导出给你的是_整张_表,不带筛选。
  • 小表、临时需求、只有终端:AWS CLI scan + jq方法 2)。嵌套属性一出现就不够用了。
  • 大表(GB 级以上):DynamoDB 导出到 S3方法 3),然后转换那份转储。它异步运行且不消耗读取容量——但它输出的是 DynamoDB JSON,不是 CSV。

方法 1:DynoTable 中的一键导出

DynoTable 把导出当作浏览的一部分:运行或筛选一个查询,按 ⌘⇧E(或标签页工具栏里的 Export(导出)按钮),然后挑选要输出什么:

  • 格式:CSV(每个项一行,带表头——set 在单元格内序列化为 JSON 数组)、JSONNDJSON。JSON/NDJSON 可以是解开后的纯形式(unmarshalled,直接 "count": 3),也可以是 marshalled DynamoDB-JSON,用于需要保留大数字的无损往返。
  • 范围:当前已加载的行、只是你的选择,或完整的筛选匹配——你的查询匹配到的每一个项,直接从 DynamoDB 流式取出,而不只是屏幕上那些。最后这个正是托管 S3 快照做不到的筛选导出。
  • 目的地:剪贴板或文件。要一次性抓取,右键点一行然后 Copy as…(复制为…)就能把 CSV、JSON、NDJSON 或 DynamoDB-JSON 直接放上剪贴板,不用弹窗。
DynoTable 的导出对话框:格式(CSV / JSON / NDJSON)、从选择到完整筛选匹配的范围,以及剪贴板或文件目的地。
DynoTable 的导出对话框:格式(CSV / JSON / NDJSON)、从选择到完整筛选匹配的范围,以及剪贴板或文件目的地。

那些击穿下面各条 DIY 路线的压平问题都已被处理:类型描述符被解开、嵌套的 map 和 list 被压平、重命名后的列会流入 CSV 表头。大型导出会脱离页面在后台运行——逐行流式写到磁盘,扛得住切换标签页甚至应用重载——所以一次几 GB 的导出永远不必塞进内存。

它是一个桌面 DynamoDB 客户端,就是你已经在用来浏览表的那个工具;看看它与其他 DynamoDB 图形界面的对比。什么时候_不_该用它?当导出必须在流水线里无人值守地运行时——那是脚本路线(方法 4)的地盘。

方法 2:AWS CLI scan + jq

对小表,你可以扫描它并用 jq 重塑输出。一次 Scan 读取表中的每一个项,按最多 1 MB 一页返回;CLI 会自动替你跟完分页(AWS 文档:扫描表)。

aws dynamodb scan --table-name MyTable --output json \
  | jq -r '.Items[] | [.id.S, .name.S, .price.N] | @csv' \
  > out.csv

坑就在那行 jq 里:你得手写 .id.S.name.S.price.N——伸手越过每个属性的类型描述符SNBBOOLMLSSNSBS)去拿原始值。对一张只有三个字符串列的扁平表还应付得来。但一旦出现下面这些就散架了:

  • 嵌套的 map/list——{"M": {...}}{"L": [...]} 没有可以压进去的单一列;@csv 会噎住,否则就得你手动把单元格 JSON 编码。
  • Set——{"SS": ["a","b"]} 是数组,不是标量。
  • 稀疏属性——DynamoDB 是无 schema 的,项 A 可能有 price 而项 B 没有。你写死的列清单会悄悄丢列或错位。

而且根本没有 --output csv——CLI 的输出格式是 jsonyamltexttableoff,没有一个理解 DynamoDB 类型。所以你仍然需要 jq(或脚本)来剥掉类型标签。这就是"用 AWS CLI 把 DynamoDB 表导出为 CSV"越过最简单场景后就永远不是一行命令的根本原因。

要用这种方式导出一张更大的整表而不至于花上一整天,用 --segment / --total-segments 把扫描并行化(AWS 文档:并行扫描——DynamoDB"通过对每个项的分区键应用哈希函数来把项分配到_段_",所以各段可能不均匀),并读一读分页,免得停在第一个 1 MB 页。

方法 3:DynamoDB 导出到 S3(大表)

对任何真正有规模的表,托管的导出到 Amazon S3 才是正确工具。它从你时间点恢复(PITR)窗口内的任意时刻导出一份快照——所以表上必须先启用 PITR,否则导出会以 PointInTimeRecoveryUnavailableException 失败——异步运行,且不消耗读取容量单位,因此对表的吞吐量和可用性零影响(AWS 文档:"导出是异步的,不消耗读取容量单位(RCU),对表的性能和可用性没有影响";"你需要在表上启用 PITR 才能使用导出功能")。这也是控制台的 Exports to S3 操作在底层触发的东西:控制台只是同一个 API 的前端,所以它带着同样的 PITR 要求和同样的 JSON 输出。

aws dynamodb export-table-to-point-in-time \
  --table-arn arn:aws:dynamodb:us-east-1:123456789012:table/MyTable \
  --s3-bucket my-export-bucket \
  --export-format DYNAMODB_JSON

唯一的坑:S3 导出不输出 CSV。它只写 DynamoDB JSONAmazon Ion,以 gzip 压缩文件的形式、按 JSON-lines 格式(每行一个项)落盘,外加清单文件(AWS 文档:导出输出格式——数据文件写成 .json.gz,"格式是 JSON lines",随附 manifest-summary.json / manifest-files.json)。之后你仍然需要一个转换步骤:

  • Athena / Glue 可以直接读取导出的 DynamoDB JSON——把一张表指向那个 S3 前缀,然后从一条 SELECT 写出 CSV(这就是常见的"DynamoDB 导出到 S3 再到 CSV"流水线)。AWS 指出"许多 AWS 服务,如 Athena 和 AWS Glue,会自动解析这种格式"(导出输出格式)。
  • 自己动手——解压那些 .gz 文件,逐行解析 JSON,然后把它压平(和其他所有方法面对的是同一个压平问题)。

它还是一份全表快照:没有服务端筛选可以只导出部分项。如果你需要一个子集,要么事后在 Athena 里筛,要么改用图形界面(方法 1)/ 脚本。

方法 4:一段小脚本(boto3 / Node)

当导出必须无人值守地运行时——夜间任务、CI 步骤——一段小脚本胜过上面的一切。它的优势在于 AWS SDK 会替你解开(unmarshall)带类型的 JSON:boto3 的 resource 接口和 JS SDK 的 DynamoDBDocumentClient 返回的是纯的 {"price": 2000} 而不是 {"price": {"N": "2000"}}(boto3 的 resource 接口让"数据类型处理变得隐式",见 AWS Python 指南;JS 的 DocumentClient"将带注解的响应数据转换为原生 JavaScript 类型",见 @aws-sdk/lib-dynamodb)。

import boto3, csv

table = boto3.resource("dynamodb").Table("MyTable")
rows, resp = [], table.scan()
rows += resp["Items"]
while "LastEvaluatedKey" in resp:                  # paginate to the end
    resp = table.scan(ExclusiveStartKey=resp["LastEvaluatedKey"])
    rows += resp["Items"]

with open("out.csv", "w", newline="") as f:
    w = csv.DictWriter(f, fieldnames=["id", "name", "price"])
    w.writeheader()
    for r in rows:
        w.writerow({k: r.get(k) for k in w.fieldnames})

有两个 SDK 无法替你做的决定仍归你管:如何把嵌套的 map/list 压平成列(把单元格 JSON 编码?还是把键展开成点路径?),以及稀疏属性怎么办(这里缺失的键经由 r.get(k) 变成空单元格)。还有别丢掉 LastEvaluatedKey 循环——单次 scan() 调用只返回第一个 1 MB 页,没有它你会悄无声息地只导出表的一部分。

和方法 2 同样的告诫:这里的全表 scan 仍然消耗读取容量、仍然与线上流量竞争。大表请优先用方法 3,然后重塑那份转储。

坑:DynamoDB JSON 与扁平 CSV

无论选哪种方法,DynamoDB 数据模型与扁平 CSV 之间的这几处错位都会咬你一口:

  • 类型描述符。原始 API / CLI / S3 导出的输出把每个值都包起来({"S": "..."}{"N": "123"})。你要么经由 SDK 解开它,要么自己剥掉描述符。完整集合是 SNBBOOLNULLMLSSNSBS——参见 DynamoDB 数据类型
  • 嵌套的 map 和 listML)最深可以嵌套 32 层AWS 文档:数据类型——list 和 map"可以相互嵌套,以表示最深 32 层的复杂数据结构"),并且没有天然的单列形式。提前决定:把单元格 JSON 编码,还是把嵌套键展开成点路径列(address.city)。
  • SetSS/NS/BS)是无序集合,不是标量——AWS 警告"集合内各值的顺序不会被保留"(数据类型)——所以压平成带分隔符的字符串,并且不要依赖元素顺序。
  • 稀疏属性。DynamoDB 是无 schema 的,两个项可以有不同的属性。不存在固定列集;对所有项的键做并集,否则列会错位。这是单表设计的直接后果——一张表装着多种实体形状。
  • 分页。Scan(和 Query)每次调用最多返回 1 MB。不循环处理 LastEvaluatedKey,你就会悄无声息地只导出第一页。参见分页
  • 数字精度。DynamoDB 的数字最多携带 38 位精度,并以字符串形式在网络上传输(AWS 文档:数据类型:"数字最多可以有 38 位精度";"所有数字都以字符串形式通过网络发送到 DynamoDB");电子表格软件可能把长数字或 ID 强转成浮点数并丢失位数。让它们保持为文本。

常见问题

把 DynamoDB 表导出为 CSV 最快的方式是什么? 一个替你做压平的图形界面:在 DynoTable 里筛选表,按 ⌘⇧E,选 CSV,再挑一个范围——从选中的行到筛选匹配到的每一个项,从 DynamoDB 流式取出。类型描述符和嵌套值都会自动处理。

如何用 AWS CLI 把 DynamoDB 表导出为 CSV? 扫描表并用 jq 重塑输出(方法 2):aws dynamodb scan → 用 jq 剥掉每个值的类型描述符 → @csv。不存在理解 DynamoDB 的 --output csv,所以剥类型标签永远得自己来,而且它在嵌套 map、list 和 set 上会崩。

能从 AWS 直接把 DynamoDB 表导出为 CSV 吗? 一步到位不行。控制台和托管的 S3 导出产出的都是 DynamoDB JSON 或 Amazon Ion,绝不是 CSV。你永远需要一个转换步骤——CLI + jq、脚本、在 S3 转储之上跑 Athena/Glue,或者一个替你压平的图形界面。

如何在不影响生产的情况下导出整张 DynamoDB 表?导出到 S3 功能(方法 3)。它异步运行且不消耗读取容量单位,所以不与线上流量竞争——不像 Scan 会计入你表的吞吐量(AWS 文档)。它要求启用 PITR,并且导出的是整张表,不是筛选后的子集。

如何把 DynamoDB 以 CSV 形式导出到 S3? 托管导出只会把 DynamoDB JSON / Ion 写到 S3,所以"到 CSV"是第二跳:把导出前缀注册为一张 Athena(或 Glue)表,然后从一条 SELECT 写出 CSV。不存在 --export-format CSV

如何把 DynamoDB 导出到 Excel? 先导出为 CSV(上面任一方法),然后在 Excel 里打开这份 CSV——注意让长的数字 ID 保持为文本,免得被强转成浮点数。DynamoDB 没有直接的 .xlsx 导出;DynoTable 可以把当前视图直接保存为可供电子表格使用的 CSV。

为什么我导出的 JSON 里到处都是 {"S": ...}{"N": ...} 那是 DynamoDB 的 线上格式——每个值都被打上类型描述符标签。写 CSV 之前,用 SDK、DynamoDB JSON 转换器或图形界面把它解开。无论数据来自 API、CLI 还是 S3 导出,线上格式都是同一个。

DynoTable 浏览、筛选并把你自己的表导出为 CSV,或者先在 JSON 转换器里解开一份 DynamoDB JSON 样本。

更新于