Skip to content

数据库迁移

在开发和维护数据库驱动的应用程序过程中,所使用的数据库结构会像源代码一样不断演变。例如,在应用程序开发过程中,可能发现需要一张新表;在应用程序部署到生产环境后,可能发现应该创建一个索引来提高查询性能;等等。由于数据库结构变更通常需要相应的源代码变更,Yii 支持所谓的数据库迁移功能,允许您以数据库迁移的形式追踪数据库变更,这些迁移与源代码一起进行版本控制。

以下步骤展示了团队在开发过程中如何使用数据库迁移:

  1. Tim 创建一个新的迁移(例如创建一张新表、修改列定义等)。
  2. Tim 将新的迁移提交到源代码控制系统(例如 Git、Mercurial)。
  3. Doug 从源代码控制系统更新他的仓库,获取新的迁移。
  4. Doug 将迁移应用到他的本地开发数据库,从而将数据库同步以反映 Tim 所做的更改。

以下步骤展示了如何将带有数据库迁移的新版本部署到生产环境:

  1. Scott 为包含一些新数据库迁移的项目仓库创建一个发布标签。
  2. Scott 将生产服务器上的源代码更新到该发布标签。
  3. Scott 将所有累积的数据库迁移应用到生产数据库。
mermaid
sequenceDiagram
    participant Tim
    participant VCS as Source control
    participant Doug
    participant LocalDB as Local development database
    participant Scott
    participant ProdServer as Production server
    participant ProdDB as Production database

    Tim->>Tim: Creates a new migration
    Tim->>VCS: Commits the new migration
    Doug->>VCS: Updates his repository and receives the new migration
    Doug->>LocalDB: Applies the migration locally

    Scott->>VCS: Creates a release tag with new migrations
    Scott->>ProdServer: Updates source code to the release tag
    Scott->>ProdDB: Applies accumulated migrations

Yii 提供了一套迁移命令行工具,允许您:

  • 创建新迁移;
  • 应用迁移;
  • 回滚迁移;
  • 重新应用迁移;
  • 显示迁移历史和状态。

所有这些工具都可以通过命令 yii migrate 访问。在本节中,我们将详细描述如何使用这些工具完成各种任务。

TIP

迁移不仅可以影响数据库模式,还可以调整现有数据以适应新模式、创建 RBAC 层次结构或清理缓存。

NOTE

在使用迁移操作数据时,您可能会发现使用 Active Record 或实体类会很有用, 因为其中已经实现了一些逻辑。但请记住,与迁移中编写的代码(其本质是永久不变的)不同, 应用程序逻辑是会发生变化的。因此,在迁移代码中使用 Active Record 或实体类时, 源代码中逻辑的变更可能会意外破坏现有的迁移。出于这个原因,迁移代码应保持与 其他应用程序逻辑相互独立。

初始配置

要使用迁移,请安装 yiisoft/db-migration 包:

shell
make composer require yiisoft/db-migration

在项目根目录中创建一个用于存储迁移的目录 src/Migration

将以下配置添加到 config/common/params.php

php
'yiisoft/db-migration' => [
    'newMigrationNamespace' => 'App\\Migration',
    'sourceNamespaces' => ['App\\Migration'],
],

如果您想将迁移放置在其他位置,可以在 newMigrationPath 中定义路径。如果您要应用的迁移来自多个来源(例如外部模块),可以使用 sourcePaths 来定义这些来源。

您还需要配置数据库连接。有关为 PostgreSQL 配置的示例,请参阅 使用数据库

创建迁移

要创建一个新的空迁移,请运行以下命令:

sh
make shell
./yii migrate:create <name>

必填的 name 参数对新迁移进行简短描述。例如,如果迁移是关于创建一张名为 news 的新表,您可以使用名称 create_news_table 并运行以下命令:

make shell
./yii migrate:create create_news_table

NOTE

由于 name 参数将用作生成的迁移类名的一部分, 因此它应该只包含字母、数字和/或下划线字符。

上述命令将创建一个名为 src/Migration/M251225221906CreateNewsTable.php 的新 PHP 类文件。该文件包含以下代码,主要声明了一个带有骨架代码的迁移类:

