207 lines
7.6 KiB
Markdown
207 lines
7.6 KiB
Markdown
|
|
---
|
|||
|
|
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/]]
|