This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/GORM/01-安装与初始化.md
T
2026-04-28 20:02:42 +08:00

207 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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/]]