php
<?php

declare(strict_types=1);

namespace App\Migration;

use Yiisoft\Db\Migration\MigrationBuilder;
use Yiisoft\Db\Migration\RevertibleMigrationInterface;

final class M251225221906CreateNewsTable implements RevertibleMigrationInterface
{
    public function up(MigrationBuilder $b): void
    {
        // TODO: Implement the logic to apply the migration.
    }

    public function down(MigrationBuilder $b): void
    {
        // TODO: Implement the logic to revert the migration.
    }
}

在迁移类中,您需要在 up() 方法中编写对数据库结构进行更改的代码。您也可以在 down() 方法中编写代码来回滚 up() 所做的更改。当您使用此迁移升级数据库时,将调用 up() 方法;当您降级数据库时,将调用 down() 方法。以下代码展示了如何实现迁移类来创建一张 news 表:

php
<?php

declare(strict_types=1);

namespace App\Migration;

use Yiisoft\Db\Migration\MigrationBuilder;
use Yiisoft\Db\Migration\RevertibleMigrationInterface;

final class M251225221906CreateNewsTable implements RevertibleMigrationInterface
{
    public function up(MigrationBuilder $b): void
    {
        $cb = $b->columnBuilder();

        $b->createTable('news', [
            'id' => $cb::uuidPrimaryKey(),
            'title' => $cb::string()->notNull(),
            'content' => $cb::text(),
        ]);
    }

    public function down(MigrationBuilder $b): void
    {
        $b->dropTable('news');
    }
}

上面的迁移构建器 $b 管理数据库模式,而列构建器 $cb 管理列类型。两者都支持使用抽象类型。当迁移应用到特定数据库时,抽象类型将被转换为相应的数据库物理类型和相应的 SQL 定义语句。

迁移构建器中可用的方法属于以下类型:

  • 原始查询
    • getDb — 获取数据库连接实例。
    • execute — 执行原始 SQL 查询。
  • 数据
    • insert / update / delete
    • batchInsert
    • upsert
  • 表和视图
    • createTable / renameTable / dropTable
    • truncateTable
    • addCommentOnTable / dropCommentFromTable
    • createView / dropView
    • addColumn / renameColumn / alterColumn / dropColumn
    • addCommentOnColumn / dropCommentFromColumn
  • 键和索引
    • addPrimaryKey / dropPrimaryKey
    • addForeignKey / dropForeignKey
    • createIndex / dropIndex

此外,还有一个 columnBuilder(),用于获取如上例所示的列构建器。该构建器具有定义各种列类型的静态方法:

  • 主键
    • primaryKey
    • smallPrimaryKey
    • bigPrimaryKey
    • uuidPrimaryKey
  • 布尔值
    • boolean
  • 数字
    • bit
    • tinyint
    • smallint
    • integer
    • bigint
    • flat
    • double
    • decimal
  • 字符串
    • char
    • string
    • text
  • 日期和时间
    • timestamp
    • datetime
    • datetimeWithTimezone
    • time
    • timeWithTimezone
    • date
  • 特殊类型
    • money
    • binary
    • uuid
    • array
    • structured
    • json
  • enum

以上所有方法都会创建一个基础类型,可通过以下附加方法进行调整:

  • null / notNull
  • defaultValue
  • unique
  • scale / size / unsigned
  • primaryKey / autoIncrement
  • check
  • comment
  • computed
  • extra
  • reference

不可逆迁移

并非所有迁移都是可逆的。例如,如果 up() 方法删除了一行数据,您可能无法在 down() 方法中恢复该行。有时,您可能懒得实现 down(),因为回滚数据库迁移并不常见。在这种情况下,您应该实现只有 up()Yiisoft\Db\Migration\MigrationInterface

事务性迁移

在执行复杂的数据库迁移时,确保每个迁移作为一个整体要么全部成功要么全部失败非常重要,这样数据库才能保持完整性和一致性。为了实现这个目标,建议在迁移的 implements 中添加 TransactionalMigrationInterface,从而自动将每个迁移的数据库操作封装在事务中。

