はじめに
データベースのスキーマ管理は、アプリケーション開発において重要なポイントです。
個人の備忘録程度の走り書きとなっておりますが、温かい目で見守っていただければ幸いです。
特に AutoMigrate()
のような自動マイグレーション機能を使う場合、「何度も実行しても問題ないのか?」と疑問に思うことがあります。
記事を書こうと思ったきっかけ
Gorm の AutoMigrate()
を使用したマイグレーション処理に関して、「何度も実行しても問題ないのか?」という疑問を持つ人が多いため、
その動作や安全な運用方法についてまとめることにしました。
結論: dbConn.AutoMigrate() の動作による
現在のコードは Gorm の AutoMigrate()
を使ってマイグレーションを実行しているため、「すでに適用済みのマイグレーションはスキップされる」 仕様になっており、基本的に何度実行しても問題ありません。
補足:現在のコードは、以下になります
dbConn.AutoMigrate() の動作
Gorm の AutoMigrate()
は以下のように動作します。
- テーブルが存在しない場合、新規作成する
- カラムが足りない場合、追加する
- 既存のカラムのデータ型や制約の変更はしない
- 既存のカラムやテーブルの削除はしない
つまり、AutoMigrate()
は破壊的な変更(カラム削除・変更)をしないため、何度実行しても問題が発生することは少ないです。
問題になる可能性があるケース
以下のような場合は、毎回の実行で問題が発生する可能性があります。
1. AutoMigrate()
以外に DROP TABLE
や ALTER TABLE
を実行している場合
AutoMigrate()
だけなら安全ですが、明示的に DROP TABLE
や ALTER TABLE
を使っている場合、データが消えたり、不整合が発生する可能性があります。
2. AutoMigrate()
に依存せず、手動で SQL を実行している場合
以下のようなコードがあると、何度も実行するとエラーや不整合が発生する可能性があります。
// すでにテーブルが存在するとエラーになる
dbConn.Exec("CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));")
3. AutoMigrate()
の前に DROP TABLE
を実行している場合
dbConn.Exec("DROP TABLE IF EXISTS users;") // データが消える
dbConn.AutoMigrate(&model.User{})
このようなコードがある場合、データが毎回削除されるので要注意です。
安全にするための対策
方法 1: AutoMigrate()
で対応
現在のコードのままで AutoMigrate()
を使うなら基本的に問題ありませんが、Gorm の AutoMigrate()
の仕様を理解し、破壊的な変更をしないことを確認する必要があります。
err = dbConn.AutoMigrate(
&model.User{},
&model.Category{},
&model.Question{},
&model.Answer{},
&model.Choice{},
&model.Score{},
)
方法 2: マイグレーションの実行を1回だけにする
migrate.lock
ファイルを使い、一度だけマイグレーションを実行するようにする。
package main
import (
"fmt"
"log"
"os"
"skill-typing-back/db"
"skill-typing-back/model"
)
func main() {
if _, err := os.Stat("/app/migrate.lock"); err == nil {
fmt.Println("Migrations already applied, skipping.")
return
}
dbConn := db.NewDB()
if dbConn == nil {
log.Fatal("データベースの初期化に失敗しました")
}
log.Println("データベースの初期化に成功")
sqlDB, err := dbConn.DB()
if err != nil {
log.Fatal("Failed to get DB connection:", err)
}
defer sqlDB.Close()
fmt.Println("Starting migration...")
err = dbConn.AutoMigrate(
&model.User{},
&model.Category{},
&model.Question{},
&model.Answer{},
&model.Choice{},
&model.Score{},
)
if err != nil {
log.Fatal("Migration failed:", err)
}
f, err := os.Create("/app/migrate.lock")
if err != nil {
log.Fatal("Failed to create migrate.lock:", err)
}
defer f.Close()
fmt.Println("Successfully Migrated")
}
まとめ
AutoMigrate()
は基本的に何度実行しても問題なく、idempotent であるため、特に気にせず使える点は便利だと感じました。
ただし、より安全に運用するなら migrate.lock
を活用して、一度だけ実行する仕組みにするのがベスト だと思います。
個人的には、まず migrate.lock
を使う方法がシンプルで実装しやすく、かつ安全なので、この方法を試してみるのが良さそう だと感じました!