7.6 KiB
7.6 KiB
tags, create time
| tags | create time | ||||||
|---|---|---|---|---|---|---|---|
|
2026-04-28 00:00 |
安装与初始化
概述
本章介绍 GORM 的安装流程以及三种初始化方式:全局实例、独立 Session 和自定义配置。理解这些基础是后续学习 CRUD 和操作链路的前提。
安装
go get gorm.io/gorm@latest
go get gorm.io/driver/mysql@latest # MySQL
# go get gorm.io/driver/postgres@latest # PostgreSQL
# go get gorm.io/driver/sqlite@latest # SQLite
[!tip] 驱动选择 GORM 本身不包含任何数据库驱动,你安装的
gorm.io/driver/xxx包只是适配器(Adapter)。GORM 底层依赖database/sql,所以最终执行 SQL 的还是标准库。
核心概念
在动手之前,先厘清三个容易混淆的对象:
flowchart LR
Adapter["适配器 Driver"] --> ORM["GORM数据库层"]
ORM --> Builder["查询构建器 *gorm.DB"]
Builder --> Result["最终SQL"]
style Adapter fill:#EAB308,color:#fff
style ORM fill:#4FC08D,color:#fff
style Builder fill:#3B82F6,color:#fff
style Result fill:#A0AEC0,color:#fff
| 对象 | 作用 | 类比 |
|---|---|---|
| Driver | 将 *sql.DB 转化为 *gorm.DB |
翻译官 |
*gorm.DB (DB) |
全局数据库实例,存储连接池和默认配置 | 数据库连接池 |
*gorm.DB (Session) |
通过 Session() / Where() 等方法产生的新实例,不影响原实例 |
临时会话 |
初始化方式
方式一:全局单例(最简单)
import (
"gorm.io/gorm"
_ "gorm.io/driver/mysql" // 空白导入驱动包
)
var DB *gorm.DB
func InitDB() error {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
return err
}
[!warning] 全局变量陷阱 虽然方便,但全局
*gorm.DB在高并发场景下可能成为瓶颈。生产环境推荐使用函数级局部变量 + 依赖注入。
方式二:函数内局部实例(推荐)
func NewGORMInstance() (*gorm.DB, error) {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
// 每次请求或每个服务组件持有一个实例
方式三:函数内局部实例 + 连接池配置(生产推荐)
func NewDB(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 调试时开启 SQL 日志
})
if err != nil {
return nil, fmt.Errorf("connect db failed: %w", err)
}
sqlDB, _ := db.DB() // 获取底层 *sql.DB 用于调优连接池
sqlDB.SetMaxOpenConns(100) // 最大打开连接数
sqlDB.SetMaxIdleConns(20) // 最大空闲连接数
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间,避免被 MySQL 8h 超时断开
return db, nil
}
// ---- 依赖注入使用 ----
type UserService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) *UserService {
return &UserService{db: db}
}
func (s *UserService) List() ([]User, error) {
var users []User
return users, s.db.Find(&users).Error // Find 返回 *gorm.DB,取 .Error 得错误
}
[!tip] 为什么不用全局变量?
全局变量 局部 + DI 方便但难以测试 易 mocking、可并行测试 隐藏依赖关系 接口清晰,一眼看出需要什么 并发修改 config 互相影响 各组件配置独立
*gorm.DB本身是 goroutine-safe 的,共享同一个实例不会有问题。需要的是「一个实例共享」而非「每个请求新建」。
关键配置项 gorm.Config
| 字段 | 类型 | 说明 | 默认值 |
|---|---|---|---|
DefaultPageSize |
int |
分页默认 Limit | 0(无限制) |
SkipDefaultTransaction |
bool |
是否跳过默认事务包装 | false |
DisableForeignKeyConstraintWhenMigrating |
bool |
迁移时是否忽略外键约束 | false |
IgnoreRelationshipsWhenMigration |
bool |
迁移时是否忽略关联定义 | false |
PrepareStmt |
bool |
是否启用预编译语句缓存 | false |
TranslateError |
bool |
是否将错误转为 GORM 格式 | false |
Logger |
logger.Interface |
自定义日志输出器 | Panic |
NowFunc |
func() time.Time |
覆盖当前时间(测试用) | time.Now |
调试模式配置示例
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 开启详细 SQL 日志
})
// 输出示例:[info] ... [0.12ms] [rows:-] SELECT * FROM users
连接池调优(\*sql.DB)
上面的方式三已经展示了基本用法,这里展开说明每个参数的含义和推荐值:
| 方法 | 作用 | 推荐值 | 说明 |
|---|---|---|---|
SetMaxOpenConns(n) |
最大打开连接数(含在用 + 空闲) | 根据 QPS 评估 | 不超过 MySQL max_connections |
SetMaxIdleConns(n) |
最大空闲连接数 | CPU 核数或 10~20 | 越多越能应对突发流量 |
SetConnMaxLifetime(d) |
连接最大存活时间 | 5~10 分钟 | 必须小于 MySQL 的 wait_timeout(默认 8h),否则 GORM 拿到被服务端断开的连接会报错 |
SetConnMaxIdleTime(d) |
空闲连接回收时间(Go 1.15+) | 5 分钟 | 减少闲置连接占用 |
[!warning] 常见踩坑
- 忘记设
SetConnMaxLifetime→ 连接池中存活几年的旧连接,MySQL 早已断开,GORM 复用时报server has gone away。MaxOpenConns = 0(默认)→ 无上限,压测时可能打满数据库连接导致其他业务受影响。MaxIdleConns > MaxOpenConns→ panic,空闲连接不能超过最大打开数。
DSN 模板速查
| 数据库 | 驱动包 | DSN 示例 |
|---|---|---|
| MySQL | gorm.io/driver/mysql |
user:pass@tcp(host:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local |
| PostgreSQL | gorm.io/driver/postgres |
host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=require TimeZone=Asia/Shanghai |
| SQLite | gorm.io/driver/sqlite |
/path/to/db.sqlite(只需文件路径) |
| SQL Server | gorm.io/driver/mssql |
sqlserver://user:pass@host:1433?database=dbname |
GORM 操作链路概览
flowchart TD
Init["创建数据库实例"] --> Model["定义模型结构体"]
Model --> Migrate["迁移建表 AutoMigrate"]
Migrate --> Operate["CRUD 数据操作"]
Operate --> Tx["事务提交或回滚"]
Operate --> Return["返回结果"]
style Init fill:#4FC08D,color:#fff
style Migrate fill:#EAB308,color:#fff
style Operate fill:#3B82F6,color:#fff
style Tx fill:#EF4444,color:#fff
默认事务机制说明
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true, // 跳过默认事务包装
})
启用后,单次 Create / Update / Delete 操作不再自动包裹事务,性能提升约 10%~20%。但代价是失去了操作原子性保障,如果业务逻辑本身只包含单个 SQL 语句且不需要回滚,可以考虑开启。
GORM 思考题
[!question] 为什么 GORM 的
Create操作会被自动包裹在事务中?这样做有什么利弊?