Gradle編が一段落したので、データベースのマイグレーションについて調査していこうと思います〜
環境は、java・MySQLで開発している場合のマイグレーションする場合についてです。
使用するツール
使用するツールは flyway です。
Flyway by Boxfuse • Database Migrations Made Easy.
1点注意点があって、2013/07/20 現在ではコマンドラインツール版をDLすると404になります。。。
しかし大丈夫です。以下のURLからDLする事ができます。
http://repo1.maven.org/maven2/com/googlecode/flyway/flyway-commandline/2.2/
flywayの実行環境は?
flyway2.2では以下から実行する事ができます。
- jarをクラスパスに通してjavaから実行。
- コマンドラインツールから実行。実行用のjar・シェルスクリプト(*.sh)とバッチファイル(*.bat)が同梱。
- maven pluginでmavenから実行。
- gradle pluginでgradleから実行。
- ant taskでantから実行。
結構充実してます。著名なビルドツールからも、javaからも、コマンドラインからも実行可能です。
これだけ対応しているので、どんな環境からでも何かしらの方法で実行できるでしょう。
flywayの制限は?
非常に重要な制限事項があります。それは・・・
ダウングレードに未対応
です。
Flyway does NOT support downgrade scripts.
FAQ - Flyway by Boxfuse • Database Migrations Made Easy.
While the idea of downgrade scripts (popularized by Rails Migrations) is a nice one in theory, unfortunately it breaks down in practice. As soon as you have destructive changes (drop, delete, truncate, ...), you start getting into trouble. And even if you don't, you end up creating home-made alternatives for restoring backups, which need to be properly tested as well.
ロードマップを見ても、ダウングレードには対応する気はないようです。
ダウングレード未対応ということは、旧バージョンのスキーマに戻したい時は、
逐一手動でDBを直してあげないといけないのです。
早速flywayを使ってみる
早速実行して挙動を確認してみます。
今回はお手軽に実行するため、コマンドラインツール版を使います。
デーベースはMySQL、スキーマ名はaddress、としています。
まずはコマンドとオプション一覧を確認
localhost:flyway tree$ ./flyway Flyway (Command-line Tool) v.2.2 ******** * Usage ******** flyway [options] command By default, the configuration will be read from conf/flyway.properties. Options passed from the command-line override the configuration. Commands ======== clean : Drops all objects in the configured schemas init : Creates and initializes the metadata table migrate : Migrates the database validate : Validates the applied migrations against the ones on the classpath info : Prints the information about applied, current and pending migrations repair : Repairs the metadata table after a failed migration Options (Format: -key=value) ======= driver : Fully qualified classname of the jdbc driver url : Jdbc url to use to connect to the database user : User to use to connect to the database password : Password to use to connect to the database schemas : Comma-separated list of the schemas managed by Flyway table : Name of Flyway's metadata table locations : Classpath locations to scan recursively for migrations sqlMigrationPrefix : File name prefix for Sql migrations sqlMigrationSuffix : File name suffix for Sql migrations encoding : Encoding of Sql migrations placeholders : Placeholders to replace in Sql migrations placeholderPrefix : Prefix of every placeholder placeholderSuffix : Suffix of every placeholder target : Target version up to which Flyway should migrate outOfOrder : Allows migrations to be run "out of order" validateOnMigrate : Validate when running migrate cleanOnValidationError : Automatically clean on a validation error initVersion : Version to tag schema with when executing init initDescription : Description to tag schema with when executing init initOnMigrate : Init on migrate against uninitialized non-empty schema configFile : Config file to use (default: conf/flyway.properties) configFileEncoding : Encoding of the config file (default: UTF-8) jarDir : Dir for Jdbc drivers & Java migrations (default: jars) Add -X to print debug output Example ======= flyway -target=1.5 -placeholder.user=my_user info More info at http://flywaydb.org/documentation/commandline
こんな感じです。
ここでオプションに注目。
driverのurlが引数で指定できる!のです。
これはつまり、ローカル環境はlocalhost、本番環境は任意のホスト名、という具合に簡単に接続先を変えられます。
infoコマンドでマイグレーションの状態確認
localhost:flyway tree$ ./flyway info Flyway (Command-line Tool) v.2.2 +----------------+----------------------------+---------------------+---------+ | Version | Description | Installed on | State | +----------------+----------------------------+---------------------+---------+ | No migrations found | +----------------+----------------------------+---------------------+---------+
マイグレーションが見つからないそうです。
migrateしてみる
何も考えずにいきなりマイグレートしてみます。
localhost:flyway tree$ ./flyway migrate Flyway (Command-line Tool) v.2.2 ERROR: FlywayException: Found non-empty schema `address` without metadata table! Use init() first to initialize the metadata table. ERROR: Occured in com.googlecode.flyway.core.Flyway$1.execute() at line 848
addressスキーマはinitされてない!と怒られました。
ERRORの2行目は完全に内部のデバッグ用のエラーですかね。
使う側からしたら2行目のERRORが表示されても、何のことやら?これ見て何したらいいの?ってなります。
initしてみる
localhost:flyway tree$ ./flyway init Flyway (Command-line Tool) v.2.2 Creating Metadata table: `address`.`schema_version` Schema initialized with version: 1
お、成功したようです。
バージョン1でschema_versionテーブル作ったぜ!と表示されました。
早速DBを見てみます。
mysql> show tables; +-------------------+ | Tables_in_address | +-------------------+ | address | | schema_version | +-------------------+ 2 rows in set (0.00 sec) mysql> show create table schema_version \G *************************** 1. row *************************** Table: schema_version Create Table: CREATE TABLE `schema_version` ( `version_rank` int(11) NOT NULL, `installed_rank` int(11) NOT NULL, `version` varchar(50) NOT NULL, `description` varchar(200) NOT NULL, `type` varchar(20) NOT NULL, `script` varchar(1000) NOT NULL, `checksum` int(11) DEFAULT NULL, `installed_by` varchar(100) NOT NULL, `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `execution_time` int(11) NOT NULL, `success` tinyint(1) NOT NULL, PRIMARY KEY (`version`), KEY `schema_version_vr_idx` (`version_rank`), KEY `schema_version_ir_idx` (`installed_rank`), KEY `schema_version_s_idx` (`success`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec) mysql> select * from schema_version; +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ | version_rank | installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success | +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ | 1 | 1 | 1 | << Flyway Init >> | INIT | << Flyway Init >> | NULL | root | 2013-07-20 00:20:15 | 0 | 1 | +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ 1 row in set (0.00 sec)
バージョン1でschema_versionテーブルにデータが挿入されてますね。
どうやら以下が記録されるようです。
- version:現在のバージョン。
- description:ファイル名の「V2__description.sql」のdescription部分が設定されます。
- type:実行にかかった時間。
- script:実行したsqlのファイル名。
- installed_by:実行した時のmysqlのユーザ名。
- installed_on:実行した時の時間。
- execution_time:マイグレーションにかかった時間。
- success:1=true、0=false。MySQLにはbool型が無いので数値なのでしょう。
initした後に更にinitしてみる
いじわるしてみます。
localhost:flyway tree$ ./flyway init Flyway (Command-line Tool) v.2.2 Metadata table `address`.`schema_version` already initialized with (1,<< Flyway Init >>). Skipping. Schema initialized with version: 1
おお、「addressスキーマは既にinitされてんぞコラ!」と怒られました。
誤って2回initしたら壊れる、という悲劇は起こらなそうですね。
sqlを用意してmigrateコマンドを実行
用意するファイルは「$FLYWAY_HOME/sql/V1__test.sql」です。
とりあえずファイルの中身は空っぽでよいです。
localhost:flyway tree$ ./flyway migrate Flyway (Command-line Tool) v.2.2 Current version of schema `address`: 1 Schema `address` is up to date. No migration necessary.
あれ?マイグレーションの必要はないぜ!と言われました。
ここでバージョンに注目。バージョンは1ですね。
で、マイグレーションファイルのバージョンも1です。
バージョンが1なら、マイグレーションファイルのバージョン1は既に実行された事になります。
だからV1__test.sqlは既に実行済みだから、No migration necessary. なのです。
ということで、初回のマイグレーションのファイル名は「V2__xxx.sql」でなければ実行されないのです。
気分的にはファイル名がV2から始まるのは嫌ですね。。V1は無いの?と思ってしまいます。
V2のsqlを用意してmigrate
現在のマイグレーションのバージョンは1なので、V2のsql、V2__test.sqlを用意します。
ここで少しいじわるして、シンタックスエラーするsqlにしておきます。
localhost:sql tree$ cat V2__test.sql hoge
ではmigrateしてみます。
localhost:flyway tree$ ./flyway migrate Flyway (Command-line Tool) v.2.2 Current version of schema `address`: 1 Migrating schema `address` to version 2 ERROR: FlywayException: Migration of schema `address` to version 2 failed! Please restore backups and roll back database and code! ERROR: Occured in com.googlecode.flyway.core.command.DbMigrate.migrate() at line 199 ERROR: Caused by com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'hoge' at line 1 ERROR: Occured in sun.reflect.NativeConstructorAccessorImpl.newInstance0() at line -2 mysql> mysql> select * from schema_version; +--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+ | version_rank | installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success | +--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+ | 1 | 1 | 1 | << Flyway Init >> | INIT | << Flyway Init >> | NULL | root | 2013-07-20 00:20:15 | 0 | 1 | | 2 | 2 | 2 | test | SQL | V2__test.sql | -1959140262 | root | 2013-07-20 00:41:58 | 7 | 0 | +--------------+----------------+---------+-------------------+------+-------------------+-------------+--------------+---------------------+----------------+---------+
シンタックスエラーが検出されました。
バージョン2の実行は失敗したから、sqlファイルを修正してDBをrollbackしてね、と言われました。
schema_versionテーブルには、失敗したmigrateが記録されています。successが0になってますね。
repairコマンドで修復してみる
前回わざと失敗してみたので、repairコマンドで修復を試みます。
修復って何?と思ったので、挙動を注意して確認してみます。
localhost:flyway tree$ ./flyway repair Flyway (Command-line Tool) v.2.2 Metadata table `address`.`schema_version` successfully repaired (execution time 00:00.000s). Manual cleanup of the remaining effects the failed migration may still be required. mysql> select * from schema_version; +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ | version_rank | installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success | +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ | 1 | 1 | 1 | << Flyway Init >> | INIT | << Flyway Init >> | NULL | root | 2013-07-20 00:20:15 | 0 | 1 | +--------------+----------------+---------+-------------------+------+-------------------+----------+--------------+---------------------+----------------+---------+ 1 row in set (0.01 sec)
リペアに成功しました。repairすると、success=0のデータがdeleteされ、
V2のマイグレーションが無かった事にされるようです。
これはつまり、repairでバージョンダウン、sqlファイルを修正、再度V2でmigrate、とする事を想定しているようです。
なるほどなるほど。しかしDBの状態は元に戻らないので、
「Manual cleanup of the remaining effects the failed migration may still be required.」
と言われます。
失敗したマイグレーションsqlはflyway側で自動修復できんから、手動で自力で直してね♪って事でしょうか。
alter tableのような暗黙のコミットが走るコマンドがsqlに書かれていた場合、
トランザクションが効かないので、部分的にsqlが適用される場合があるから、それは自分で直してね、って事ですね。
repairした後に更にrepairしてみる
恒例のいじわるです。2回連続repairしたらどうなるか。
localhost:flyway tree$ ./flyway repair Flyway (Command-line Tool) v.2.2 Repair of metadata table `address`.`schema_version` not necessary. No failed migration detected.
おお、マイグレーション対象は無い、と言われましたね。
なんかメッセージがおかしい気がします。「リペア対象が無い」が正解なんじゃ・・・?
この挙動を見ると、直前バージョンのsuccess=0のみがrepairの対象となるようです。
これなら事故が怒らなくて安心です。
正しいsqlを用意してmigrate
ようやく正常系の動作です。
V2__test.sqlには以下のsqlを記述します。
drop table if exists hoge; create table hoge ( id int )engine=innodb charset=utf8mb4;
ではmigrateを実行。
localhost:flyway tree$ ./flyway migrate Flyway (Command-line Tool) v.2.2 Current version of schema `address`: 1 Migrating schema `address` to version 2 Successfully applied 1 migration to schema `address` (execution time 00:00.077s). mysql> select * from schema_version; +--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+ | version_rank | installed_rank | version | description | type | script | checksum | installed_by | installed_on | execution_time | success | +--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+ | 1 | 1 | 1 | << Flyway Init >> | INIT | << Flyway Init >> | NULL | root | 2013-07-20 00:20:15 | 0 | 1 | | 2 | 2 | 2 | test | SQL | V2__test.sql | 1679364319 | root | 2013-07-20 15:42:27 | 23 | 1 | +--------------+----------------+---------+-------------------+------+-------------------+------------+--------------+---------------------+----------------+---------+ 2 rows in set (0.00 sec) mysql> show create table hoge\G *************************** 1. row *************************** Table: hoge Create Table: CREATE TABLE `hoge` ( `id` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec)
これが正常系です。ただしくhogeテーブルも作成されました。
success=1を手動削除したら強制バージョンダウンできる?
ちょっと試してみました。
success=1のデータはマイグレーションに成功した事を意味しますが、これをdeleteしたら、
1個前のバージョンをマイグレーションできるか?
できました
flywayがschema_version以外のメタデータを保持したりしないから可能なのでしょう。
長くなってしまったので、今回の調査はここまでです。