# 數據庫遷移
在開發和維護一個數據庫驅動的應用程序時,數據庫的結構會隨代碼的改變而改變。例如,在開發應用程序的過程中,會增加一張新表且必須得加進來; 在應用程序被部署到生產環境后,需要建立一個索引來提高查詢的性能等等。 因為一個數據庫結構發生改變的時候源代碼也經常會需要做出改變,Yii 提供了一個?*數據庫遷移*?功能,該功能可以記錄數據庫的變化, 以便使數據庫和源代碼一起受版本控制。
如下的步驟向我們展示了數據庫遷移工具是如何為開發團隊所使用的:
1. Tim 創建了一個新的遷移對象(例如,創建一張新的表單,改變字段的定義等)。
2. Tim 將這個新的遷移對象提交到代碼管理系統(例如,Git,Mercurial)。
3. Doug 從代碼管理系統當中更新版本并獲取到這個新的遷移對象。
4. Doug 把這個遷移對象提交到本地的開發數據庫當中,這樣一來,Doug 同步了 Tim 所做的修改。
如下的步驟向我們展示了如何發布一個附帶數據庫遷移的新版本到生產環境當中:
1. Scott 為一個包含數據庫遷移的項目版本創建了一個發布標簽。
2. Scott 把發布標簽的源代碼更新到生產環境的服務器上。
3. Scott 把所有的增量數據庫遷移提交到生產環境數據庫當中。
Yii 提供了一整套的遷移命令行工具,通過這些工具你可以:
* 創建新的遷移;
* 提交遷移;
* 恢復遷移;
* 重新提交遷移;
* 現實遷移歷史和狀態。
所有的這些工具都可以通過?`yii migrate`?命令來進行操作。 在這一章節,我們將詳細的介紹如何使用這些工具來完成各種各樣的任務。你也可以通過?`yii help migrate`?命令來獲取每一種工具的具體使用方法。
> 注意:遷移不僅僅只作用于數據庫表,它同樣會調整現有的數據來適應新的表單、創建 RBAC 分層、又或者是清除緩存。
## 創建遷移
使用如下命令來創建一個新的遷移:
~~~
yii migrate/create <name>
~~~
必填參數?`name`?的作用是對新的遷移做一個簡要的描述。例如,如果這個遷移是用來創建一個叫做?*news*?的表單的,那么你可以使用`create_news_table`?這個名稱并運行如下命令:
~~~
yii migrate/create create_news_table
~~~
> 注意:因為?`name`?參數會被用來生成遷移的類名的一部分,所以該參數應當只包含字母、數字和下劃線。
如上命令將會在?`@app/migrations`?目錄下創建一個新的名為?`m150101_185401_create_news_table.php`?的 PHP 類文件。該文件包含如下的代碼,它們用來聲明一個遷移類?`m150101_185401_create_news_table`,并附有代碼框架:
~~~
<?php
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function up()
{
}
public function down()
{
echo "m101129_185401_create_news_table cannot be reverted.\n";
return false;
}
}
~~~
每個數據庫遷移都會被定義為一個繼承自 yii\db\Migration 的 PHP 類。類的名稱按照?`m<YYMMDD_HHMMSS>_<Name>`?的格式自動生成,其中
* `<YYMMDD_HHMMSS>`?指執行創建遷移命令的 UTC 時間。
* `<Name>`?和你執行命令時所帶的?`name`?參數值相同。
在遷移類當中,你應當在?`up()`?方法中編寫改變數據庫結構的代碼。你可能還需要在?`down()`?方法中編寫代碼來恢復由?`up()`?方法所做的改變。 當你通過 migration 升級數據庫時,?`up()`?方法將會被調用,反之,?`down()`?將會被調用。如下代碼展示了如何通過遷移類來創建一張?`news`?表:
~~~
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$this->createTable('news', [
'id' => Schema::TYPE_PK,
'title' => Schema::TYPE_STRING . ' NOT NULL',
'content' => Schema::TYPE_TEXT,
]);
}
public function down()
{
$this->dropTable('news');
}
}
~~~
> 注意:并不是所有遷移都是可恢復的。例如,如果?`up()`?方法刪除了表中的一行數據,這將無法通過?`down()`?方法來恢復這條數據。有時候,你也許只是懶得去執行?`down()`?方法了,因為它在恢復數據庫遷移方面并不是那么的通用。在這種情況下,你應當在?`down()`?方法中返回?`false`?來表明這個 migration 是無法恢復的。
migration 的基類 yii\db\Migration 通過 yii\db\Migration::db 屬性來連接了數據庫。你可以通過?[配合數據庫工作](http://www.yiichina.com/doc/guide/2.0/db-dao#working-with-database-schema-)?章節中所描述的那些方法來操作數據庫表。
當你通過 migration 創建一張表或者字段的時候,你應該使用?*抽象類型*?而不是?*實體類型*,這樣一來你的遷移對象就可以從特定的 DBMS 當中抽離出來。 yii\db\Schema 類定義了一整套可用的抽象類型常量。這些常量的格式為?`TYPE_<Name>`。例如,`TYPE_PK`?指代自增主鍵類型;`TYPE_STRING`?指代字符串類型。 當遷移對象被提交到某個特定的數據庫的時候,這些抽象類型將會被轉換成相對應的實體類型。以 MySQL 為例,`TYPE_PK`?將會變成?`int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, 而?`TYPE_STRING`?則變成`varchar(255)`。
在使用抽象類型的時候,你可以添加額外的約束條件。在上面的例子當中,?`NOT NULL`?被添加到?`Schema::TYPE_STRING`?當中來指定該字段不能為空。
> 提示:抽象類型和實體類型之間的映射關系是由每個具體的?`QueryBuilder`?類當中的 yii\db\QueryBuilder::$typeMap 屬性所指定的。
從 2.0.5 的版本開始,schema 構造器提供了更加方便的方法來定義字段,因此上面的 migration 可以被改寫成:
~~~
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends \yii\db\Migration
{
public function up()
{
$this->createTable('news', [
'id' => Schema::primaryKey(),
'title' => Schema::string()->notNull(),
'content' => Schema::text(),
]);
}
public function down()
{
$this->dropTable('news');
}
}
~~~
### 事務遷移
當需要實現復雜的數據庫遷移的時候,確定每一個遷移的執行是否成功或失敗就變得相當重要了,因為這將影響到數據庫的完整性和一致性。為了達到這個目標,我們建議你把每個遷移里面的數據庫操作都封裝到一個?[transaction](http://www.yiichina.com/doc/guide/2.0/db-dao#performing-transactions-)?里面。
實現事務遷移的一個更為簡便的方法是把遷移的代碼都放到?`safeUp()`?和?`safeDown()`?方法里面。它們與?`up()`?和?`down()`?的不同點就在于它們是被隱式的封裝到事務當中的。如此一來,只要這些方法里面的任何一個操作失敗了,那么所有之前的操作都會被自動的回滾。
在如下的例子當中,除了創建?`news`?表以外,我們還插入了一行初始化數據到表里面。
~~~
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function safeUp()
{
$this->createTable('news', [
'id' => Schema::primaryKey(),,
'title' => Schema::string()->notNull(),
'content' => Schema::text(),
]);
$this->insert('news', [
'title' => 'test 1',
'content' => 'content 1',
]);
}
public function safeDown()
{
$this->delete('news', ['id' => 1]);
$this->dropTable('news');
}
}
~~~
需要注意的是,當你在?`safeUp()`?當中執行多個數據庫操作的時候,你應該在?`safeDown()`?方法當中反轉它們的執行順序。在上面的例子當中,我們在?`safeUp()`?方法當中首先創建了一張表,然后插入了一條數據;而在?`safeDown()`?方法當中,我們首先刪除那一行數據,然后才刪除那張表。
> 注意:并不是所有的數據庫都支持事務。有些數據庫查詢也是不能被放倒事務里面的。在?[implicit commit](http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html)?章節當中有相關的例子可以參考。如果遇到這種情況的話,那么你應該使用?`up()`?和?`down()`?方法進行替代。
### 訪問數據庫的方法
遷移的基類 yii\db\Migration 提供了一整套訪問和操作數據庫的方法。你可能會發現這些方法的命名和 yii\db\Command 類提供的?[DAO 方法](http://www.yiichina.com/doc/guide/2.0/db-dao)?很類似。 例如,yii\db\Migration::createTable() 方法可以創建一張新的表,這和 yii\db\Command::createTable() 的功能是一模一樣的。
使用 yii\db\Migration 所提供的方法的好處在于你不需要再顯式的創建 yii\db\Command 實例,而且在執行每個方法的時候都會顯示一些有用的信息來告訴我們數據庫操作是不是都已經完成,還有它們完成這些操作花了多長時間等等。
如下是所有這些數據庫訪問方法的列表:
* yii\db\Migration::execute(): 執行一條 SQL 語句
* yii\db\Migration::insert(): 插入單行數據
* yii\db\Migration::batchInsert(): 插入多行數據
* yii\db\Migration::update(): 更新數據
* yii\db\Migration::delete(): 刪除數據
* yii\db\Migration::createTable(): 創建表
* yii\db\Migration::renameTable(): 重命名表名
* yii\db\Migration::dropTable(): 刪除一張表
* yii\db\Migration::truncateTable(): 清空表中的所有數據
* yii\db\Migration::addColumn(): 加一個字段
* yii\db\Migration::renameColumn(): 重命名字段名稱
* yii\db\Migration::dropColumn(): 刪除一個字段
* yii\db\Migration::alterColumn(): 修改字段
* yii\db\Migration::addPrimaryKey(): 添加一個主鍵
* yii\db\Migration::dropPrimaryKey(): 刪除一個主鍵
* yii\db\Migration::addForeignKey(): 添加一個外鍵
* yii\db\Migration::dropForeignKey(): 刪除一個外鍵
* yii\db\Migration::createIndex(): 創建一個索引
* yii\db\Migration::dropIndex(): 刪除一個索引
> 提示:yii\db\Migration 并沒有提供數據庫的查詢方法。這是因為通常你是不需要去數據庫把數據一行一行查出來再顯示出來的。另外一個原因是你完全可以使用強大的?[Query Builder 查詢構建器](http://www.yiichina.com/doc/guide/2.0/db-query-builder)?來構建和查詢。
## 提交遷移
為了將數據庫升級到最新的結構,你應該使用如下命令來提交所有新的遷移:
~~~
yii migrate
~~~
這條命令會列出迄今為止所有未提交的遷移。如果你確定你需要提交這些遷移,它將會按照類名當中的時間戳的順序,一個接著一個的運行每個新的遷移類里面的?`up()`?或者是?`safeUp()`?方法。如果其中任意一個遷移提交失敗了,那么這條命令將會退出并停止剩下的那些還未執行的遷移。
對于每一個成功提交的遷移,這條命令都會在一個叫做?`migration`?的數據庫表中插入一條包含應用程序成功提交遷移的記錄,該記錄將幫助遷移工具判斷哪些遷移已經提交, 哪些還沒有提交。
> 提示:遷移工具將會自動在數據庫當中創建?`migration`?表,該數據庫是在該命令的 yii\console\controllers\MigrateController::db 選項當中指定的。默認情況下,是由?`db`?[application component](http://www.yiichina.com/doc/guide/2.0/structure-application-components)?指定的。
有時,你可能只需要提交一個或者少數的幾個遷移,你可以使用該命令指定需要執行的條數,而不是執行所有的可用遷移。例如,如下命令將會嘗試提交前三個可用的遷移:
~~~
yii migrate 3
~~~
你也可以指定一個特定的遷移,按照如下格式使用?`migrate/to`?命令來指定數據庫應該提交哪一個遷移:
~~~
yii migrate/to 150101_185401 # using timestamp to specify the migration 使用時間戳來指定遷移
yii migrate/to "2015-01-01 18:54:01" # using a string that can be parsed by strtotime() 使用一個可以被 strtotime() 解析的字符串
yii migrate/to m150101_185401_create_news_table # using full name 使用全名
yii migrate/to 1392853618 # using UNIX timestamp 使用 UNIX 時間戳
~~~
如果在指定要提交的遷移前面還有未提交的遷移,那么在執行這個被指定的遷移之前,這些還未提交的遷移會先被提交。
如果被指定提交的遷移在之前已經被提交過,那么在其之后的那些遷移將會被還原。
## 還原遷移
你可以使用如下命令來還原其中一個或多個意見被提交過的遷移:
~~~
yii migrate/down # revert the most recently applied migration 還原最近一次提交的遷移
yii migrate/down 3 # revert the most 3 recently applied migrations 還原最近三次提交的遷移
~~~
> 注意:并不是所有的遷移都能被還原。嘗試還原這類遷移將可能導致報錯甚至是終止所有的還原進程。
## 重做遷移
重做遷移的意思是先還原指定的遷移,然后再次提交。如下所示:
~~~
yii migrate/redo # redo the last applied migration 重做最近一次提交的遷移
yii migrate/redo 3 # redo the last 3 applied migrations 重做最近三次提交的遷移
~~~
> 注意:如果一個遷移是不能被還原的,那么你將無法對它進行重做。
## 列出遷移
你可以使用如下命令列出那些提交了的或者是還未提交的遷移:
~~~
yii migrate/history # 顯示最近10次提交的遷移
yii migrate/history 5 # 顯示最近5次提交的遷移
yii migrate/history all # 顯示所有已經提交過的遷移
yii migrate/new # 顯示前10個還未提交的遷移
yii migrate/new 5 # 顯示前5個還未提交的遷移
yii migrate/new all # 顯示所有還未提交的遷移
~~~
## 修改遷移歷史
有時候你也許需要簡單的標記一下你的數據庫已經升級到一個特定的遷移,而不是實際提交或者是還原遷移。這個經常會發生在你手動的改變數據庫的一個特定狀態,而又不想相應的遷移被重復提交。那么你可以使用如下命令來達到目的:
~~~
yii migrate/mark 150101_185401 # 使用時間戳來指定遷移
yii migrate/mark "2015-01-01 18:54:01" # 使用一個可以被 strtotime() 解析的字符串
yii migrate/mark m150101_185401_create_news_table # 使用全名
yii migrate/mark 1392853618 # 使用 UNIX 時間戳
~~~
該命令將會添加或者刪除?`migration`?表當中的某幾行數據來表明數據庫已經提交到了指定的某個遷移上。執行這條命令期間不會有任何的遷移會被提交或還原。
## 自定義遷移
有很多方法可以自定義遷移命令。
### 使用命令行選項
遷移命令附帶了幾個命令行選項,可以用來自定義它的行為:
* `interactive`: boolean (默認值為 true),指定是否以交互模式來運行遷移。當被設置為 true 時,在命令執行某些操作前,會提示用戶。如果你希望在后臺執行該命令,那么你應該把它設置成 false。
* `migrationPath`: string (默認值為?`@app/migrations`),指定存放所有遷移類文件的目錄。該選項可以是一個目錄的路徑,也可以是?[路徑別名](http://www.yiichina.com/doc/guide/2.0/concept-aliases)。需要注意的是指定的目錄必選存在,否則將會觸發一個錯誤。
* `migrationTable`: string (默認值為?`migration`),指定用于存儲遷移歷史信息的數據庫表名稱。如果這張表不存在,那么遷移命令將自動創建這張表。當然你也可以使用這樣的字段結構:?`version varchar(255) primary key, apply_time integer`?來手動創建這張表。
* `db`: string (默認值為?`db`),指定數據庫?[application component](http://www.yiichina.com/doc/guide/2.0/structure-application-components)?的 ID。它指的是將會被該命令遷移的數據庫。
* `templateFile`: string (defaults to?`@yii/views/migration.php`),指定生產遷移框架代碼類文件的模版文件路徑。該選項即可以使用文件路徑來指定,也可以使用路徑?[別名](http://www.yiichina.com/doc/guide/2.0/concept-aliases)?來指定。該模版文件是一個可以使用預定義變量?`$className`?來獲取遷移類名稱的 PHP 腳本。
如下例子向我們展示了如何使用這些選項:
例如,如果我們需要遷移一個?`forum`?模塊,而該遷移文件放在該模塊下的?`migrations`?目錄當中,那么我們可以使用如下命令:
~~~
# 在 forum 模塊中以非交互模式進行遷移
yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0
~~~
### 全局配置命令
在運行遷移命令的時候每次都要重復的輸入一些同樣的參數會很煩人,這時候,你可以選擇在應用程序配置當中進行全局配置,一勞永逸:
~~~
return [
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationTable' => 'backend_migration',
],
],
];
~~~
如上所示配置,在每次運行遷移命令的時候,`backend_migration`?表將會被用來記錄遷移歷史。你再也不需要通過`migrationTable`?命令行參數來指定這張歷史紀錄表了。
## 遷移多個數據庫
默認情況下,遷移將會提交到由?`db`?[application component](http://www.yiichina.com/doc/guide/2.0/structure-application-components)?所定義的同一個數據庫當中。如果你需要提交到不同的數據庫,你可以像下面那樣指定?`db`?命令行選項,
~~~
yii migrate --db=db2
~~~
上面的命令將會把遷移提交到?`db2`?數據庫當中。
偶爾有限時候你需要提交?*一些*?遷移到一個數據庫,而另外一些則提交到另一個數據庫。為了達到這個目的,你應該在實現一個遷移類的時候指定需要用到的數據庫組件的 ID , 如下所示:
~~~
use yii\db\Schema;
use yii\db\Migration;
class m150101_185401_create_news_table extends Migration
{
public function init()
{
$this->db = 'db2';
parent::init();
}
}
~~~
即使你使用?`db`?命令行選項指定了另外一個不同的數據庫,上面的遷移還是會被提交到?`db2`?當中。需要注意的是這個時候遷移的歷史信息依然會被記錄到?`db`?命令行選項所指定的數據庫當中。
如果有多個遷移都使用到了同一個數據庫,那么建議你創建一個遷移的基類,里面包含上述的?`init()`?代碼。然后每個遷移類都繼承這個基類就可以了。
> 提示:除了在 yii\db\Migration::db 參數當中進行設置以外,你還可以通過在遷移類中創建新的數據庫連接來操作不同的數據庫。然后通過這些連接再使用?[DAO 方法](http://www.yiichina.com/doc/guide/2.0/db-dao)?來操作不同的數據庫。
另外一個可以讓你遷移多個數據庫的策略是把遷移存放到不同的目錄下,然后你可以通過如下命令分別對不同的數據庫進行遷移:
~~~
yii migrate --migrationPath=@app/migrations/db1 --db=db1
yii migrate --migrationPath=@app/migrations/db2 --db=db2
...
~~~
第一條命令將會把?`@app/migrations/db1`?目錄下的遷移提交到?`db1`?數據庫當中,第二條命令則會把?`@app/migrations/db2`?下的遷移提交到?`db2`?數據庫當中,以此類推。
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)