Skip to main content

数据库迁移

🌐 Database migrations

Page summary:

数据库迁移会在模式同步之前运行一次性脚本,以在升级过程中保留数据。迁移文件会导出一个 up() 函数,并按字母顺序运行一次。在随后的模式同步过程中,Strapi 会删除先前管理的、但在内容类型模式中不再使用的表、列、索引和外键。

数据库迁移用于对数据库运行一次性查询,通常是在升级 Strapi 应用时修改表的结构或数据。当应用启动时,这些迁移会自动运行,并且会在 Strapi 启动时执行的自动模式同步之前执行。

🌐 Database migrations exist to run one-time queries against the database, typically to modify the tables structure or the data when upgrading the Strapi application. These migrations are run automatically when the application starts and are executed before the automated schema sync that Strapi also performs on boot.

🚧 Experimental feature

数据库迁移是实验性的。此功能仍在开发中,将持续更新和改进。与此同时,你可以随时在 GitHub Discussions 或社区 Discord寻求帮助。

理解数据库迁移文件​

🌐 Understanding database migration files

迁移是使用存储在 ./database/migrations 中的 JavaScript 迁移文件运行的。

🌐 Migrations are run using JavaScript migration files stored in ./database/migrations.

Strapi 会自动检测迁移文件,并在下一次启动时按照字母顺序运行它们。每个新文件都会执行一次。

🌐 Strapi automatically detects migration files and run them once at the next startup in alphabetical order. Every new file is executed once.

启动时会发生什么​

🌐 What happens on startup

理解运算顺序有助于预测你的数据会发生什么。在每次启动时,Strapi 会执行以下步骤:

🌐 Understanding the order of operations helps predict what happens to your data. On every startup, Strapi performs the following steps:

  1. Strapi 加载架构:内容类型和组件被转换成数据库模型,然后验证关联关系。
  2. Strapi 会运行待处理的迁移:你在 /database/migrations 中的迁移文件会先运行,然后是 Strapi 自己的内部迁移。每个迁移都会在自己的事务中运行,并且已经应用的迁移会被记录,因此它们不会运行两次。
  3. Strapi 会同步数据库模式:它会比较内容类型的模式和数据库,然后应用差异。先创建表和列,然后删除不再属于模式的表,最后修改剩下的表。
  4. Strapi 会保存新的模式:生成的模式会存储在数据库中,并在下次启动时成为参考。
Note

迁移在模式同步之前运行,所以 up() 函数仍然会看到数据库的旧状态。编写迁移时应针对旧的模式,而不是你要迁移到的模式。

🌐 Migrations run before the schema sync, so an up() function still sees the database in its previous state. Write migrations against the old schema, not the one you are migrating to.

Note

如果没有待处理的迁移并且架构自上次启动以来没有变化,那么步骤2和3将会完全跳过。Strapi 通过比较内容类型的架构来检测变化,而不是检查数据库,所以直接在数据库中进行的手动更改不会被检测到或回滚。

🌐 Steps 2 and 3 are skipped entirely when there is no pending migration and the schema has not changed since the last startup. Strapi detects changes by comparing the content-types schemas, not by inspecting the database, so manual changes made directly to the database are not detected or reverted.

在模式同步期间丢失数据​

🌐 Data loss during the schema sync

在模式同步期间,Strapi 会删除它之前管理的、但现在不再符合内容类型模式的表、列、索引和外键。这是自动发生的,不会有任何警告或确认提示,而且在开发和生产环境中都是一样的。因此,从代码中删除一个内容类型,下次启动时就会删除它的表和数据。

🌐 During the schema sync, Strapi drops the tables, columns, indexes, and foreign keys that it previously managed and that no longer match the content-types schemas. This happens automatically, without any warning or confirmation prompt, and identically in development and in production. Deleting a content-type from your code therefore deletes its table, and its data, on the next startup.

Strapi从未管理过的表,比如你自己直接在数据库里创建的表,会保持不变。

🌐 Tables that Strapi has never managed, such as tables you created yourself directly in the database, are left untouched.

迁移是应对这种变化的方式:在模式同步删除旧结构之前,使用它们来复制或转换数据。

🌐 Migrations are the way to preserve data across such changes: use them to copy or transform data before the schema sync removes the old structure.

Warning

Strapi 不支持回退迁移。如果你需要撤销一次迁移,就得手动操作。目前计划支持回退迁移,但尚未有具体时间表。

🌐 Strapi does not support down migrations. If you need to revert a migration, you have to do it manually. Down migrations are planned, but no timeline is currently available.

Tip

forceMigration 数据库配置参数 控制这个行为。把它设置为 false 会跳过所有的删除操作。新的模式仍然会被记录为参考,所以被跳过删除的对象会停止被 Strapi 跟踪,如果你之后把参数再设回 true,它也不会被删除。runMigrations 参数只控制你自己在 /database/migrations 中的文件是否执行,对模式同步没有影响。

🌐 The forceMigration database configuration parameter controls this behavior. Setting it to false skips every drop operation. The new schema is still recorded as the reference, so an object whose deletion was skipped stops being tracked by Strapi and is not dropped if you later set the parameter back to true. The runMigrations parameter only controls whether your own files in /database/migrations run, and has no effect on the schema sync.

迁移文件应导出函数 up(),该函数在升级时使用(例如添加一个新表 my_new_table)。

