RemitAid Tech Blog
🪤

MySQL DDL の暗黙的コミット-トランザクションが効かない DDL の落とし穴

に公開

こんにちは!株式会社 RemitAid のremitaid_wataruです。

RemitAid ではデータベースに MySQL、データベースマイグレーションに golang-migrate を使用しています。
今回は、マイグレーション実行時にトランザクションを明示的に記述していたにも関わらず、エラー発生時にロールバックされずデータベースマイグレーションが中途半端な状態になってしまった経験を共有します。

早速結論

原因は MySQL の DDL ステートメントが持つ「暗黙的なコミット」でした。

環境

  • MySQL: 8.0.28
  • golang-migrate: v4.18.2

事象

マイグレーションの内容は簡単に言うと ALTER TABLE → UPDATE → ALTER TABLE です。
実行したマイグレーションは、簡略化した例ですが、以下のようなものです。

-- 001_add_user_status_column.up.sql
START TRANSACTION;

-- まずNULL許可で追加
ALTER TABLE users
ADD COLUMN status VARCHAR(20)
COMMENT 'ステータス'
AFTER email;

-- 既存データにデフォルト値を設定
UPDATE users SET status = 'xxx' WHERE status IS NULL;

-- NOT NULL制約を追加(ここでエラー発生)
ALTER TABLE users
MODIFY COLUMN status VARCHAR(20) NOT NULL
COMMENT 'ステータス';

COMMIT;

マイグレーションファイルでは、明示的にトランザクションを記述していたので、2 つ目の ALTER TABLE が失敗した時に、ロールバックされると思っていましたが UPDATE まで実行された状態となっていました。

原因

調査の結果、MySQL の DDL ステートメントが持つ「暗黙的なコミット」が原因であることが判明しました。

詳細は以下のリンクに記載がありますが、ドキュメントによると

いくつかのステートメントはロールバックできません。 これには一般に、データベースを作成または削除したり、テーブルやストアドルーチンを作成、削除、または変更したりするデータ定義言語 (DDL) ステートメントが含まれます。

とのことでした。

https://dev.mysql.com/doc/refman/8.0/ja/cannot-roll-back.html
https://dev.mysql.com/doc/refman/8.0/ja/implicit-commit.html

ちなみに PostgreSQL では DDL もトランザクション内で実行可能なようです。

対処方法

DDL ステートメントを含むマイグレーションファイルの分割

結果としての状態は変わりませんが、DDL ステートメントを含むマイグレーションファイルを細かく分割することが有効です。
上記の例では、以下のように分割するイメージです。

-- 001_add_status_column.up.sql
ALTER TABLE users
ADD COLUMN status VARCHAR(20)
COMMENT 'ステータス'
AFTER email;

-- 002_update_status_default_value.up.sql
START TRANSACTION;
UPDATE users SET status = 'xxx' WHERE status IS NULL;
COMMIT;

-- 003_modify_status_not_null.up.sql
ALTER TABLE users
MODIFY COLUMN status VARCHAR(20) NOT NULL
COMMENT 'ステータス';

分割するメリットとしては以下があります。

  • 失敗したバージョンが明確になる
  • 切り戻しが容易になる(golang-migrate では切り戻し用の マイグレーションファイルも作成しているはずなので)

事前検証とバックアップ

直接的な対処ではありませんが、マイグレーション実行前には以下を行うことが重要です。

  • 開発環境での事前検証
  • マイグレーション実行前のデータベースバックアップ取得
  • 問題発生時の迅速な切り戻し手順の確立

まとめ

MySQL の DDL ステートメントは実行時に暗黙的にコミットされるため、明示的にトランザクションを記述していてもロールバックされません。
実行時はこの特性を理解して、適切なマイグレーション戦略を立てる必要があります。

対策としては、マイグレーションファイルの分割を基本とし、事前検証とデータベースバックアップなどを組み合わせることで、安全なマイグレーションが実現できるはずです。

We Are Hiring!

RemitAid では一緒に働く仲間を募集しています。
興味がある方はこちらからどうぞ!

https://youtrust.jp/recruitment_posts/fca1654630a5be7d404f275c537e9e2c

https://youtrust.jp/recruitment_posts/dab930db9b573452241f0d9b6ea8cd29

https://youtrust.jp/recruitment_posts/ad655de82471df86af4f19469fe4c0de

https://youtrust.jp/recruitment_posts/7141d690aaa5ed348de45757da069e81

RemitAid とは...?

https://remitaid.co.jp/
https://remitaid.io/

RemitAid Tech Blog
RemitAid Tech Blog

Discussion