您是否曾經遇到過這樣的情況:當您使用更新的資料庫架構在生產環境中部署新的更新時,但之後出現錯誤並需要恢復內容......這就是遷移出現的情況。
資料庫遷移有幾個關鍵目的:
要使用 GORM 和 MySQL 為 Golang 服務建立全面的生產級設置,以便輕鬆遷移、更新和回滾,您需要包含遷移工具、處理資料庫連接池並確保正確的結構定義。這是一個完整的範例來引導您完成整個過程:
/golang-service |-- main.go |-- database | |-- migration.go |-- models | |-- user.go |-- config | |-- config.go |-- migrations | |-- ... |-- go.mod
package config import ( "fmt" "log" "os" "time" "github.com/joho/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func ConnectDB() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis. // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type. // loc=Local: Uses the local timezone of the server for time-related queries and storage. dsn := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"), ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") } sqlDB, err := db.DB() if err != nil { panic("failed to configure database connection") } // Set connection pool settings sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) // 1.sqlDB.SetMaxIdleConns(10) // Sets the maximum number of idle (unused but open) connections in the connection pool. // A value of 10 means up to 10 connections can remain idle, ready to be reused. // 2. sqlDB.SetMaxOpenConns(100): // Sets the maximum number of open (active or idle) connections that can be created to the database. // A value of 100 limits the total number of connections, helping to prevent overloading the database. // 3. sqlDB.SetConnMaxLifetime(time.Hour): // Sets the maximum amount of time a connection can be reused before it’s closed. // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed. DB = db }
package database import ( "golang-service/models" "golang-service/migrations" "gorm.io/gorm" ) func Migrate(db *gorm.DB) { db.AutoMigrate(&models.User{}) // Apply additional custom migrations if needed }
package models import "gorm.io/gorm" type User struct { gorm.Model Name string `json:"name"` }
DB_USER=root DB_PASS=yourpassword DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=yourdb
package main import ( "golang-service/config" "golang-service/database" "golang-service/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) func main() { config.ConnectDB() database.Migrate(config.DB) r := gin.Default() r.POST("/users", createUser) r.GET("/users/:id", getUser) r.Run(":8080") } func createUser(c *gin.Context) { var user models.User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if err := config.DB.Create(&user).Error; err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(201, user) } func getUser(c *gin.Context) { id := c.Param("id") var user models.User if err := config.DB.First(&user, id).Error; err != nil { c.JSON(404, gin.H{"error": "User not found"}) return } c.JSON(200, user) }
對於生產環境,我們可以使用像 golang-migrate 這樣的遷移程式庫來應用、回滾或重做遷移。
安裝golang-migrate:
go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
為使用者表產生遷移檔案
migrate create -ext=sql -dir=./migrations -seq create_users_table
運行指令後,我們將得到一對 .up.sql (用於更新架構)和 down.sql (用於稍後可能的回滾)。數字000001是自動產生的遷移索引。
/golang-service |-- migrations | |-- 000001_create_users_table.down.sql | |-- 000001_create_users_table.up.sql
新增相關sql指令到.up檔和.down檔。
000001_create_users_table.up.sql
CREATE TABLE users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, created_at DATETIME, updated_at DATETIME, deleted_at DATETIME);
000001_create_users_table.down.sql
DROP TABLE IF EXISTS users;
運行向上遷移並使用以下命令將變更套用到資料庫(-verbose 標誌以查看更多日誌詳細資訊):
migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" -verbose up
如果我們遇到遷移問題,我們可以使用以下命令查看目前的遷移版本及其狀態:
migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" version
如果因為某些原因導致遷移失敗,我們可以考慮使用強制(謹慎使用)指令以及髒遷移的版本號。如果版本是1(可以在migrations或schema_migrations表中檢查),我們將運行:
migrate -path ./migrations -database "mysql://user:password@tcp(localhost:3306)/dbname" force 1
在某個時間點,我們可能想要新增功能,其中一些可能需要更改資料方案,例如我們想要在使用者表格中新增電子郵件欄位。我們將按照以下方式進行。
進行新的遷移,將電子郵件列新增至使用者表
migrate create -ext=sql -dir=./migrations -seq add_email_to_users
現在我們有了一對新的 .up.sql 和 .down.sql
/golang-service |-- migrations | |-- 000001_create_users_table.down.sql | |-- 000001_create_users_table.up.sql | |-- 000002_add_email_to_users.down.sql | |-- 000002_add_email_to_users.up.sql
將以下內容加入*_add_email_to_users.*.sql檔案
000002_add_email_to_users.up.sql
/golang-service |-- main.go |-- database | |-- migration.go |-- models | |-- user.go |-- config | |-- config.go |-- migrations | |-- ... |-- go.mod
000002_add_email_to_users.down.sql
package config import ( "fmt" "log" "os" "time" "github.com/joho/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func ConnectDB() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis. // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type. // loc=Local: Uses the local timezone of the server for time-related queries and storage. dsn := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"), ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") } sqlDB, err := db.DB() if err != nil { panic("failed to configure database connection") } // Set connection pool settings sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) // 1.sqlDB.SetMaxIdleConns(10) // Sets the maximum number of idle (unused but open) connections in the connection pool. // A value of 10 means up to 10 connections can remain idle, ready to be reused. // 2. sqlDB.SetMaxOpenConns(100): // Sets the maximum number of open (active or idle) connections that can be created to the database. // A value of 100 limits the total number of connections, helping to prevent overloading the database. // 3. sqlDB.SetConnMaxLifetime(time.Hour): // Sets the maximum amount of time a connection can be reused before it’s closed. // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed. DB = db }
再次執行向上遷移指令以更新資料模式
package database import ( "golang-service/models" "golang-service/migrations" "gorm.io/gorm" ) func Migrate(db *gorm.DB) { db.AutoMigrate(&models.User{}) // Apply additional custom migrations if needed }
我們還需要更新 golang users 結構(新增 Email 欄位)以使其與新模式保持同步..
package models import "gorm.io/gorm" type User struct { gorm.Model Name string `json:"name"` }
如果因為某些原因我們在新更新的模式中出現錯誤,並且需要回滾,這種情況下我們將使用 down 指令:
DB_USER=root DB_PASS=yourpassword DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=yourdb
數字 1 表示我們要回滾 1 個遷移。
這裡我們還需要手動更新 golang users 結構(刪除 Email 欄位)以反映資料架構變更。
package main import ( "golang-service/config" "golang-service/database" "golang-service/models" "github.com/gin-gonic/gin" "gorm.io/gorm" ) func main() { config.ConnectDB() database.Migrate(config.DB) r := gin.Default() r.POST("/users", createUser) r.GET("/users/:id", getUser) r.Run(":8080") } func createUser(c *gin.Context) { var user models.User if err := c.ShouldBindJSON(&user); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } if err := config.DB.Create(&user).Error; err != nil { c.JSON(500, gin.H{"error": err.Error()}) return } c.JSON(201, user) } func getUser(c *gin.Context) { id := c.Param("id") var user models.User if err := config.DB.First(&user, id).Error; err != nil { c.JSON(404, gin.H{"error": "User not found"}) return } c.JSON(200, user) }
為了簡化遷移和回滾的過程,我們可以新增一個Makefile。
go install -tags 'mysql' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
Makefile 內容如下。
migrate create -ext=sql -dir=./migrations -seq create_users_table
現在我們只需在 CLI 上執行 make migrate_up 或 make migrate_down 即可進行遷移和回滾。
在回滾遷移或進行可能影響資料庫的變更之前,以下是需要考慮的一些關鍵點。
所以備份資料至關重要。這是一個簡短的指南:
資料庫轉儲:
使用特定於資料庫的工具建立資料庫的完整備份。對於 MySQL,您可以使用:
/golang-service |-- main.go |-- database | |-- migration.go |-- models | |-- user.go |-- config | |-- config.go |-- migrations | |-- ... |-- go.mod
這將建立一個檔案 (backup_before_rollback.sql),其中包含 dbname 資料庫的所有資料和架構。
匯出特定表:
如果您只需要備份某些表,請在 mysqldump 指令中指定它們:
package config import ( "fmt" "log" "os" "time" "github.com/joho/godotenv" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB func ConnectDB() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } // charset=utf8mb4: Sets the character set to utf8mb4, which supports all Unicode characters, including emojis. // parseTime=True: Tells the driver to automatically parse DATE and DATETIME values into Go's time.Time type. // loc=Local: Uses the local timezone of the server for time-related queries and storage. dsn := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"), ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { panic("failed to connect database") } sqlDB, err := db.DB() if err != nil { panic("failed to configure database connection") } // Set connection pool settings sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) // 1.sqlDB.SetMaxIdleConns(10) // Sets the maximum number of idle (unused but open) connections in the connection pool. // A value of 10 means up to 10 connections can remain idle, ready to be reused. // 2. sqlDB.SetMaxOpenConns(100): // Sets the maximum number of open (active or idle) connections that can be created to the database. // A value of 100 limits the total number of connections, helping to prevent overloading the database. // 3. sqlDB.SetConnMaxLifetime(time.Hour): // Sets the maximum amount of time a connection can be reused before it’s closed. // A value of time.Hour means that each connection will be kept for up to 1 hour, after which it will be discarded and a new connection will be created if needed. DB = db }
驗證備份:
確保備份檔案已建立並檢查其大小或開啟它以確保它包含必要的資料。
安全地儲存備份:
將備份副本保存在安全位置,例如雲端儲存或單獨的伺服器,以防止回溯過程中資料遺失。
要在使用 Golang 並部署在 AWS EKS 上時備份 MySQL 數據,您可以按照以下步驟操作:
使用mysqldump進行資料庫備份:
使用 Kubernetes cron 作業建立 MySQL 資料庫的 mysqldump。
package database import ( "golang-service/models" "golang-service/migrations" "gorm.io/gorm" ) func Migrate(db *gorm.DB) { db.AutoMigrate(&models.User{}) // Apply additional custom migrations if needed }
將其儲存在持久性磁碟區或 S3 儲存桶中。
使用 Kubernetes CronJob 實現自動化:
使用 Kubernetes CronJob 自動化 mysqldump 流程。
YAML 設定範例:yaml
package models import "gorm.io/gorm" type User struct { gorm.Model Name string `json:"name"` }
`
使用 AWS RDS 自動備份(如果使用 RDS):
如果您的 MySQL 資料庫位於 AWS RDS 上,您可以利用 RDS 自動備份和快照。
設定備份保留期並手動拍攝快照或使用 Lambda 函數自動拍攝快照。
使用 Velero 備份持久卷 (PV):
使用 Kubernetes 備份工具 Velero 來備份保存 MySQL 資料的持久卷。
在您的 EKS 叢集上安裝 Velero 並將其配置為備份到 S3。
透過使用這些方法,您可以確保您的 MySQL 資料得到定期備份和安全儲存。
以上是資料庫遷移對於 Golang 服務,為什麼重要?的詳細內容。更多資訊請關注PHP中文網其他相關文章!