种子数据:v7 的 Seeding 新姿势
本教程共 54 篇 · 第 33 篇 · 更新于 2026-08-11 · 约 6 分钟阅读
本节目标:学会配置并运行种子脚本,写出可重复执行的幂等种子,并掌握 v7 中 seeding 的行为变化。
种子数据解决什么问题
种子数据(seeding)就是给数据库填充初始数据:应用启动必需的默认配置,或者开发环境用来调试的假数据。有了种子脚本,同事拉下代码、重置数据库后,一条命令就能得到一致的初始状态。
什么时候该用种子?两种典型场景:一是应用启动依赖的默认数据(默认语言、默认币种、管理员账号),二是开发联调需要的演示数据(几十个用户、几百篇文章)。前者进了生产也要跑,后者通常只在开发环境跑——靠下文的自定义参数区分环境。
配置:prisma.config.ts 的 migrations.seed
v6 时代种子命令写在 package.json 的 "prisma" 字段里,v7 挪进了 prisma.config.ts:
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "tsx prisma/seed.ts",
},
datasource: {
url: env("DATABASE_URL"),
},
});
migrations.seed 可以是任意命令:tsx 跑 TypeScript、node 跑编译后的 JS、psql file.sql 跑 SQL,甚至 bash seed.sh 调用其他语言。
prisma db seed 本身不做什么,只是把 migrations.seed 里的命令原样执行。所以换脚本语言、换脚本名都不影响使用方式——改配置即可。
种子脚本里不要写死连接字符串,统一从环境变量读——和应用的方式一致。.env 由 dotenv 加载,脚本开头 import "dotenv/config" 即可。路径用项目相对路径,别用绝对路径,保证同事克隆后能直接跑。
Warningv7 行为变化:
migrate dev和migrate reset不再自动运行种子脚本。想要数据,显式执行npx prisma db seed。忘了这一点,重置后看到空表别惊讶。
写第一个种子脚本
import "dotenv/config";
import { Pool } from "pg";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../generated/prisma/client";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter: new PrismaPg(pool) });
async function main() {
await prisma.user.upsert({
where: { email: "alice@prisma.io" },
update: {},
create: { email: "alice@prisma.io", name: "Alice" },
});
}
main()
.then(async () => {
await prisma.$disconnect();
await pool.end();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
await pool.end();
process.exit(1);
});
v7 里 Prisma Client 必须带驱动程序适配器(driver adapter),这和应用的写法一致。注意收尾:$disconnect 关客户端,pool.end() 关连接池,否则脚本会挂住不退出。
脚本结构固定三段:初始化(建连接池、实例化 Client)、main 函数里写数据、收尾(断开连接)。错误处理记得 process.exit(1),否则 CI 里脚本失败了退出码还是 0,流水线照常绿。
幂等:种子的第一原则
种子脚本会被反复执行。所谓幂等,就是跑一次和跑一百次结果一样。
最稳妥的写法是用 upsert:按唯一字段查找,存在就更新,不存在就创建。上面例子里的 update: {} 表示「存在则什么都不改」。
批量插入用 createMany 加 skipDuplicates,重复执行时跳过冲突行:
await prisma.user.createMany({
data: users,
skipDuplicates: true,
});
注意:skipDuplicates 依赖数据库的唯一约束。字段没有 @unique,重复数据照样插进去,幂等无从谈起——先建模,再谈幂等。
Tip另一种常见策略是开头先
deleteMany清空再插入。简单直接,但会重置自增 ID;有外键引用时要按子表先删、父表后删的顺序执行。deleteMany策略适合数据完全由种子生成的测试库,upsert策略适合要保留人工改动数据的库。两种都行,选一种保持一致。
判断种子是否幂等的标准:连续跑两次,第二次不报错、不产生重复数据。团队里任何人在任何时间点都能安全重跑,这条标准值得写进代码评审清单。
执行顺序也影响幂等:先造父表数据,再造子表;先清空子表,再清空父表。顺序反了,外键约束会直接报错。
造数据三件套
Faker 生成随机假数据,faker.seed(n) 固定随机种子,让每次生成的结果可复现:
import { faker } from "@faker-js/faker";
faker.seed(42);
const users = Array.from({ length: 50 }, () => ({
email: faker.internet.email(),
name: faker.person.fullName(),
}));
await prisma.user.createMany({ data: users });
CSV 导入脱敏后的真实数据:csv-parse 解析文件,每行按 Zod Schema 校验,最后 createMany 批量写入。适合导入存量数据。
工厂函数把造数逻辑封装成可复用的函数,测试和种子共用一份:
const userFactory = (overrides = {}) => ({
email: faker.internet.email(),
role: "USER",
...overrides,
});
数据量大时按 1k–10k 条分批 createMany;几百万行直接上数据库的 COPY 命令,别走 ORM。
关联数据:父子一起造
种子数据往往有依赖关系,父表先建、子表后建。嵌套写入一次搞定:
await prisma.user.create({
data: {
email: "demo@example.com",
posts: { create: [{ title: "欢迎" }, { title: "你好" }] },
},
});
posts.create 会连同用户一起创建,外键自动填上。已经存在的用户可以用 connect 复用,避免重复创建。嵌套 create 是一次事务,要么全部成功要么全部失败,不会留下半截数据。
用原始 SQL 补数据
少量固定数据可以直接用 $executeRaw 写进种子脚本,幂等靠 ON CONFLICT DO NOTHING:
await prisma.$executeRaw`
INSERT INTO "User" ("email", "name")
VALUES ('foo@example.com', 'Foo')
ON CONFLICT DO NOTHING;
`;
比起维护一个 .sql 文件再配 psql,这种方式省去了连接字符串和二进制的依赖。
带参数运行
prisma db seed 支持透传自定义参数,-- 后面接参数:
npx prisma db seed -- --environment test
脚本里用 Node 内置的 node:util 的 parseArgs 读取,一个脚本就能服务多套环境。参数化还能做增量种子:-- --only users 只重建用户表,省去全量重跑的时间。
Note种子脚本不是迁移,它只管数据、不管结构。结构变更永远走迁移,数据填充走 seed,两条线别混。
参考来源
- Prisma 官方文档:Seeding
- Prisma 官方文档:prisma db seed
- Mapagam:Seeding database data
- DevSheets:Database seeding