性能优化
2026/1/15大约 5 分钟GORM性能优化Performance
性能优化
一、连接池优化
1.1 配置连接池
sqlDB, err := db.DB()
// 设置最大空闲连接数
sqlDB.SetMaxIdleConns(10)
// 设置最大打开连接数
sqlDB.SetMaxOpenConns(100)
// 设置连接最大生命周期
sqlDB.SetConnMaxLifetime(time.Hour)
// 设置连接最大空闲时间
sqlDB.SetConnMaxIdleTime(time.Minute * 10)1.2 连接池监控
stats := sqlDB.Stats()
fmt.Printf("最大打开连接数: %d\n", stats.MaxOpenConnections)
fmt.Printf("当前打开连接数: %d\n", stats.OpenConnections)
fmt.Printf("使用中的连接数: %d\n", stats.InUse)
fmt.Printf("空闲连接数: %d\n", stats.Idle)
fmt.Printf("等待连接数: %d\n", stats.WaitCount)二、索引优化
2.1 创建索引
type User struct {
ID uint
Name string `gorm:"index"` // 单字段索引
Email string `gorm:"uniqueIndex"` // 唯一索引
Age int `gorm:"index:idx_age_status"` // 复合索引
Status int `gorm:"index:idx_age_status"` // 复合索引
}2.2 使用索引
// 好:使用索引字段查询
db.Where("email = ?", "test@example.com").First(&user)
// 不好:使用非索引字段
db.Where("nickname = ?", "张三").First(&user)
// 好:复合索引按顺序使用
db.Where("age = ? AND status = ?", 18, 1).Find(&users)
// 不好:跳过复合索引第一个字段
db.Where("status = ?", 1).Find(&users)2.3 避免索引失效
// 索引失效:使用函数
db.Where("YEAR(created_at) = ?", 2024).Find(&users)
// 优化:直接比较
db.Where("created_at >= ? AND created_at < ?",
time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)).Find(&users)
// 索引失效:使用 LIKE '%xxx'
db.Where("name LIKE ?", "%张三%").Find(&users)
// 优化:使用全文索引或搜索引擎三、查询优化
3.1 只查询需要的字段
// 不好:查询所有字段
db.Find(&users)
// 好:只查询需要的字段
db.Select("id", "name", "email").Find(&users)
// 使用结构体
type UserBasic struct {
ID uint
Name string
Email string
}
db.Model(&User{}).Find(&userBasics)3.2 分页查询
func (s *UserService) List(page, pageSize int) ([]User, int64, error) {
var users []User
var total int64
// 先统计总数
s.db.Model(&User{}).Count(&total)
// 再查询数据
offset := (page - 1) * pageSize
err := s.db.Offset(offset).Limit(pageSize).Find(&users).Error
return users, total, err
}3.3 避免 N+1 查询
// 不好:N+1 查询
var users []User
db.Find(&users)
for _, user := range users {
var articles []Article
db.Where("user_id = ?", user.ID).Find(&articles)
// 处理文章
}
// 好:使用预加载
var users []User
db.Preload("Articles").Find(&users)
for _, user := range users {
// 直接使用 user.Articles
}3.4 使用 Joins 代替 Preload
// Preload:两次查询
db.Preload("Company").Find(&users)
// SELECT * FROM users;
// SELECT * FROM companies WHERE id IN (1,2,3);
// Joins:一次查询(适合一对一)
db.Joins("Company").Find(&users)
// SELECT users.*, companies.* FROM users
// LEFT JOIN companies ON companies.id = users.company_id四、批量操作优化
4.1 批量插入
users := make([]User, 10000)
// 不好:逐条插入
for _, user := range users {
db.Create(&user)
}
// 好:批量插入
db.CreateInBatches(users, 1000)4.2 批量更新
// 不好:逐条更新
for _, user := range users {
db.Model(&user).Update("status", 1)
}
// 好:批量更新
db.Model(&User{}).Where("id IN ?", userIDs).Update("status", 1)4.3 批量查询
// 使用 FindInBatches
db.Where("status = ?", 1).FindInBatches(&users, 1000, func(tx *gorm.DB, batch int) error {
// 处理每批数据
for _, user := range users {
// 处理用户
}
return nil
})五、预编译
5.1 启用预编译
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
PrepareStmt: true, // 启用预编译
})5.2 预编译优势
// 启用预编译后,相同的 SQL 只编译一次
for i := 0; i < 1000; i++ {
db.Where("id = ?", i).First(&user)
}
// SQL 只编译一次:SELECT * FROM users WHERE id = ?六、缓存优化
6.1 查询结果缓存
import "github.com/go-redis/redis/v8"
type UserService struct {
db *gorm.DB
redis *redis.Client
}
func (s *UserService) GetUser(id uint) (*User, error) {
// 先从缓存获取
cacheKey := fmt.Sprintf("user:%d", id)
cached, err := s.redis.Get(ctx, cacheKey).Result()
if err == nil {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}
// 缓存未命中,查询数据库
var user User
if err := s.db.First(&user, id).Error; err != nil {
return nil, err
}
// 写入缓存
data, _ := json.Marshal(user)
s.redis.Set(ctx, cacheKey, data, time.Hour)
return &user, nil
}6.2 缓存更新策略
func (s *UserService) Update(id uint, updates map[string]interface{}) error {
// 更新数据库
if err := s.db.Model(&User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
return err
}
// 删除缓存
cacheKey := fmt.Sprintf("user:%d", id)
s.redis.Del(ctx, cacheKey)
return nil
}七、读写分离
7.1 配置读写分离
import "gorm.io/plugin/dbresolver"
db.Use(dbresolver.Register(dbresolver.Config{
// 从库
Replicas: []gorm.Dialector{
mysql.Open("user:pass@tcp(slave1:3306)/db?charset=utf8mb4"),
mysql.Open("user:pass@tcp(slave2:3306)/db?charset=utf8mb4"),
},
// 负载均衡策略
Policy: dbresolver.RandomPolicy{},
}))7.2 使用读写分离
// 读操作自动使用从库
db.Find(&users)
// 写操作使用主库
db.Create(&user)
// 强制使用主库
db.Clauses(dbresolver.Write).Find(&users)
// 强制使用从库
db.Clauses(dbresolver.Read).Find(&users)八、SQL 日志优化
8.1 生产环境关闭日志
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})8.2 慢查询日志
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond, // 慢查询阈值
LogLevel: logger.Warn, // 只记录慢查询
},
)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})九、性能监控
9.1 查询时间监控
type QueryMonitor struct{}
func (QueryMonitor) Name() string {
return "query_monitor"
}
func (QueryMonitor) Initialize(db *gorm.DB) error {
return db.Callback().Query().Before("gorm:query").Register("query_monitor:before", func(db *gorm.DB) {
db.InstanceSet("start_time", time.Now())
})
}
func (QueryMonitor) After() func(*gorm.DB) {
return func(db *gorm.DB) {
if startTime, ok := db.InstanceGet("start_time"); ok {
duration := time.Since(startTime.(time.Time))
if duration > 100*time.Millisecond {
log.Printf("慢查询: %s, 耗时: %v", db.Statement.SQL.String(), duration)
}
}
}
}
// 注册插件
db.Use(&QueryMonitor{})9.2 Prometheus 监控
import "github.com/prometheus/client_golang/prometheus"
var (
queryDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "gorm_query_duration_seconds",
Help: "GORM query duration",
},
[]string{"table", "operation"},
)
)
func init() {
prometheus.MustRegister(queryDuration)
}
// 在回调中记录指标
db.Callback().Query().After("gorm:query").Register("prometheus:after", func(db *gorm.DB) {
if startTime, ok := db.InstanceGet("start_time"); ok {
duration := time.Since(startTime.(time.Time)).Seconds()
queryDuration.WithLabelValues(
db.Statement.Table,
"query",
).Observe(duration)
}
})十、最佳实践
10.1 避免 SELECT *
// 不好
db.Find(&users)
// 好
db.Select("id", "name", "email").Find(&users)10.2 使用 Count 优化
// 不好:查询所有数据再统计
var users []User
db.Find(&users)
count := len(users)
// 好:直接统计
var count int64
db.Model(&User{}).Count(&count)10.3 避免在循环中查询
// 不好
for _, id := range userIDs {
var user User
db.First(&user, id)
}
// 好
var users []User
db.Find(&users, userIDs)10.4 使用事务批量操作
// 不好
for _, user := range users {
db.Create(&user)
}
// 好
db.Transaction(func(tx *gorm.DB) error {
return tx.CreateInBatches(users, 100).Error
})十一、性能测试
11.1 基准测试
func BenchmarkQuery(b *testing.B) {
for i := 0; i < b.N; i++ {
var user User
db.First(&user, 1)
}
}
func BenchmarkQueryWithSelect(b *testing.B) {
for i := 0; i < b.N; i++ {
var user User
db.Select("id", "name").First(&user, 1)
}
}11.2 EXPLAIN 分析
// 查看执行计划
var result map[string]interface{}
db.Raw("EXPLAIN SELECT * FROM users WHERE age > ?", 18).Scan(&result)
fmt.Println(result)