--- tags: [GORM, Go, ORM, 安装, 初始化, 数据库连接] create time: 2026-04-28 00:00 --- # 安装与初始化 ## 概述 本章介绍 GORM 的安装流程以及三种初始化方式:全局实例、独立 Session 和自定义配置。理解这些基础是后续学习 CRUD 和操作链路的前提。 ## 安装 ```bash 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 的还是标准库。 ## 核心概念 在动手之前,先厘清三个容易混淆的对象: ```mermaid 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()` 等方法产生的新实例,不影响原实例 | 临时会话 | ## 初始化方式 ### 方式一:全局单例(最简单) ```go 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` 在高并发场景下可能成为瓶颈。**生产环境推荐使用函数级局部变量 + 依赖注入**。 ### 方式二:函数内局部实例(推荐) ```go 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{}) } // 每次请求或每个服务组件持有一个实例 ``` ### 方式三:函数内局部实例 + 连接池配置(生产推荐) ```go 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` | ### 调试模式配置示例 ```go 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 操作链路概览 ```mermaid 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 ``` ## 默认事务机制说明 ```go db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ SkipDefaultTransaction: true, // 跳过默认事务包装 }) ``` 启用后,单次 `Create` / `Update` / `Delete` 操作不再自动包裹事务,性能提升约 10%~20%。**但代价是失去了操作原子性保障**,如果业务逻辑本身只包含单个 SQL 语句且不需要回滚,可以考虑开启。 ## GORM 思考题 > [!question] 为什么 GORM 的 `Create` 操作会被自动包裹在事务中?这样做有什么利弊? > > **详见**:[[01-安装与初始化/为什么GORM的Create会被自动包裹在事务中的]] ## 关联笔记 - [[01-安装与初始化/为什么GORM的Create会被自动包裹在事务中的]] - [[02-模型定义]] - [[03-CRUD 操作]] - [[hhs/Go/]]