注意: yii从版本1.1.6以后才开始支持数据库迁移特性。

和源码一样,数据库的结构也随着我们开发和维护数据库驱动应用而不断壮大.例如,在开发期间,我们可能想要添加一个新的表; 或者在应用投入生产期以后,我们可能会意识到需要在某个列上添加索引. 跟踪这些数据库结构的改变(被称作迁移)和操作源代码一样很重要.如果源码和数据库不同步了,可能这个系统都会中断。正是因为这个原因,Yii框架提供了数据库迁移工具,以便跟踪数据库迁移历史,应用新的迁移,或者恢复旧的迁移.

下面的步骤显示了如何在开发期间使用数据库迁移:
Tim添加一个新的迁移(e.g. create a new table)
Tim提交一个新的迁移到版本控制工具(e.g. SVN, GIT)
Doug 从版本控制工具更新并取出一个新的迁移
Doug 应用新的迁移到本地开发版本的数据库

Yii框架通过yiic migrate命令行工具支持数据库迁移. 这个工具支持创建新的迁移,应用/恢复/取消迁移,并且显示迁移历史和新的迁移。

接下来,我们将会描述如何使用这个工具。

注意: 当使用命令行迁移工具进行迁移时最好使用application目录下的 yiic (e.g. cd path/to/protected)而不是系统目录下的. 确保拥有protected\migrations 文件夹并且它是可写的. 还要检查在protected/config/console.php中是否配置了数据库连接 。

1. 创建迁移

想要创建一个新的迁移 (例如创建一个news表), 我们可以运行如下命令:

yiic migrate create <name>

参数name是必须的,指定了关于这个迁移的非常简短的描述(e.g. create_news_table). 正如我们在下面要展示的, name参数是PHP类名的一部分。并且它只能包含字母,数字和下划线。

yiic migrate create create_news_table

上面的命令将会在路径protected/migrations下创建一个新的名为m101129_185401_create_news_table.php的文件,该文件包含了下列代码:

class m101129_185401_create_news_table extends CDbMigration
{
    public function up(){}

    public function down()
    {
        echo "m101129_185401_create_news_table does not support migration down.\n";
        return false;
    }

    /*
    // implement safeUp/safeDown instead if transaction is needed
    public function safeUp(){}

    public function safeDown(){}
    */
}

注意这个类名和文件名一样,都是m<timestamp>_<name>模式,其中<timestamp>表示迁移创建时的UTC时间戳 (格式为yymmdd_hhmmss) , 而 <name> 是从命令的命名参数中获取的。

up() 方法应该包含实现数据库迁移的代码, 而down() 方法包含的代码则用于还原up()方法中的操作。

有时候, 实现down()中的操作是不可能的。比如, 如果在up()方法中删除表的某一行, 就不能在down方法中恢复。 在这种情况下, 迁移被称作不可逆的,这意味着我们不能回滚到数据库的前一个状态。 在上面生成的代码中, down() 方法返回false来表明迁移的不可逆

Info: 从版本1.1.7开始, 如果up() 或者down() 方法返回false, 下面的所有迁移都会被取消。而在版本1.1.6中, 必须抛出异常来取消下面的迁移。

让我们用一个例子来展示创建一个news表的迁移.

class m101129_185401_create_news_table extends CDbMigration
{
    public function up()
    {
        $this->createTable('tbl_news', array(
            'id' => 'pk',
            'title' => 'string NOT NULL',
            'content' => 'text',
        ));
    }

    public function down()
    {
        $this->dropTable('tbl_news');
    }
}

基类 CDbMigration 提供了一系列的方法来操作数据和数据库,例如, CDbMigration::createTable 会创将数据库表,CDbMigration::insert 会插入一行数据。这些方法都使用CDbMigration::getDbConnection()返回的数据库连接, 默认是Yii::app()->db。

Info: 你可能注意到CDbMigration提供的数据库方法和CDbCommand中的很类似。的确,它们基本上相同,除了CDbMigration方法会计算执行所耗的时间以及打印一些方法参数的信息。

也可以扩展操作数据库的方法,比如:

public function up()
{
    $sql = "CREATE TABLE IF NOT EXISTS user(
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(32) NOT NULL,
        password VARCHAR(32) NOT NULL,
        email VARCHAR(32) NOT NULL
    ) ENGINE=MyISAM";
    $this->createTableBySql('user',$sql);
}
public function createTableBySql($table,$sql){
    echo " > create table $table ...";
    $time=microtime(true);
    $this->getDbConnection()->createCommand($sql)->execute();
    echo " done (time: ".sprintf('%.3f', microtime(true)-$time)."s)\n";
}

2. 事务迁移

Info: 事务迁移的特性从版本1.1.7起开始支持.

在复杂的数据库迁移中, 我们经常想要确保每一个迁移都是成功的还是失败的,以便数据库保持一致性和完整性。 为了实现这个目标我们可以利用数据库事务.

我们可以明确地开启数据库事务并附上其他数据库相关的包含事务的代码,例如:

class m101129_185401_create_news_table extends CDbMigration
{
    public function up()
    {
        $transaction=$this->getDbConnection()->beginTransaction();
        try
        {
            $this->createTable('tbl_news', array(
                'id' => 'pk',
                'title' => 'string NOT NULL',
                'content' => 'text',
            ));
            $transaction->commit();
        }catch(Exception $e){
            echo "Exception: ".$e->getMessage()."\n";
            $transaction->rollback();
            return false;
        }
    }

    // ...similar code for down()
}

然而, 一个更简单的获取事务支持的方法是实现safeUp() 方法来取代 up(), 以及safeDown() 来取代down(). 例如:

