TypeORM+MySQLでmigrationのエラー時、query: ROLLBACKとログ出力されるけどDDLはロールバックされない
TypeORMのmigration機能でややハマりました。
確認した環境
- TypeORM 0.2.29
- MySQL 8.x
- PostgreSQL 13.x
TypeORMのmigration機能
TypeORMのmigration機能では、デフォルトでトランザクションを張った状態で実行されます。
(migration時のトランザクションをOFFにしたい場合、オプションとして-t false
を指定すればよいです。)
migration:run コマンドを実行すると、query: START TRANSACTION
とログ出力され、migrationファイルに定義した処理が順番に実行されます。
テーブル作成等のDDL、データ登録等のDMLを実行できます。
また、migrationにて作成しようとしたテーブルが既に存在する場合や、外部キー制約、SQLの構文エラー等、migration中に何かしらのエラーが発生した場合、query: ROLLBACK
とログ出力されます。
そのため、エラー発生時には定義した一連の処理が正しくロールバックされ、元の状態となることを期待します。
が、MySQLを使用した環境では、DDLのロールバックはされません。
query: ROLLBACK
と出力されるにも関わらず、です。
...なぜ?
ロールバックされない原因
MySQLのDDLはロールバックすることができないため、です。
(つまり、TypeORMのログ出力が適切ではない!?)
参考:https://dev.mysql.com/doc/refman/8.0/ja/cannot-roll-back.html
再現方法
既にuserテーブルが存在する場合、かつcompanyテーブルが存在しない場合で以下のmigrationファイルを用意します。
migrationファイル
import { MigrationInterface, QueryRunner } from "typeorm";
export class test1649923114889 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
"CREATE TABLE `company` (`id` int NOT NULL AUTO_INCREMENT, `companyName` varchar(255) NOT NULL, `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB"
);
await queryRunner.query(
"CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `userName` varchar(255) NOT NULL, `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB"
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query("DROP TABLE `user`");
await queryRunner.query("DROP TABLE `company`");
}
}
migrationを実行します。
既にuserテーブルは作成されていないるので、想定される結果は以下の通り。
- companyテーブルのCREATE TABLE のSQLがトランザクション上で成功する
- userテーブルのCREATEのSQLはエラーとなる
- 1がロールバックされ、companyとuserのどちらのテーブルも作られない
以下、migration実行時のログの抜粋
$ ts-node -T node_modules/.bin/typeorm migration:run
...(中略)...
query: START TRANSACTION
query: CREATE TABLE `company` (`id` int NOT NULL AUTO_INCREMENT, `companyName` varchar(255) NOT NULL, `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB
query: CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `userName` varchar(255) NOT NULL, `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB
query failed: CREATE TABLE `user` (`id` int NOT NULL AUTO_INCREMENT, `userName` varchar(255) NOT NULL, `createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB
error: Error: Table 'user' already exists
...(中略)...
code: 'ER_TABLE_EXISTS_ERROR',
errno: 1050,
sqlState: '42S01',
sqlMessage: "Table 'user' already exists"
}
query: ROLLBACK
Error during migration run:
QueryFailedError: Table 'user' already exists
...(中略)...
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
ログから想定される結果も
- companyテーブルのCREATE TABLE のSQLがトランザクション上で成功する
- userテーブルのCREATEのSQLはエラーとなる
- 1がロールバックされ、companyとuserのどちらのテーブルも作られない
となりますが、結果はcompanyテーブルのみ作成された状態となります。
ログにはquery: ROLLBACK
と出力されるので、3が正しく行われていないように見えます。
実際にはMySQLのDDLはロールバックできないため、3の想定は誤りかつ、TypeORMのログ出力が適切ではない、ということになります。
PostgreSQLの時の動作
MySQLとは異なりPostgreSQLでは想定通りの動作となります。
companyテーブル、userテーブルのどちらも作られていない状態にロールバックされます。
(migrationファイルはPostgreSQL用に用意します(省略))
migration実行時のログ(PostgreSQL)
$ ts-node --transpile-only node_modules/.bin/typeorm migration:run
...(中略)...
query: START TRANSACTION
query: CREATE TABLE "company" ("id" SERIAL NOT NULL, "companyName" character varying NOT NULL, "createdAt" character varying NOT NULL, "updatedAt" character varying NOT NULL, CONSTRAINT "PK_635c0seeabda8862d5b0237b42b4" PRIMARY KEY ("id"))
query: CREATE TABLE "user" ("id" SERIAL NOT NULL, "companyName" character varying NOT NULL, "createdAt" character varying NOT NULL, "updatedAt" character varying NOT NULL, CONSTRAINT "PK_635c0eeabada8862d5b0237b42b4" PRIMARY KEY ("id"))
query failed: CREATE TABLE "user" ("id" SERIAL NOT NULL, "companyName" character varying NOT NULL, "createdAt" character varying NOT NULL, "updatedAt" character varying NOT NULL, CONSTRAINT "PK_635c0eeabada8862d5b0237b42b4" PRIMARY KEY ("id"))
error: error: relation "user" already exists
...(中略)...
length: 98,
severity: 'ERROR',
code: '42P07',
...(中略)...
query: ROLLBACK
Error during migration run:
...(中略)...
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
(あれ?エラーはrelationが既に存在する、ではなくてtableが既に存在する の方が適切...?)
ロールバックされない原因(再掲)
MySQLの仕様です。
まさかログ出力が適切でないとは、、、という言い訳をしつつ、
ログ自体を疑うことも必要であることを学びました。
採用しているDBのドキュメントは読みましょうね。。。
なお、本件はTypeORMのリポジトリのissueにて報告、ログ出力の改善が提案されています。
以上です。
Author And Source
この問題について(TypeORM+MySQLでmigrationのエラー時、query: ROLLBACKとログ出力されるけどDDLはロールバックされない), 我々は、より多くの情報をここで見つけました https://zenn.dev/nkmrkz/articles/typeorm-migration-transaction著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol