数据库迁移
页面摘要: 数据库迁移在模式同步之前运行一次性脚本,以在升级过程中保留数据。迁移文件导出一个
up()函数,按字母顺序运行一次。在随后的模式同步期间,Strapi 会删除此前由它管理、但已不再存在于内容类型模式中的表、列、索引和外键。
数据库迁移的存在是为了对数据库运行一次性查询,通常是在升级 Strapi 应用时修改表结构或数据。这些迁移在应用启动时自动运行,并在 Strapi 启动时也会执行的自动模式同步之前执行。
数据库迁移处于实验阶段。该功能仍在开发中,将持续更新和改进。在此期间,欢迎在 GitHub Discussions 或社区 Discord 上寻求帮助。
理解数据库迁移文件
迁移通过存储在 ./database/migrations 中的 JavaScript 迁移文件运行。
Strapi 会自动检测迁移文件,并在下次启动时按字母顺序运行一次。每个新文件仅执行一次。
启动时发生了什么 {#startup-sequence}
理解操作顺序有助于预测你的数据会发生什么。每次启动时,Strapi 执行以下步骤:
- Strapi 加载模式:内容类型和组件被转换为数据库模型,然后验证关系。
- Strapi 运行待处理的迁移:你在
/database/migrations中的迁移文件先运行,随后是 Strapi 自身的内部迁移。每个迁移在自己的事务中运行,并且已应用的迁移会被跟踪,因此不会运行两次。 - Strapi 同步数据库模式:它将内容类型模式与数据库进行比较,然后应用差异。首先创建表和列,然后删除不再属于模式的表,最后修改剩余的表。
- Strapi 持久化新模式:生成的模式被存储在数据库中,并成为下次启动时的参考。
迁移在模式同步之前运行,因此 up() 函数看到的仍是数据库的先前状态。请针对旧模式编写迁移,而不是针对你要迁移到的目标模式。
当没有待处理迁移且自上次启动以来模式未发生变化时,步骤 2 和 3 会被完全跳过。Strapi 通过比较内容类型模式来检测变更,而不是检查数据库,因此直接对数据库所做的手动更改不会被检测或回滚。
模式同步期间的数据丢失 {#data-loss}
在模式同步期间,Strapi 会删除此前由它管理、且不再匹配内容类型模式的表、列、索引和外键。这一过程自动发生,没有任何警告或确认提示,并且在开发和生产环境中表现一致。因此,从代码中删除一个内容类型会在下次启动时删除其表及其数据。
Strapi 从未管理过的表(例如你直接在数据库中自己创建的表)则保持不变。
迁移是跨此类变更保留数据的方式:在模式同步移除旧结构之前,使用迁移来复制或转换数据。
Strapi 不支持向下迁移。如果你需要回滚迁移,必须手动完成。向下迁移已在计划中,但目前没有时间表。
forceMigration 数据库配置参数 控制此行为。将其设置为 false 会跳过所有删除操作。新模式仍会被记录为参考,因此被跳过删除的对象将不再被 Strapi 跟踪,如果你之后将该参数设回 true,它也不会被删除。runMigrations 参数仅控制你在 /database/migrations 中的文件是否运行,对模式同步没有影响。
迁移文件应导出一个 up() 函数,用于升级时(例如添加新表 my_new_table)。
up() 函数在数据库事务中运行,这意味着如果迁移期间某个查询失败,整个迁移将被取消,且不会对数据库应用任何更改。如果在迁移函数内创建了另一个事务,它将充当嵌套事务。
没有用于手动执行数据库迁移的 CLI。
创建迁移文件
创建迁移文件:
-
在
./database/migrations文件夹中,创建一个以迁移日期和名称命名的新文件(例如2022.05.10T00.00.00.name-of-my-migration.js)。请确保文件名遵循此命名模式,因为文件的字母顺序决定了迁移的运行顺序。 -
将以下模板复制粘贴到之前创建的文件中:
'use strict'
async function up(knex) {}
module.exports = { up };
- 通过在
up()函数内添加实际的迁移代码来填充模板。up()接收一个 Knex instance(Knex 实例),该实例已处于事务状态,可用于运行数据库查询。
迁移文件示例
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 实例
如果用户选择不直接使用 Knex 进行迁移,而是使用 Strapi 实例,重要的是用 strapi.db.transaction() 包裹迁移代码。否则,如果发生错误,迁移可能无法回滚。
使用 Strapi 实例的迁移文件示例
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();
});
},
};
读取迁移进度心跳
Strapi 的部分内部迁移会处理大量数据,可能运行数分钟。为了表明它们仍在工作,它们会记录一条周期性进度行,限制为每 60 秒一条消息:
[document-id] still running (120s) · articles 12000/450000
前缀标识正在运行的内部迁移,计数器显示目前已处理了多少行。这些消息是追加而非就地重写的,因此在日志文件中仍清晰可读。
进度心跳仅由 Strapi 的内部迁移发出。你自己的迁移文件中不可用。
使用 TypeScript 代码处理迁移
默认情况下,使用 TypeScript 时,Strapi 会在源目录而非构建目录中查找迁移文件。这意味着除非你将 Strapi 配置为查找正确的位置,否则 TypeScript 迁移不会被正确找到和执行。
要在 Strapi 中启用 TypeScript 迁移,你需要在数据库配置中将 useTypescriptMigrations 参数设为 true。该设置告诉 Strapi 在构建目录而非源目录中查找迁移。
以下是在数据库设置中的配置方式:
JavaScript
module.exports = ({ env }) => ({
connection: {
// Your database connection settings
},
settings: {
useTypescriptMigrations: true
}
});
TypeScript
export default ({ env }) => ({
connection: {
// Your database connection settings
},
settings: {
useTypescriptMigrations: true
}
});
此外,如果你想在 TypeScript 迁移的同时继续使用现有的 JavaScript 迁移,可以在 tsconfig.json 文件的编译器选项中设置 allowJs: true,如 数据库配置文档 中所述。