class m101129_185401_create_news_table extends CDbMigration
{
    public function safeUp()
    {
        $this->createTable('tbl_news', array(
            'id' => 'pk',
            'title' => 'string NOT NULL',
            'content' => 'text',
        ));
    }

    public function safeDown()
    {
        $this->dropTable('tbl_news');
    }
}

当Yii执行迁移的时候, 将会开启数据库迁移然后调用 safeUp() 或者 safeDown(). 如果safeUp() 和 safeDown()出现任何错误, 事务将会回滚, 以确保数据库保持一致性和完整性.

Note: 不是所有的DBMS都支持事务. 并且一些DB查询不能放到事务中. 在这种情况下, 你比许实现up()和down()取而代之. 对MySQL而言, 一些SQL语句会引发冲突.

3.使用迁移

想要使用所有有效的新迁移 (i.e., make the local database up-to-date), 运行下面的命令:

yiic migrate

这个命令会显示所有新迁移的列表. 如果你确定使用迁移, 它将会在每一个新的迁移类中运行up()方法, 一个接着一个, 按照类名中的时间戳的顺序.

在使用迁移之后, 迁移工具会在一个数据表tbl_migration中写一条记录——允许工具识别应用了哪一个迁移. 如果tbl_migration表不存在 ,工具会在配置文件中db指定的数据库中自动创建。

有时候, 我们可能指向应用一条或者几条迁移. 那么可以运行如下命令:

yiic migrate up 3

这个命令会运行3个新的迁移. 该表value的值3将允许我们改变将要被应用的迁移的数目。

我们还可以通过如下命令迁移数据库到一个指定的版本:

yiic migrate to 101129_185401

也就是我们使用数据库迁移名中的时间戳部分来指定我们想要迁移到的数据库的版本。如果在最后应用的数据库迁移和指定的迁移之间有多个迁移, 所有这些迁移都会被应用. 如果指定迁移已经使用过了, 所有之后应用的迁移都会恢复。

4. 恢复迁移

想要恢复最后一个或几个已应用的迁移,我们可以运行如下命令:

yiic migrate down [step]

其中选项 step 参数指定了要恢复的迁移的数目. 默认是1, 意味着恢复最后一个应用的迁移.

正如我们之前所描述的, 不是所有的迁移都能恢复. 尝试恢复这种迁移会抛出异常并停止整个恢复进程。

5. 重做迁移

重做迁移意味着第一次恢复并且之后应用指定的迁移. 这个可以通过如下命令来实现:

yiic migrate redo [step]

其中可选的step参数指定了重做多少个迁移 . 默认是1, 意味着重做最后一个迁移.

6. 显示迁移信息

除了应用和恢复迁移之外, 迁移工具还可以显示迁移历史和被应用的新迁移。

yiic migrate history [limit]
yiic migrate new [limit]

其中可选的参数 limit 指定克显示的迁移的数目。如果limit没有被指定,所有的有效迁移都会被显示。

第一个命令显示已经被应用的迁移, 而第二个命令显示还没被应用的迁移。

7. 编辑迁移历史

有时候, 我们可能想要在没有应用和恢复相应迁移的时候编辑迁移历史来指定迁移版本. 这通常发生在开发一个新的迁移的时候. 我们使用下面的命令来实现这一目标.

yiic migrate mark 101129_185401

这个命令和yiic migrate to命令非常类似, 但它仅仅只是编辑迁移历史表到指定版本而没有应用或者恢复迁移。

8. 自定义迁移命令

有多种方式来自定义迁移命令。

使用命令行选项

迁移命令需要在命令行中指定四个选项:

interactive: boolean, specifies whether to perform migrations in an interactive mode. Defaults to true, meaning the user will be prompted when performing a specific migration. You may set this to false should the migrations be done in a background process.

migrationPath: string, specifies the directory storing all migration class files. This must be specified in terms of a path alias, and the corresponding directory must exist. If not specified, it will use the migrations sub-directory under the application base path.

migrationTable: string, specifies the name of the database table for storing migration history information. It defaults to tbl_migration. The table structure is version varchar(255) primary key, apply_time integer.

connectionID: string, specifies the ID of the database application component. Defaults to 'db'.

templateFile: string, specifies the path of the file to be served as the code template for generating the migration classes. This must be specified in terms of a path alias (e.g. application.migrations.template). If not set, an internal template will be used. Inside the template, the token {ClassName} will be replaced with the actual migration class name.

想要指定这些选项, 使用如下格式的迁移命令执行即可:

yiic migrate up --option1=value1 --option2=value2 ...

例如, 如果我们想要迁移一个论坛模块,它的迁移文件都放在模块的迁移文件夹中,可以使用如下命令:

yiic migrate up --migrationPath=ext.forum.migrations

注意在你设置布尔选项如interactive的时候,使用如下方式传入1或者0到命令行:

yiic migrate --interactive=0

配置全局命令

命令行选项允许我们快速配置迁移命令, 但有时候我们可能想要只配置一次命令. 例如, 我们可能想要使用不同的表来保存迁移历史, 或者我们想要使用自定义的迁移模板。我们可以通过编辑控制台应用的配置文件来实现,如下所示:

return array(
    ......
    'commandMap'=>array(
        'migrate'=>array(
            'class'=>'system.cli.commands.MigrateCommand',
            'migrationPath'=>'application.migrations',
            'migrationTable'=>'tbl_migration',
            'connectionID'=>'db',
            'templateFile'=>'application.migrations.template',
        ),
        ......
    ),
    ......
);

现在如果我们运行迁移命令,上述配置将会生效,而不需要我们每一次在命令行中都输入那么多选项信息。