🌐 Migration files should export the function up(), which is used when upgrading (e.g. adding a new table my_new_table).

up() 函数在数据库事务中运行,这意味着如果迁移过程中查询失败,整个迁移将被取消,并且不会对数据库应用任何更改。如果在迁移函数中创建了另一个事务,它将作为嵌套事务运行。

🌐 The up() function runs in a database transaction which means if a query fails during the migration, the whole migration is cancelled, and no changes are applied to the database. If another transaction is created within the migration function, it will act as a nested transaction.

Note

没有 CLI 可以手动执行数据库迁移。

🌐 There is no CLI to manually execute the database migrations.

创建迁移文件​

🌐 Creating a migration file

创建迁移文件:

🌐 To create a migration file:

  1. 在 ./database/migrations 文件夹中,创建一个以日期和迁移名称命名的新文件(例如 2022.05.10T00.00.00.name-of-my-migration.js)。确保文件名遵循此命名规则,因为文件的字母顺序决定了迁移运行的顺序。
  2. 将以下模板复制并粘贴到之前创建的文件中:
'use strict'

async function up(knex) {}

module.exports = { up };
  1. 通过在 up() 函数中添加实际的迁移代码来填写模板。up() 接收一个 Knex instance,该 Knex instance 已处于事务状态,可用于执行数据库查询。
迁移文件示例
./database/migrations/2022.05.10T00.00.00.name-of-my-migration.js

module.exports = {
async up(knex) {
// You have full access to the Knex.js API with an already initialized connection to the database

// Example: renaming a table
await knex.schema.renameTable('oldName', 'newName');

// Example: renaming a column
await knex.schema.table('someTable', table => {
table.renameColumn('oldName', 'newName');
});

// Example: updating data
await knex.from('someTable').update({ columnName: 'newValue' }).where({ columnName: 'oldValue' });
},
};

使用 Strapi 实例进行迁移​

🌐 Using Strapi Instance for migrations

Warning

如果用户选择不直接使用 Knex 进行迁移,而是使用 Strapi 实例,那么将迁移代码用 strapi.db.transaction() 封装是很重要的。如果不这样做,可能会导致在发生错误时迁移无法回滚。

🌐 If a user opts not to use Knex directly for migrations and instead utilizes the Strapi instance, it is important to wrap the migration code with strapi.db.transaction(). Failure to do so may result in migrations not rolling back if an error occurs.

带有 Strapi 实例的迁移文件示例
./database/migrations/2022.05.10T00.00.00.name-of-my-migration.js
module.exports = {
async up() {
await strapi.db.transaction(async () => {
// Your migration code here

// Example: creating new entries
await strapi.documents('api::article.article').create({
data: {
title: 'My Article',
},
});

// Example: custom service method
await strapi.service('api::article.article').updateRelatedArticles();
});
},
};

读取迁移进度心跳​

🌐 Reading the migration progress heartbeats

Strapi 的一些内部迁移处理大量数据,可能运行几分钟。为了显示它们仍在工作,它们会记录一个定期的进度行,每 60 秒限制一条信息:

🌐 Some of Strapi's internal migrations process large amounts of data and can run for several minutes. To show that they are still working, they log a periodic progress line, throttled to one message every 60 seconds:

[document-id] still running (120s) · articles 12000/450000

这个前缀用来标识正在运行的内部迁移,计数器显示到目前为止已经处理了多少行。这些消息是追加的,而不是原地重写,所以它们在日志文件中仍然可以读到。

🌐 The prefix identifies the internal migration that is running, and the counters show how many rows have been processed so far. These messages are appended rather than rewritten in place, so they remain readable in log files.

Note

进度心跳只会由 Strapi 的内部迁移发出。在你自己的迁移文件中是用不到的。

🌐 Progress heartbeats are emitted by Strapi's internal migrations only. They are not available in your own migration files.

使用 TypeScript 代码处理迁移​

🌐 Handling migrations with TypeScript code

默认情况下,Strapi 在使用 TypeScript 时会在源目录而不是构建目录中查找迁移文件。这意味着除非你将 Strapi 配置为在正确的位置查找,否则 TypeScript 迁移文件将无法被找到和正确执行。

🌐 By default Strapi looks for migration files in the source directory rather than the build directory when using TypeScript. This means that TypeScript migrations won't be found and executed properly unless you configure Strapi to look in the right place.

要在 Strapi 中启用 TypeScript 迁移,你需要在数据库配置中将 useTypescriptMigrations 参数设置为 true。此设置会告诉 Strapi 在构建目录中查找迁移,而不是在源目录中查找。

🌐 To enable TypeScript migrations in Strapi, you need to set the useTypescriptMigrations parameter to true in your database configuration. This setting tells Strapi to look for migrations in the build directory instead of the source directory.

以下是如何在数据库设置中配置它:

🌐 Here's how to configure it in your database settings:

/config/database.js
module.exports = ({ env }) => ({
connection: {
// Your database connection settings
},
settings: {
useTypescriptMigrations: true
}
});

此外,如果你想在使用 TypeScript 迁移的同时继续使用现有的 JavaScript 迁移,可以在你的 tsconfig.json 文件的编译器选项中设置 allowJs: true,如数据库配置文档中所述。

🌐 Additionally, if you want to continue using existing JavaScript migrations alongside TypeScript migrations, you can set allowJs: true in your tsconfig.json file's compiler options, as mentioned in the database configuration documentation.