您是否曾经遇到过这样的情况:当您使用更新的数据库架构在生产环境中部署新的更新时,但之后出现错误并需要恢复内容......这就是迁移出现的情况。
数据库迁移有几个关键目的:
要使用 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中文网其他相关文章!