数据迁移
Payload 提供了一套完整的迁移控制功能供你使用。迁移命令可以通过项目目录中的 npm run payload
命令来访问。
确保你的 package.json
文件中有一个名为 "payload" 的 npm 脚本。
{
"scripts": {
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
}
}
注意:你需要通过你正在使用的包管理器来运行 Payload 迁移, 因为 Payload 不应该被全局安装在你的系统上。
迁移文件内容
Payload 将所有创建的迁移存储在你指定的文件夹中。默认情况下,迁移文件存储在 ./src/migrations
目录下。
一个迁移文件有两个导出项 - 一个是 up
函数,在执行迁移时被调用;另一个是 down
函数,当迁移因某些原因未能成功完成时会被调用。up
函数应包含你在迁移中尝试进行的所有更改,而 down
函数理想情况下应该撤销你所做的任何更改。
以下是一个迁移文件的示例:
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
// 在此处执行对数据库的更改。
// 你可以通过参数访问 `payload`,
// 所有操作都在 TypeScript 中完成。
}
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
// 如果 `up` 函数失败,执行任何需要回滚更改的操作
}
使用事务
当运行迁移时,每个迁移都会在一个新的事务中自动执行。你只需要将 req
对象传递给任何本地 API 或直接数据库调用,例如 payload.db.updateMany()
,就能在事务内进行数据库更改。如果没有抛出错误,事务会在你的 up
或 down
函数运行完成后提交。如果迁移过程中出现任何错误或提交失败,系统会捕获异常并中止事务。这样即使迁移失败,数据库也不会发生任何更改。
直接在事务中使用数据库
此外,你可以完全绕过 Payload 的抽象层,在活动事务中直接对底层数据库执行操作:
MongoDB:
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
export async function up({
session,
payload,
req,
}: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection
.find({ session })
.toArray()
}
Postgres:
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
SQLite:
在 SQLite 中,事务默认是禁用的。了解更多。
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
迁移目录
每个数据库适配器都有一个可选属性 migrationDir
,用于覆盖你希望存储/读取迁移文件的位置。如果未指定此属性,Payload 将检查默认路径,并可能通过搜索常见位置(如 ./src/migrations
、./dist/migrations
、./migrations
等)来尽力找到你的迁移目录。
所有数据库适配器都应实现类似的迁移模式,但根据适配器及其特定需求会有细微差异。以下是你的数据库适配器应支持的所有迁移命令列表。
命令
迁移
migrate
命令将运行所有尚未执行的迁移。
npm run payload migrate
创建
在迁移目录中创建一个新的迁移文件。你可以选择为要创建的迁移命名。默认情况下,迁移将使用时间戳命名。
npm run payload migrate:create 可选名称
标志:
--skip-empty
:对于 Postgres,跳过"未检测到模式更改。是否要创建一个空白迁移文件?"的提示,这在 CI 中生成迁移时很有用。--force-accept-warning
:接受所有命令提示,即使没有模式更改也会创建空白迁移。
状态
migrate:status
命令将检查迁移状态,并输出一个表格,显示哪些迁移已运行,哪些尚未运行。
payload migrate:status
npm run payload migrate:status
回退
回滚最后一批迁移。
npm run payload migrate:down
刷新
回滚所有已运行的迁移,然后重新运行它们。
npm run payload migrate:refresh
重置
回滚所有迁移。
npm run payload migrate:reset
Fresh(全新迁移)
清空数据库中的所有实体,并从头开始重新运行所有迁移。
npm run payload migrate:fresh
何时运行迁移
根据你使用的数据库适配器不同,迁移工作流程可能会有细微差异。
在关系型数据库中,必须为非开发环境的数据库执行迁移。但对于 MongoDB,你可能只需要偶尔运行迁移(甚至可能完全不需要)。
MongoDB#mongodb-migrations
在 MongoDB 中,通常只有在需要改变数据库结构,并且有大量现有数据需要从结构 A 转换到结构 B 时,才需要运行迁移。
这种情况下,你可以通过运行 pnpm payload migrate:create
创建迁移,然后编写将文档转换为新结构所需的逻辑。之后,你可以在构建/部署前通过 CI 运行迁移,或者通过本地计算机使用生产数据库连接字符串运行 pnpm payload migrate
命令,直接针对生产数据库执行迁移。
Postgres#postgres-migrations
在 Postgres 等关系型数据库中,迁移更为重要,因为每次添加新字段或新集合时,都需要更新数据库结构以匹配 Payload 配置(否则在尝试读写数据时会报错)。
这意味着 Payload 的 Postgres 用户需要全面熟悉整个迁移工作流程。以下是针对开发数据库本地工作、创建迁移,以及在部署前对生产数据库运行迁移的常见工作流程概述。
1 - 在本地使用 push 模式工作
Payload 利用 Drizzle ORM 强大的 push
模式,在开发模式下自动将数据变更同步到你的数据库。默认情况下,此功能是启用的,这也是在本地开发时使用 Postgres 和 Payload 的推荐工作流程。
你可以禁用此设置,仅使用迁移来管理本地开发数据库(向 Postgres 适配器传递 push: false
),但如果禁用,在运行开发模式时可能会频繁遇到错误。这是因为 Payload 已经更新到新的数据结构,但你的本地数据库尚未更新。
因此,我们建议保持 push
为默认设置,并将本地开发数据库视为沙盒环境。
有关 push 模式和在开发中进行原型设计的更多信息,点击此处。
Payload 的典型工作流程是:构建 Payload 配置、安装插件,并在开发模式下进行开发 - 让 Drizzle 将你的变更推送到本地数据库。完成后,你可以创建迁移。
但重要的是,你不需要对开发数据库运行迁移,因为 Drizzle 已经将你的变更推送到数据库了。
警告:不要在本地开发数据库中混用 "push" 和迁移。如果你在本地使用 "push",然后尝试迁移,Payload 会抛出警告,提示这两种方法不应交替使用。
2 - 创建迁移
完成 Payload Config 的修改后,你可以创建迁移。最佳实践是在创建迁移前完成特定任务或完整构建某个功能。
当你准备好时,可以运行 pnpm payload migrate:create
命令,该命令将为你执行以下步骤:
- 我们会查找所有现有迁移,并自动生成将你的 schema 从先前状态转换到 Payload Config 新状态所需的 SQL 变更
- 然后我们会在你的
/migrations
文件夹中创建一个新的迁移文件,其中包含所有需要运行的 SQL 语句
不过,我们不会立即为你运行这个迁移。
提示:Payload 创建的迁移本质上是相对程序化的,因此应该不会有意外情况。但在提交生成的迁移文件前,最好总是仔细检查迁移文件的内容。
3 - 设置构建流程以运行迁移
通常,你需要在生产环境构建 Payload 之前运行迁移。这通常在 CI 流水线中完成,在 Payload Cloud、Vercel 或 Netlify 等平台上可以通过指定构建脚本来配置。
在 package.json
中设置的一组常见脚本(用于在 CI 中运行迁移)可能如下所示:
"scripts": {
// 用于开发模式运行
"dev": "next dev --turbo",
// 为生产环境构建 Next + Payload 应用
"build": "next build",
// 为方便起见与 Payload CLI 的"连接"
// 这帮助你运行 `pnpm payload migrate:create` 等命令
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
// 这个命令就是你设置的 `build script`
// 注意它先运行 `payload migrate` 然后运行 `pnpm build`?
// 这将在 CI 中构建前为你运行所有迁移,
// 针对你的生产数据库
"ci": "payload migrate && pnpm build",
},
在上面的示例中,我们指定了一个 ci
脚本,可以将其用作我们部署到生产环境的平台中的"构建脚本"。
这要求你的构建流水线能够连接到数据库,它会在开始构建过程之前简单地运行 payload migrate
命令。通过调用 payload migrate
,Payload 会自动按照创建顺序执行 /migrations
文件夹中尚未针对生产数据库执行的任何迁移。
如果迁移失败,部署将被拒绝。但现在,通过设置构建脚本来运行迁移,你就一切就绪了!下次部署时,你的 CI 将为你执行所需的迁移,你的数据库将与 Payload Config 所需的结构保持同步。
在生产环境中运行迁移
在某些情况下,你可能希望在服务器启动时运行迁移(migrations)。由于在构建时无法访问数据库连接或其他类似原因,在构建时运行迁移可能不可行。
如果你使用的是长期运行的服务器或容器,其中 Node 服务器启动一次后保持初始化状态,那么你可能更倾向于在服务器启动时运行迁移,而不是在 CI 中运行。
要在运行时初始化期间运行迁移,你可以将迁移传递给数据库适配器,使用 prodMigrations
键,如下所示:
// 从 Payload 为你生成的 `index.ts` 文件中导入迁移
import { migrations } from './migrations'
import { buildConfig } from 'payload'
export default buildConfig({
// 你的配置在这里
db: postgresAdapter({
// 你的适配器配置在这里
prodMigrations: migrations,
}),
})
如上所示传递迁移将告诉 Payload(仅在生产环境中)在完成 Payload 初始化之前执行任何需要运行的迁移。这对于只在启动时初始化 Payload 的长期运行服务来说是理想的选择。
警告: 如果指示 Payload 在生产环境中运行迁移,这可能会减慢 Vercel 等平台上的无服务器冷启动速度。通常,此选项应仅用于长期运行的服务器/容器。
环境特定配置与迁移
你的配置可能包含特定于环境的设置(例如,仅在生产环境中启用某个插件)。如果在生成迁移时不考虑环境因素,可能会导致差异和问题。在本地运行迁移时,Payload 使用的是开发环境,这可能会遗漏生产环境的特定配置。同样,在生产环境中运行迁移可能会遗漏开发环境的特定实体。
这是一个容易被忽视的问题,因此在处理迁移时,请留意配置中的任何环境特定逻辑。
解决方法:
- 在生成迁移文件后,手动更新它以包含任何环境特定的配置。
- 在本地生成迁移时,临时启用所需的生产环境变量,以捕获必要的更新。
- 为每个环境使用单独的迁移文件,确保在对应环境中执行正确的迁移。