因此,如果 up()down() 方法中的任何操作失败,所有之前的操作将自动回滚。

注意:并非所有数据库管理系统都支持事务。而且某些数据库查询无法放入事务中。有关一些示例, 请参阅 隐式提交

生成迁移

该命令提供了一种方便的方式来生成部分代码,而不必手动编写迁移。

shell
make shell
./yii migrate:create -- my_first_table --command=table --fields=name,example --table-comment=my_first_table

这将生成以下内容:

php
<?php

declare(strict_types=1);

namespace App\Migration;

use Yiisoft\Db\Migration\MigrationBuilder;
use Yiisoft\Db\Migration\RevertibleMigrationInterface;
use Yiisoft\Db\Migration\TransactionalMigrationInterface;

/**
 * Handles the creation of table `my_first_table`.
 */
final class M251227095006CreateMyFirstTableTable implements RevertibleMigrationInterface, TransactionalMigrationInterface
{
    public function up(MigrationBuilder $b): void
    {
        $columnBuilder = $b->columnBuilder();

        $b->createTable('my_first_table', [
            'id' => $columnBuilder::primaryKey(),
            'name',
            'example',
        ]);

        $b->addCommentOnTable('my_first_table', 'my_first_table');
    }

    public function down(MigrationBuilder $b): void
    {
        $b->dropTable('my_first_table');
    }
}

可用的命令有:

  • create — 空迁移。
  • table — 创建一张表。使用 --fields 指定字段列表。也可以指定类型,例如 id:primaryKey,name:string:defaultValue("Alex"),user_id:integer:foreignKey,category_id2:integer:foreignKey(category id2)
  • dropTable — 删除一张表。
  • addColumn — 添加一列。
  • dropColumn — 删除一列。
  • junction — 创建关联表。使用 --and 指定第二张表。

应用迁移

要将数据库升级到最新结构,应使用以下命令应用所有可用的新迁移:

./yii migrate:up

此命令将列出迄今为止尚未应用的所有迁移。如果您确认要应用这些迁移,它将按时间戳顺序依次运行每个新迁移类的 up() 方法。如果任何迁移失败,该命令将退出,而不会应用其余的迁移。

对于每个成功应用的迁移,该命令将在名为 migration 的数据库表中插入一行,以记录迁移的成功应用。这将允许迁移工具识别哪些迁移已经应用,哪些还没有。

有时,您可能只想应用一个或几个新迁移,而不是所有可用的迁移。您可以在运行命令时指定要应用的迁移数量来实现此目的。例如,以下命令将尝试应用接下来的三个可用迁移:

./yii migrate:up --limit=3

回滚迁移

要回滚(撤销)之前已应用的一个或多个迁移,可以运行以下命令:

./yii migrate:down            # revert the most recently applied migration
./yii migrate:down --limit=3  # revert the most 3 recently applied migrations
./yii migrate:down --all      # revert all migrations

注意:并非所有迁移都是可逆的。尝试回滚此类迁移将导致错误并停止 整个回滚过程。

重做迁移

重做迁移是指先回滚指定的迁移,然后再重新应用。可以按如下方式操作:

./yii migrate:redo            # redo the last applied migration
./yii migrate:redo --limit=3  # redo the last 3 applied migrations
./yii migrate:redo --all      # redo all migrations

注意:如果迁移不可逆,您将无法重做它。

列出迁移

要列出哪些迁移已应用、哪些未应用,可以使用以下命令:

./yii migrate/history            # showing the last 10 applied migrations
./yii migrate:history --limit=5  # showing the last 5 applied migrations
./yii migrate:history --all      # showing all applied migrations

./yii migrate:new                # showing the first 10 new migrations
./yii migrate:new --limit=5      # showing the first 5 new migrations
./yii migrate:new --all          # showing all new migrations

从 Yii 2.0 升级

Yii 2.0 中的迁移与 yiisoft/db-migration 包不兼容,migration 表也不兼容。

一个可行的解决方案是使用结构转储并重命名旧的 migration 表。在初次执行迁移时,将创建一个具有新字段的新 migration 表。此后数据库模式的所有变更都使用新的 migration 组件应用,并记录在新的迁移表中。