golang-migrate/migrate —— 它是 Go 社区最主流、生产级、轻量且与框架无关的迁移方案,“不用反射、手写 SQL”的风格。
✅ 为什么选 golang-migrate/migrate?
| 特性 | 说明 |
|---|---|
| 纯 SQL 迁移 | 你写 .sql 文件,完全掌控 DDL/DML,无黑盒 ORM |
| 命令行 + 库双模式 | 可 CLI 执行,也可嵌入 Gin 启动时自动运行 |
| 支持 MySQL/PostgreSQL 等 | 你的 MySQL 完美兼容 |
| 版本控制 | 自动生成 schema_migrations 表记录版本 |
| Go 原生 | 无 CGO 依赖,编译简单 |
⚠️ 不推荐 GORM 的 AutoMigrate(黑盒、难回滚)或 sqlc(只生成查询,不管理结构变更)
一、安装 CLI 工具(开发用)
# macOS (Homebrew)
brew install golang-migrate
# Linux / Windows: 下载二进制
# https://github.com/golang-migrate/migrate/releases
验证:
migrate -version
# 输出如: v4.17.0
二、在 Gin 项目中集成(自动迁移)
1. 添加 Go 依赖
go get -u github.com/golang-migrate/migrate/v4
go get -u github.com/golang-migrate/migrate/v4/database/mysql
go get -u github.com/golang-migrate/migrate/v4/source/file
2. 创建迁移目录
mkdir -p migrations
3. 生成第一个迁移文件(示例:创建 users 表)
migrate create -ext sql -dir migrations -seq create_users_table
生成:
migrations/
├── 000001_create_users_table.up.sql
└── 000001_create_users_table.down.sql
4. 编写迁移 SQL
migrations/000001_create_users_table.up.sql
CREATE TABLE `users` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`user_uid` VARCHAR(50) NOT NULL UNIQUE,
`gold` INT DEFAULT 0,
`last_login` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
migrations/000001_create_users_table.down.sql
DROP TABLE IF EXISTS `users`;
💡 每次加新表/改字段,都用
migrate create生成新文件
三、在 Gin 启动时自动执行迁移
修改 main.go
// main.go
package main
import (
"database/sql"
"log"
"net/url"
"github.com/gin-gonic/gin"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/mysql"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
// 1. 初始化数据库连接(和你之前一样)
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/game_db?parseTime=true")
if err != nil {
log.Fatal("DB connect failed:", err)
}
defer db.Close()
// 2. 执行数据库迁移(关键!)
runMigrations(db)
// 3. 启动 Gin
r := gin.Default()
// ... 注册路由
r.Run(":8080")
}
func runMigrations(db *sql.DB) {
// 构造 MySQL DSN for migrate
dsn := "user:password@tcp(localhost:3306)/game_db?multiStatements=true"
// 注意:必须 URL-encode 密码中的特殊字符(如 @, /)
// 更安全的方式:
cfg := mysql.Config{
User: "user",
Passwd: "password",
Net: "tcp",
Addr: "localhost:3306",
DBName: "game_db",
MultiStatements: true,
AllowNativePasswords: true,
}
dsn = cfg.FormatDSN()
// 创建 migrate 实例
m, err := migrate.New(
"file://./migrations", // 迁移文件目录
dsn,
)
if err != nil {
log.Fatal("Migration init failed:", err)
}
defer m.Close()
// 自动升级到最新版本
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatal("Migration failed:", err)
}
log.Println("✅ Database migrated successfully")
}
🔒 安全提示:
- 生产环境建议不要自动迁移,而是手动执行 CLI 命令
- 开发/测试环境可开启自动迁移
四、常用 CLI 命令(开发时用)
| 命令 | 说明 |
|---|---|
migrate -path migrations -database "mysql://..." up | 升级到最新 |
migrate -path migrations -database "mysql://..." down | 回滚一步 |
migrate -path migrations -database "mysql://..." goto 1 | 回滚到指定版本 |
migrate -path migrations -database "mysql://..." version | 查看当前版本 |
示例(本地开发):
migrate -path ./migrations -database "mysql://root:123456@tcp(127.0.0.1:3306)/game_db?multiStatements=true" up
五、项目结构建议
your-gin-project/
├── main.go
├── migrations/ # ← 迁移文件目录(纳入 Git)
│ ├── 000001_create_users.up.sql
│ ├── 000001_create_users.down.sql
│ └── 000002_add_gacha_tables.up.sql
├── model/ # 手写 Scan 的模型
├── service/
├── go.mod
└── ...
六、替代方案对比
| 工具 | 优点 | 缺点 | 适合你吗? |
|---|---|---|---|
| golang-migrate | 纯 SQL、轻量、生产级 | 需手写 SQL | ✅ 强烈推荐 |
| GORM AutoMigrate | 代码即 schema | 黑盒、难回滚、类型限制多 | ❌ |
| sqlc | 生成类型安全查询 | 不管理 DDL,需另配迁移工具 | ⚠️ 需搭配 migrate |
| Goose | 类似 migrate | 社区活跃度较低 | 可用,但 migrate 更主流 |
总结
- 开发阶段:用
migrate create生成.sql文件,写好up/down - 本地启动 Gin:自动执行
m.Up()(仅开发环境) - 上线部署:用 CLI 手动执行迁移,确保可控