Prisma 数据导入导出实战:基于 NDF 的备份、迁移与克隆完整指南
Prisma 数据导入导出实战:基于 NDF 的备份、迁移与克隆完整指南
Prisma 通过一种专用的中间数据格式——Normalized Data Format(NDF)——来承载所有数据的导入与导出操作。本文以官方 Database Workflows 教程(见 docs/1.10/03-Tutorials2/07-Database-Workflows/01-Data-Export-&-Import.md)为主线,完整演示"创建服务 → 写入种子数据 → 导出 NDF → 部署新 stage → 导入 NDF"的全流程,并结合本仓库 CLI 源码(prisma-cli-core 中的 Import/Export 命令实现)深入讲解 NDF 的结构规范、prisma export/prisma import 的底层原理(游标分页、断点续传、数据校验)以及原始 Import/Export API 的调用方式。读完本文,你将能够独立完成 Prisma 服务的数据备份、数据库克隆与跨 stage 数据迁移。
数据流动:SQL / MongoDB / JSON → NDF → Prisma
在 Prisma 中,导入与导出始终以 NDF 为"中间媒介"。整体数据流如下:
+--------------+ +----------------+ +------------+
| +--------------+ | | | |
| | | | | | | |
| | SQL | | (1) transform | NDF | (2) chunked upload | prisma |
| | MongoDB | | +--------------> | | +-------------------> | |
| | JSON | | | | | |
| | | | | | | |
+--------------+ | +----------------+ +------------+
+--------------+
两个关键步骤中:
- (1)transform:把 MySQL、MongoDB、JSON 等具体数据源转换为 NDF。根据 docs/1.10/04-Reference/06-Data-Import-&-Export/01-Data-Import.md 的说明,这一步目前需要手动完成(仓库文档明确标注"not available yet",CLI 尚不支持直接从 MySQL/MongoDB/Firebase 导入)。
- (2)chunked upload:把 NDF 数据分批上传到 Prisma 服务,既可以通过
prisma importCLI 命令,也可以调用原始的/importHTTP API。
教程:完整走一遍数据导出与导入
以下步骤将创建一个小型 Prisma 服务(包含一对 User–Post 关系),写入种子数据后导出为 NDF,再部署到新的 stage 并导入数据,最终得到一个数据完全相同的"克隆"服务。
第一步:创建 Prisma 服务
在任意目录下执行:
prisma init import-example
当 CLI 询问使用哪种模板时,选择 Minimal setup: database-only。命令会生成一个名为 import-example 的新目录,其中包含根配置文件 prisma.yml 与数据模型定义文件 datamodel.graphql。
接下来更新数据模型,加入一个一对多关系。将 datamodel.graphql 的内容改为:
type User {
id: ID! @unique
name: String!
posts: [Post!]!
}
type Post {
id: ID! @unique
title: String!
author: User!
}
第二步:用 seed 机制写入初始数据
在 import-example 目录中新建 seed.graphql,写入以下 mutation(创建一个名为 Sarah 的用户及三篇关联文章):
mutation {
createUser(data: {
name: "Sarah",
posts: {
create: [
{ title: "GraphQL is awesome" },
{ title: "It really is" },
{ title: "How to GraphQL is the best GraphQL tutorial" }
]
}
}) {
id
}
}
然后修改 prisma.yml,通过 seed 属性告诉 CLI 这个文件的存在:
service: import-example
stage: dev
datamodel: datamodel.graphql
# to enable auth, provide
# secret: my-secret
disableAuth: true
seed:
import: seed.graphql
执行部署,让 CLI 在部署服务时自动执行 seed.graphql 中的 mutation:
prisma deploy
当 CLI 询问部署到哪个 cluster 时,从公共集群中选择 prisma-eu1 或 prisma-us1(教程注明:使用 Docker 本地部署时 seeding 同样生效)。
部署完成后,打开 GraphQL Playground 发送以下查询即可验证数据已写入:
{
users {
name
posts {
title
}
}
}
预期返回:
{
"data": {
"users": [
{
"name": "Sarah",
"posts": [
{ "title": "GraphQL is awesome" },
{ "title": "It really is" },
{ "title": "How to GraphQL is the best GraphQL tutorial" }
]
}
]
}
}
第三步:以 NDF 导出数据
在 import-example 目录执行:
prisma export
该命令会生成名为 export-__TIMESTAMP__.zip 的文件(__TIMESTAMP__ 为导出时刻的时间戳),压缩包内的文件即为 NDF 格式。NDF 的详细结构规范见 Normalized Data Format 参考文档。
从源码看,默认导出文件名正是由时间戳生成的:在 cli/packages/prisma-cli-core/src/commands/export/index.ts 中,exportPath 默认为 export-${new Date().toISOString()}.zip,且若路径不以 .zip 结尾会自动补上后缀。
第四步:部署到新的 stage
现在创建服务的"克隆":打开 prisma.yml,将 stage 改为新值,并移除 seed 与 cluster 属性:
service: import-example
stage: test
datamodel: datamodel.graphql
# to enable auth, provide
# secret: my-secret
disableAuth: true
再次执行 prisma deploy 部署到新的 test stage(cluster 仍选 prisma-eu1 或 prisma-us1)。此时 test 服务是全新的、空的。
第五步:导入 NDF 数据
使用 prisma import 将之前导出的 zip 数据导入新服务。注意将 __DATA__ 替换为导出文件的真实路径(例如 export-2018-01-13T19:28:25.921Z.zip):
prisma import --data __DATA__
导入完成后,为当前 test stage 打开 GraphQL Playground,再次发送上面那条 users 查询,即可看到 Sarah 及其三篇文章——数据成功迁移到了新 stage。
深入 NDF:三种 value type 与文件结构
NDF 是描述 JSON 结构的一种规范,把数据按三种 value type 拆分:
- Nodes:节点标量字段的数据(即数据库记录本身)
- Lists:节点的列表字段(scalar list)数据
- Relations:通过关系字段把两个节点连接起来的数据
每个 NDF JSON 文档是一个包含两个键的对象:
valueType:取值"nodes"、"lists"或"relations"values:符合对应 value type 的数据数组
以下示例均基于这个数据模型:
type User {
id: String! @unique
firstName: String!
lastName: String!
hobbies: [String!]!
partner: User
}
nodes
结构为 { "_typeName": STRING, "id": STRING, "<scalarField1>": ANY, ... }。_typeName 指向数据模型中的 SDL 类型名,<scalarFieldX> 是该类型的标量字段名。例如导入两个 User 节点的标量值:
{
"valueType": "nodes",
"values": [
{"_typeName": "User", "id": "johndoe", "firstName": "John", "lastName": "Doe"},
{"_typeName": "User", "id": "sarahdoe", "firstName": "Sarah", "lastName": "Doe"}
]
}
lists
结构为 { "_typeName": STRING, "id": STRING, "<scalarListField>": [ANY] }。与标量字段不同,每个对象只能为列表字段提供值。例如导入两个 User 节点的 hobbies 列表字段:
{
"valueType": "lists",
"values": [
{"_typeName": "User", "id": "johndoe", "hobbies": ["Fishing", "Cooking"]},
{"_typeName": "User", "id": "sarahdoe", "hobbies": ["Biking", "Coding"]}
]
}
relations
结构与前两者不同:values 数组中的每个元素本身是一对节点(固定包含两个元素的数组),用于描述一条关系。例如通过 partner 关系字段连接 johndoe 与 sarahdoe:
{
"valueType": "relations",
"values": [
[
{ "_typeName": "User", "id": "johndoe", "fieldName": "partner" },
{ "_typeName": "User", "id": "sarahdoe", "fieldName": "partner" }
]
]
}
目录与文件命名约定
当通过 CLI 导入目录(或 zip)时,NDF 文件必须按类型放入同名目录 nodes、lists、relations 中;文件必须是 .json 后缀,并按从 1 开始的递增序号命名,允许前导零,如 1.json、01.json 或 0000001.json(见 Data Import 文档)。典型结构如下:
.
├── data
│ ├── lists
│ │ ├── 0001.json
│ │ ├── 0002.json
│ │ └── 0003.json
│ ├── nodes
│ │ ├── 0001.json
│ │ └── 0002.json
│ └── relations
│ └── 0001.json
├── datamodel.graphql
└── prisma.yml
随后执行 prisma import --data data 即可导入整个 data 目录。
CLI 命令参考与实现原理
prisma export
在 cli/packages/prisma-cli-core/src/commands/export/index.ts 中,prisma export 支持的 flags 如下:
| Flag | 短写 | 说明 |
|---|---|---|
--path |
-p |
导出 zip 文件的保存路径;缺省时自动生成 export-<ISO 时间戳>.zip |
--env-file |
-e |
注入环境变量的 .env 文件路径 |
--project |
— | Prisma 定义文件(prisma.yml)的路径 |
导出完成后 CLI 还会提示下一步命令:prisma import --data <exportPath>。
底层实现(Exporter.ts)可以总结为三步:
- 按类型分页下载:依次下载
nodes、lists、relations三类数据。每次请求携带一个四元组游标cursor: { table, row, field, array }(初始全部为 0),服务端返回数据与新的游标;客户端循环请求直到游标四项之和小于 0 为止,即"游标耗尽"。 - 分片写盘:每批数据写入
.export/<fileType>/目录下、按序号命名的 JSON 文件(000001.json起,6 位前导零),文件内容为{ "valueType": <fileType>, "values": [...] }。 - 打包压缩:用
archiver将整个.export目录打成 zip,随后清理临时目录。
prisma import
在 cli/packages/prisma-cli-core/src/commands/import/index.ts 中,prisma import 支持的 flags 如下:
| Flag | 短写 | 说明 |
|---|---|---|
--data |
-d |
必填。包含 NDF 数据的 zip 文件或目录路径 |
--env-file |
-e |
注入环境变量的 .env 文件路径 |
--project |
-p |
Prisma 定义文件(prisma.yml)的路径 |
命令入口会做两类预检查:--data 指向的路径必须存在;且该路径必须以 .zip 结尾或是一个目录,否则直接报错。CLI 命令的完整参考见 prisma import 命令文档。
底层实现(Importer.ts)的导入管线为:
- 解压:若是 zip 文件,用
adm-zip解压到.import/临时目录。 - 校验:通过 Validator.ts 对三类文件逐一做结构校验——要求存在
values数组、节点必须带_typeName与字符串类型的id、缺失/多余字段会报错、标量值按ID/String/Int/Float/DateTime/Boolean/Json及数据模型中枚举进行类型检查。 - 分类型上传:按
nodes → lists → relations的顺序逐文件调用上传接口,每个文件上传成功后更新state.json记录进度;异常时打印错误并清理临时目录。
值得强调的是,state.json 的存在使得导入可中断并续传:若导入中途失败,重新执行 prisma import 会跳过已导入的文件(日志输出 Skipping file ... (already imported))。此外,导入操作不是幂等的——重复导入总是新增数据而不会更新已有节点,同一 id 的节点被导入多次会导致未定义行为,可能损坏服务(见 Data Import 文档 的警告框)。
直接调用原始 Import / Export API
除 CLI 外,还可以直接调用 HTTP API。/export 与 /import 端点位于服务 HTTP 地址之下,例如 http://localhost:60000/my-app/dev/export 或 https://database.prisma.sh/my-app/prod/import。单次请求传输的 NDF JSON 数据上限为 10 MB,且必须携带 JWT 认证 token(放在 HTTP Authorization: Bearer ... 头中)。
原始导出 API
向 /export 发送 POST 请求,body 为:
{
"fileType": "nodes",
"cursor": {
"table": 0,
"row": 0,
"field": 0,
"array": 0
}
}
cursor 描述从数据库何处开始导出。每次响应都会返回一个新游标,其状态有两种(详见 Data Export 文档):
- 已终止:
table、row、field、array全部为-1,表示导出完成。 - 未终止:任一值不是
-1,表示已达 10 MB 上限,应把返回的游标作为下一次请求的输入继续导出。
curl 示例:
curl 'http://localhost:60000/my-app/dev/export' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer __JWT_AUTH_TOKEN__' \
-d '{"fileType":"nodes","cursor":{"table":0,"row":0,"field":0,"array":0}}' \
-sSv
原始导入 API
向 /import 发送 POST 请求,body 为 NDF JSON(valueType 取 nodes / lists / relations)。curl 示例:
curl '__SERVICE_ENDPOINT__/import' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer __JWT_AUTH_TOKEN__' \
-d '{"valueType":"nodes","values": __DATA__ }' \
-sSv
需要注意的是:原始 import API 不做任何数据校验,导入非法数据会导致未定义行为;只有走 CLI 时才会执行前述 Validator 的基础校验。官方给出的最佳实践是:以"相同数据模型的服务上做一次 export"得到的数据作为参考,来确保手工构造的导入数据合法。
注意事项与边界
围绕本仓库代码与文档,以下几点对实际使用尤为关键:
- 文件大小建议:通过 CLI 导入时,每个 NDF 文件建议不超过 1 MB,否则可能触发超时(见 Data Import 文档);直接调 API 时单请求上限为 10 MB。
- 文档型数据库暂不支持:在 export/index.ts 与 import/index.ts 中,若服务配置的
databaseType为document(MongoDB),CLI 会直接报错,提示使用 MongoDB 原生的mongodump/mongorestore。 - 手动转换是必经环节:把 SQL / MongoDB / JSON 数据转换成 NDF 目前没有自动化工具,需要按 NDF 规范手工构造 JSON。
- 命令维护状态:CLI 源码中的输出警告明确说明
prisma export/prisma import命令后续不再继续开发,建议关系型数据库(MySQL、PostgreSQL)的数据迁移改用各自的原生工具(如mysqldump/mysqlimport、pg_dump/pg_restore)。在当前版本中,这两个命令仍可正常完成 NDF 层面的备份、迁移与 stage 克隆工作。 - 同一 schema 才能互导:NDF 数据只能导入到数据模型一致的服务中,这也是"克隆服务"场景(如向
devstage 灌入测试数据)最推荐的使用方式。