Golang后端性能优化手册(第一章:数据库性能优化)

前言:

"过早优化是万恶之源,但过晚优化可能让你失去用户"

---这是一篇帮助 你我 更好的做牛马,做更好的牛马 的文档

---刚刚开始,慢慢来

📋 目录

  • [🎯 文档说明](#🎯 文档说明)
  • [📊 性能优化全景图](#📊 性能优化全景图)
  • 💾 [第一章:数据库性能优化\](#第一章数据库性能优化)- `点击跳转相应文档`](https://blog.csdn.net/bojinyuan00/article/details/156270988) * [1.1 SQL 执行分析与优化](#1.1 SQL 执行分析与优化) * [1.2 索引优化的艺术](#1.2 索引优化的艺术) * [1.3 查询优化技巧](#1.3 查询优化技巧) * [1.4 连接池优化](#1.4 连接池优化) * [1.5 读写分离与分库分表](#1.5 读写分离与分库分表)

    • [2.1 缓存设计原则](#2.1 缓存设计原则)
    • [2.2 多级缓存架构](#2.2 多级缓存架构)
    • [2.3 缓存三大问题及解决方案](#2.3 缓存三大问题及解决方案)
    • [2.4 缓存更新策略](#2.4 缓存更新策略)
    • [2.5 Redis 性能优化](#2.5 Redis 性能优化)
  • [🎨 第三章:代码层面性能优化](#🎨 第三章:代码层面性能优化) 点击跳转相应文档
    • [3.1 内存管理与优化](#3.1 内存管理与优化)
    • [3.2 并发编程最佳实践](#3.2 并发编程最佳实践)
    • [3.3 字符串处理优化](#3.3 字符串处理优化)
    • [3.4 数据结构选择](#3.4 数据结构选择)
    • [3.5 对象复用与内存池](#3.5 对象复用与内存池)
  • [🔄 第四章:异步处理与消息队列](#🔄 第四章:异步处理与消息队列) 点击跳转相应文档
    • [4.1 异步编程模式](#4.1 异步编程模式)
    • [4.2 消息队列选型](#4.2 消息队列选型)
    • [4.3 任务队列设计](#4.3 任务队列设计)
    • [4.4 异步回调机制](#4.4 异步回调机制)
  • [🌐 第五章:网络 I/O 优化](#🌐 第五章:网络 I/O 优化) 点击跳转相应文档
    • [5.1 HTTP 性能优化](#5.1 HTTP 性能优化)
    • [5.2 gRPC 高性能实践](#5.2 gRPC 高性能实践)
    • [5.3 WebSocket 优化](#5.3 WebSocket 优化)
    • [5.4 连接复用与池化](#5.4 连接复用与池化)
  • [📈 第六章:监控、分析与调优](#📈 第六章:监控、分析与调优) 点击跳转相应文档
    • [6.1 性能监控体系](#6.1 性能监控体系)
    • [6.2 pprof 深度使用](#6.2 pprof 深度使用)
    • [6.3 链路追踪](#6.3 链路追踪)
    • [6.4 日志优化](#6.4 日志优化)
  • [🏗️ 第七章:架构层面优化](#🏗️ 第七章:架构层面优化) 点击跳转相应文档
    • [7.1 服务治理](#7.1 服务治理)
    • [7.2 限流与熔断](#7.2 限流与熔断)
    • [7.3 负载均衡策略](#7.3 负载均衡策略)
    • [7.4 CDN 与边缘计算](#7.4 CDN 与边缘计算)
  • [💡 第八章:高级优化技巧](#💡 第八章:高级优化技巧) 点击跳转相应文档
    • [8.1 CPU 缓存友好的代码](#8.1 CPU 缓存友好的代码)
    • [8.2 减少 GC 压力](#8.2 减少 GC 压力)
    • [8.3 编译优化](#8.3 编译优化)
    • [8.4 性能测试与压测](#8.4 性能测试与压测)
  • [📝 第九章:实战案例分析](#📝 第九章:实战案例分析) 点击跳转相应文档
  • [✅ 第十章:性能优化 Checklist](#✅ 第十章:性能优化 Checklist) 点击跳转相应文档

🎯 文档说明

为什么需要这份手册?

在微服务盛行的今天,后端接口性能直接影响用户体验和系统稳定性。一个响应时间从 3 秒优化到 300 毫秒的接口,不仅能让用户体验提升 10 倍,还能节省大量服务器成本。

本手册的特色

  • 实战导向:每个优化点都配有真实代码示例
  • 场景明确:清晰说明每种优化的适用场景
  • 对比鲜明:用 ❌ 和 ✅ 直观展示好坏实践
  • 深入浅出:用生动的比喻解释复杂概念
  • 可操作性强:提供完整的代码和配置示例

如何使用本手册

  1. 快速诊断:遇到性能问题时,查找对应章节
  2. 系统学习:按章节顺序学习性能优化知识体系
  3. 代码审查:用 Checklist 检查现有项目
  4. 方案设计:参考架构章节设计高性能系统

性能优化的黄金法则

💡 80/20 原则:80% 的性能问题通常来自 20% 的代码

💡 测量先行:没有测量就没有优化,先用数据说话

💡 渐进式优化:先优化瓶颈,再优化细节


📊 性能优化全景图

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        性能优化层次模型                          │
├─────────────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  架构层 🏗️  │ 服务拆分 • 负载均衡 • 限流熔断 • CDN      │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  存储层 💾  │ 数据库优化 • 缓存策略 • 读写分离           │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  应用层 ⚡  │ 代码优化 • 并发控制 • 异步处理            │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  网络层 🌐  │ 协议优化 • 连接池 • 序列化优化             │  │
│  └───────────────────────────────────────────────────────────┘  │
│  ┌───────────────────────────────────────────────────────────┐  │
│  │  监控层 📈  │ 性能监控 • 链路追踪 • 日志分析            │  │
│  └───────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

💾 第一章:数据库性能优化

"数据库就像冰山,90% 的性能问题都藏在水面下"

数据库往往是后端性能的第一瓶颈。据统计,超过 70% 的慢接口问题源于数据库查询。本章将带你深入了解数据库优化的方方面面。

1.1 SQL 执行分析与优化

📌 慢查询日志:性能问题的侦察兵

适用场景:生产环境持续监控,定位性能瓶颈

go 复制代码
// 配置 GORM 慢查询日志
import (
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "log"
    "os"
    "time"
)

func InitDB() *gorm.DB {
    // 自定义日志配置
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags),
        logger.Config{
            SlowThreshold:             200 * time.Millisecond, // 慢查询阈值:200ms
            LogLevel:                  logger.Warn,            // 只记录警告级别
            IgnoreRecordNotFoundError: true,                   // 忽略 ErrRecordNotFound
            Colorful:                  true,                   // 彩色输出
        },
    )

    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
        Logger: newLogger,
    })
    
    if err != nil {
        panic("failed to connect database")
    }
    
    return db
}

// 输出示例:
// [2.156s] [rows:1523] SELECT * FROM orders WHERE user_id = 12345
// ⚠️  这条查询太慢了,需要优化!
📌 EXPLAIN:SQL 的体检报告

适用场景:开发阶段分析查询计划,优化 SQL 语句

go 复制代码
// 在 Go 中使用 EXPLAIN 分析查询
func AnalyzeQuery(db *gorm.DB) {
    var result []map[string]interface{}
    
    // 使用 EXPLAIN 分析查询
    db.Raw(`
        EXPLAIN 
        SELECT u.name, o.order_no, o.amount 
        FROM users u 
        LEFT JOIN orders o ON u.id = o.user_id 
        WHERE u.email = ? 
        AND o.created_at > ?
    `, "test@example.com", time.Now().AddDate(0, -1, 0)).Scan(&result)
    
    // 打印分析结果
    for _, row := range result {
        fmt.Printf("Type: %v, Rows: %v, Extra: %v\n", 
            row["type"], row["rows"], row["Extra"])
    }
}

EXPLAIN 关键字段解读

字段 好的值 坏的值 说明
type const, ref, range ALL, index ALL 意味着全表扫描,性能最差
rows 越小越好 数万以上 预计扫描的行数
Extra Using index Using filesort, Using temporary filesort 表示需要额外排序
key 有索引名 NULL NULL 表示没有使用索引
📌 N+1 查询问题:性能杀手 No.1

问题场景:查询用户列表,然后循环查询每个用户的订单

go 复制代码
// ❌ 糟糕的做法:N+1 查询问题
func GetUsersWithOrdersBad(db *gorm.DB) []UserWithOrders {
    var users []User
    db.Find(&users) // 1 次查询
    
    var result []UserWithOrders
    for _, user := range users { // N 次查询
        var orders []Order
        db.Where("user_id = ?", user.ID).Find(&orders) // 每个用户都查一次!
        
        result = append(result, UserWithOrders{
            User:   user,
            Orders: orders,
        })
    }
    return result
}
// 如果有 100 个用户,就会执行 101 次查询!😱

// ✅ 优秀的做法:使用 JOIN 或 Preload
func GetUsersWithOrdersGood(db *gorm.DB) []User {
    var users []User
    // 方法一:使用 Preload(推荐)
    db.Preload("Orders").Find(&users) // 只需 2 次查询!
    return users
}

// ✅ 方法二:使用 Joins(更高效)
func GetUsersWithOrdersJoin(db *gorm.DB) []UserWithOrders {
    var results []UserWithOrders
    db.Table("users").
        Select("users.*, orders.*").
        Joins("LEFT JOIN orders ON users.id = orders.user_id").
        Scan(&results) // 只需 1 次查询!
    return results
}

// 性能对比:
// ❌ N+1 查询:101 次数据库请求,耗时约 1000ms
// ✅ Preload:  2 次数据库请求,耗时约 50ms
// ✅ JOIN:     1 次数据库请求,耗时约 30ms
📌 只查询需要的字段

适用场景:列表查询、API 响应字段较少时

go 复制代码
// ❌ 查询所有字段(包括大字段)
func GetUsersBad(db *gorm.DB) []User {
    var users []User
    db.Find(&users) // SELECT * FROM users
    // 即使不需要,也会查询 avatar(大图)、description(长文本)等字段
    return users
}

// ✅ 只查询需要的字段
func GetUsersGood(db *gorm.DB) []UserSimple {
    var users []UserSimple
    db.Model(&User{}).
        Select("id, name, email, created_at"). // 只查询必要字段
        Find(&users)
    return users
}

// ✅ 使用 DTO 而不是完整实体
type UserDTO struct {
    ID        int64     `json:"id"`
    Name      string    `json:"name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
    // 不包含 Avatar、Description 等大字段
}

// 性能提升:
// 假设 users 表有 20 个字段,包含大字段
// ❌ SELECT *:每行数据 5KB,100 行 = 500KB
// ✅ SELECT 4 字段:每行数据 0.5KB,100 行 = 50KB
// 网络传输时间减少 90%!
📌 批量操作优化

适用场景:批量插入、更新、删除操作

go 复制代码
// ❌ 逐条插入:慢如蜗牛
func BatchInsertBad(db *gorm.DB, orders []Order) error {
    for _, order := range orders {
        if err := db.Create(&order).Error; err != nil {
            return err
        }
    }
    return nil
}
// 1000 条数据需要 1000 次网络往返,耗时约 10 秒

// ✅ 批量插入:快如闪电
func BatchInsertGood(db *gorm.DB, orders []Order) error {
    // 每批 100 条
    return db.CreateInBatches(orders, 100).Error
}
// 1000 条数据只需 10 次网络往返,耗时约 200ms

// ✅ 使用事务批量插入(更快)
func BatchInsertBest(db *gorm.DB, orders []Order) error {
    return db.Transaction(func(tx *gorm.DB) error {
        return tx.CreateInBatches(orders, 100).Error
    })
}

// 性能对比:
// ❌ 逐条插入:  10,000 ms
// ✅ 批量插入:     200 ms  (50倍提升)
// ✅ 事务批量:     150 ms  (66倍提升)

1.2 索引优化的艺术

"索引就像书的目录,没有它你只能一页页翻"

📌 索引的基本原理
go 复制代码
// 索引前后的性能对比示例

// 假设 users 表有 100 万条数据

// ❌ 没有索引:全表扫描
// SELECT * FROM users WHERE email = 'test@example.com'
// 扫描行数:1,000,000 行
// 查询时间:5000ms

// ✅ 添加索引后
// CREATE INDEX idx_email ON users(email)
// 扫描行数:1 行
// 查询时间:5ms
📌 索引类型选择
go 复制代码
// 1. 单列索引:最常见
type User struct {
    ID    int64  `gorm:"primaryKey"`
    Email string `gorm:"index"` // 单列索引
    Name  string
}

// 2. 复合索引:多列组合
type Order struct {
    ID        int64 `gorm:"primaryKey"`
    UserID    int64 `gorm:"index:idx_user_status"`       // 复合索引第一列
    Status    int   `gorm:"index:idx_user_status"`       // 复合索引第二列
    CreatedAt time.Time `gorm:"index:idx_user_status"`  // 复合索引第三列
}

// 复合索引的左前缀原则:
// ✅ 可以使用索引:WHERE user_id = 1
// ✅ 可以使用索引:WHERE user_id = 1 AND status = 2
// ✅ 可以使用索引:WHERE user_id = 1 AND status = 2 AND created_at > '2024-01-01'
// ❌ 不能使用索引:WHERE status = 2 (没有最左列)
// ❌ 不能使用索引:WHERE created_at > '2024-01-01' (跳过了前面的列)

// 3. 唯一索引:保证唯一性
type User struct {
    ID    int64  `gorm:"primaryKey"`
    Email string `gorm:"uniqueIndex"` // 唯一索引
}

// 4. 覆盖索引:索引包含所有需要的字段
type User struct {
    ID    int64  `gorm:"primaryKey"`
    Email string `gorm:"index:idx_email_name"` // 复合索引
    Name  string `gorm:"index:idx_email_name"` // 同一个索引
}

// 查询时只使用索引列,不需要回表
// SELECT email, name FROM users WHERE email = 'test@example.com'
// 直接从索引获取数据,更快!
📌 索引失效的常见场景
go 复制代码
// ❌ 场景1:在索引列上使用函数
db.Where("DATE(created_at) = ?", "2024-01-01").Find(&orders)
// 索引失效,全表扫描

// ✅ 改为范围查询
startDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
endDate := startDate.AddDate(0, 0, 1)
db.Where("created_at >= ? AND created_at < ?", startDate, endDate).Find(&orders)

// ❌ 场景2:隐式类型转换
// id 是 int64 类型,但传入字符串
db.Where("id = ?", "12345").Find(&user) // 索引可能失效

// ✅ 使用正确的类型
db.Where("id = ?", int64(12345)).Find(&user)

// ❌ 场景3:前缀模糊查询
db.Where("name LIKE ?", "%张三%").Find(&users) // 索引失效

// ✅ 只用后缀模糊查询(如果业务允许)
db.Where("name LIKE ?", "张三%").Find(&users)

// ✅ 或使用全文索引
// ALTER TABLE users ADD FULLTEXT INDEX idx_name_fulltext(name);
db.Where("MATCH(name) AGAINST(?)", "张三").Find(&users)

// ❌ 场景4:使用 OR 连接不同列
db.Where("email = ? OR phone = ?", email, phone).Find(&users) // 索引失效

// ✅ 改用 UNION
var users []User
db.Where("email = ?", email).Find(&users)
var phoneUsers []User
db.Where("phone = ?", phone).Find(&phoneUsers)
users = append(users, phoneUsers...)

// ❌ 场景5:不等于操作
db.Where("status != ?", 1).Find(&orders) // 可能不走索引

// ✅ 使用 IN 指定具体值
db.Where("status IN ?", []int{0, 2, 3, 4}).Find(&orders)
📌 索引优化实战案例
go 复制代码
// 案例:电商订单查询优化

// 需求:查询某用户最近30天的已完成订单,按时间倒序

// ❌ 糟糕的索引设计
type Order struct {
    ID        int64
    UserID    int64     `gorm:"index"`        // 单独的索引
    Status    int       `gorm:"index"`        // 单独的索引
    CreatedAt time.Time `gorm:"index"`        // 单独的索引
    Amount    float64
}
// 查询会使用其中一个索引,但不是最优的

// ✅ 优秀的索引设计
type Order struct {
    ID        int64
    UserID    int64     `gorm:"index:idx_user_time_status"`
    CreatedAt time.Time `gorm:"index:idx_user_time_status"`
    Status    int       `gorm:"index:idx_user_time_status"`
    Amount    float64
}
// 复合索引完美匹配查询条件

func GetUserRecentOrders(db *gorm.DB, userID int64) []Order {
    var orders []Order
    thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
    
    db.Where("user_id = ? AND created_at > ? AND status = ?", 
        userID, thirtyDaysAgo, StatusCompleted).
        Order("created_at DESC").
        Find(&orders)
    
    return orders
}

// 性能提升:
// ❌ 单独索引:扫描 50,000 行,耗时 500ms
// ✅ 复合索引:扫描 50 行,耗时 5ms
// 提升 100 倍!

1.3 查询优化技巧

📌 分页优化:深度分页的噩梦
go 复制代码
// ❌ 传统分页:OFFSET 越大越慢
func GetUsersPaginationBad(db *gorm.DB, page, pageSize int) []User {
    var users []User
    offset := (page - 1) * pageSize
    
    db.Offset(offset).Limit(pageSize).Find(&users)
    // SELECT * FROM users LIMIT 20 OFFSET 100000
    // 需要先扫描前 100,000 条,然后丢弃,再取 20 条
    // 当 OFFSET 很大时,性能急剧下降
    
    return users
}

// 第 1 页:耗时 10ms
// 第 100 页:耗时 50ms
// 第 5000 页:耗时 2000ms 😱

// ✅ 游标分页:性能稳定
func GetUsersPaginationGood(db *gorm.DB, lastID int64, pageSize int) []User {
    var users []User
    
    query := db.Limit(pageSize).Order("id ASC")
    if lastID > 0 {
        query = query.Where("id > ?", lastID)
    }
    query.Find(&users)
    
    // SELECT * FROM users WHERE id > 100000 LIMIT 20
    // 直接从指定位置开始,性能稳定
    
    return users
}

// 所有页面:耗时都在 10ms 左右 ✨

// ✅ 游标分页的完整实现
type PageResult struct {
    Data    []User `json:"data"`
    LastID  int64  `json:"last_id"`  // 下一页的游标
    HasMore bool   `json:"has_more"` // 是否还有更多数据
}

func GetUsersWithCursor(db *gorm.DB, lastID int64, pageSize int) PageResult {
    var users []User
    
    // 多查一条判断是否还有数据
    query := db.Limit(pageSize + 1).Order("id ASC")
    if lastID > 0 {
        query = query.Where("id > ?", lastID)
    }
    query.Find(&users)
    
    hasMore := len(users) > pageSize
    if hasMore {
        users = users[:pageSize] // 去掉多查的那一条
    }
    
    var newLastID int64
    if len(users) > 0 {
        newLastID = users[len(users)-1].ID
    }
    
    return PageResult{
        Data:    users,
        LastID:  newLastID,
        HasMore: hasMore,
    }
}

// 前端调用示例:
// 第一页:GET /users?page_size=20
// 第二页:GET /users?last_id=123&page_size=20
// 第三页:GET /users?last_id=456&page_size=20
📌 子查询优化
go 复制代码
// ❌ 使用子查询(可能很慢)
func GetActiveUsersWithOrdersBad(db *gorm.DB) []User {
    var users []User
    db.Raw(`
        SELECT * FROM users 
        WHERE id IN (
            SELECT DISTINCT user_id FROM orders 
            WHERE created_at > ?
        )
    `, time.Now().AddDate(0, -1, 0)).Scan(&users)
    
    return users
}

// ✅ 使用 JOIN(通常更快)
func GetActiveUsersWithOrdersGood(db *gorm.DB) []User {
    var users []User
    db.Table("users u").
        Select("DISTINCT u.*").
        Joins("INNER JOIN orders o ON u.id = o.user_id").
        Where("o.created_at > ?", time.Now().AddDate(0, -1, 0)).
        Scan(&users)
    
    return users
}

// 性能对比:
// ❌ 子查询:可能执行两次全表扫描
// ✅ JOIN:只需一次扫描 + 索引查找
📌 COUNT 查询优化
go 复制代码
// ❌ SELECT COUNT(*) 在大表上很慢
func GetTotalUserCountBad(db *gorm.DB) int64 {
    var count int64
    db.Model(&User{}).Count(&count)
    // 在千万级别的表上可能需要几秒钟
    return count
}

// ✅ 方案1:使用缓存
func GetTotalUserCountGood(db *gorm.DB, cache *redis.Client) int64 {
    cacheKey := "user:total_count"
    
    // 先从缓存获取
    if val, err := cache.Get(context.Background(), cacheKey).Int64(); err == nil {
        return val
    }
    
    // 缓存未命中,查询数据库
    var count int64
    db.Model(&User{}).Count(&count)
    
    // 写入缓存,1小时过期
    cache.Set(context.Background(), cacheKey, count, time.Hour)
    
    return count
}

// ✅ 方案2:使用近似值(适用于不需要精确计数的场景)
func GetApproximateCount(db *gorm.DB) int64 {
    var result struct {
        Count int64
    }
    db.Raw(`
        SELECT table_rows as count 
        FROM information_schema.tables 
        WHERE table_schema = DATABASE() 
        AND table_name = 'users'
    `).Scan(&result)
    
    return result.Count
}
// 几乎瞬间返回,但是近似值

// ✅ 方案3:异步更新计数
// 使用单独的计数表,通过触发器或异步任务更新
type Statistics struct {
    ID         int64
    TotalUsers int64
    UpdatedAt  time.Time
}

func GetCachedCount(db *gorm.DB) int64 {
    var stats Statistics
    db.First(&stats)
    return stats.TotalUsers
}
// 查询极快,但需要维护计数表

1.4 连接池优化

go 复制代码
// 📌 连接池配置优化

func InitDBWithPool() *gorm.DB {
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    sqlDB, err := db.DB()
    if err != nil {
        panic(err)
    }
    
    // ✅ 连接池配置
    // 最大空闲连接数
    // 原则:根据并发请求数设置,一般为最大连接数的 1/2
    sqlDB.SetMaxIdleConns(50)
    
    // 最大打开连接数
    // 原则:不要超过数据库的最大连接数限制
    // MySQL 默认:151,生产环境通常设置为 1000+
    sqlDB.SetMaxOpenConns(100)
    
    // 连接最大存活时间
    // 原则:小于数据库的 wait_timeout(MySQL 默认 8 小时)
    sqlDB.SetConnMaxLifetime(time.Hour)
    
    // 连接最大空闲时间
    // 原则:根据请求频率设置,低频可以设短一些
    sqlDB.SetConnMaxIdleTime(10 * time.Minute)
    
    return db
}

// 连接池大小计算公式:
// 最大连接数 = ((核心数 * 2) + 有效磁盘数)
// 例如:4核CPU + 1个磁盘 = (4 * 2) + 1 = 9个连接

// ⚠️ 常见问题:
// 1. 连接数过小:高并发时连接不够,请求等待
// 2. 连接数过大:消耗数据库资源,增加调度开销
// 3. 没有设置超时:连接泄露导致连接耗尽

1.5 读写分离与分库分表

📌 读写分离实现
go 复制代码
// ✅ 使用 GORM 实现读写分离

import (
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/plugin/dbresolver"
)

func InitDBWithReadWrite() *gorm.DB {
    // 主库(写)
    db, err := gorm.Open(mysql.Open(masterDSN), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    // 配置读写分离
    db.Use(dbresolver.Register(dbresolver.Config{
        // 从库(读)
        Replicas: []gorm.Dialector{
            mysql.Open(slave1DSN),
            mysql.Open(slave2DSN),
        },
        // 负载均衡策略
        Policy: dbresolver.RandomPolicy{},
    }))
    
    return db
}

// 使用示例:
func Example(db *gorm.DB) {
    // 读操作自动路由到从库
    var users []User
    db.Find(&users) // 从库
    
    // 写操作自动路由到主库
    db.Create(&User{Name: "test"}) // 主库
    
    // 强制使用主库
    db.Clauses(dbresolver.Write).Find(&users) // 主库
}

// 📊 读写分离的性能提升:
// - 读请求分散到多个从库,QPS 可提升 3-5 倍
// - 主库专注处理写请求,写入性能更稳定
// - 适用场景:读多写少的业务(读写比 > 3:1)
📌 分库分表策略
go 复制代码
// 场景:订单表数据量过大,需要分表

// ✅ 方案1:按时间分表(适合时序数据)
func GetOrderTableByTime(orderTime time.Time) string {
    // 按月分表:orders_202401, orders_202402...
    return fmt.Sprintf("orders_%s", orderTime.Format("200601"))
}

func CreateOrder(db *gorm.DB, order Order) error {
    tableName := GetOrderTableByTime(order.CreatedAt)
    return db.Table(tableName).Create(&order).Error
}

// ✅ 方案2:按用户ID分表(适合用户数据)
func GetOrderTableByUserID(userID int64) string {
    // 按用户ID取模:orders_0, orders_1, ..., orders_9
    tableIndex := userID % 10
    return fmt.Sprintf("orders_%d", tableIndex)
}

func GetUserOrders(db *gorm.DB, userID int64) []Order {
    var orders []Order
    tableName := GetOrderTableByUserID(userID)
    db.Table(tableName).Where("user_id = ?", userID).Find(&orders)
    return orders
}

// ✅ 方案3:使用 ShardingSphere 等中间件
// ShardingSphere 可以自动处理分片路由,对业务代码透明

// 分表建议:
// - 单表数据量 < 500万 时无需分表
// - 单表数据量 500万-2000万 时考虑分表
// - 单表数据量 > 2000万 时强烈建议分表

相关推荐
历程里程碑2 小时前
LeetCode 283:原地移动零的优雅解法
java·c语言·开发语言·数据结构·c++·算法·leetcode
渡我白衣2 小时前
Python 与数据科学工具链入门:NumPy、Pandas、Matplotlib 快速上手
人工智能·python·机器学习·自然语言处理·numpy·pandas·matplotlib
虾说羊2 小时前
java中的反射详解
java·开发语言
AI360labs_atyun2 小时前
OpenAI应用商店,试试用它写年终PPT!
人工智能·科技·学习·ai·chatgpt·powerpoint
杨二K2 小时前
Milvus性能权衡
数据库·人工智能·milvus
love530love2 小时前
【笔记】把已有的 ComfyUI 插件发布到 Comfy Registry(官方节点商店)全流程实录
人工智能·windows·笔记·python·aigc·comfyui·torchmonitor
特立独行的猫a2 小时前
C++23 std::expected 详解:告别传统错误码和异常,构建现代健壮代码
开发语言·c++23·expected·错误码处理
一往无前fgs2 小时前
【问题记录】在openEuler 24 系统使用宝塔面板安装Mysql数据库启动失败问题
数据库·mysql
廋到被风吹走2 小时前
【Spring】HandlerInterceptor解析
java·后端·spring