Golang操作Redis与MySQL实战指南

1. 为什么选择Golang操作Redis和MySQL

在当今的互联网应用开发中,数据存储和缓存是两大核心需求。Golang凭借其出色的并发性能和简洁的语法,成为构建高性能后端服务的首选语言之一。而Redis作为内存数据库,MySQL作为关系型数据库,在实际项目中经常需要配合使用。

我最近在一个电商促销系统项目中,就遇到了需要同时操作Redis和MySQL的场景。系统需要在秒杀活动期间处理高并发请求,同时保证数据一致性。通过Golang的 database/sql 包和 redigo 库,我们实现了稳定可靠的数据库操作层。

2. 环境准备与依赖安装

2.1 安装Golang开发环境

首先确保你的系统已经安装了Golang 1.16或更高版本。可以通过以下命令检查:

go version

如果未安装,可以从 Golang官网 下载对应平台的安装包。安装完成后,设置GOPATH环境变量:

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

2.2 安装MySQL和Redis

对于MySQL,推荐使用官方提供的社区版。在Ubuntu系统上可以这样安装:

sudo apt update
sudo apt install mysql-server
sudo mysql_secure_installation

Redis的安装同样简单:

sudo apt install redis-server
sudo systemctl enable redis-server

2.3 安装必要的Golang库

我们需要以下几个核心库来实现Redis和MySQL操作:

go get github.com/go-sql-driver/mysql
go get github.com/gomodule/redigo/redis

这些库都是经过社区验证的成熟解决方案。 go-sql-driver/mysql 是MySQL官方推荐的Golang驱动,而 redigo 则是Redis的轻量级客户端。

3. MySQL数据库操作实战

3.1 初始化数据库连接

在Golang中连接MySQL数据库,我们需要先创建一个连接池:

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

func initDB() (*sql.DB, error) {
    db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/dbname?parseTime=true")
    if err != nil {
        return nil, err
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    return db, nil
}

这里有几个关键点需要注意:

  1. 连接字符串格式为 username:password@protocol(address)/dbname?param=value
  2. parseTime=true 参数确保能正确处理MySQL的日期时间类型
  3. 连接池参数需要根据实际负载调整

3.2 执行CRUD操作

让我们看一个完整的用户管理示例:

type User struct {
    ID        int64
    Username  string
    Email     string
    CreatedAt time.Time
}

// 创建用户
func createUser(db *sql.DB, user *User) error {
    result, err := db.Exec(
        "INSERT INTO users (username, email) VALUES (?, ?)",
        user.Username, user.Email,
    )
    if err != nil {
        return err
    }
    
    user.ID, err = result.LastInsertId()
    return err
}

// 查询用户
func getUser(db *sql.DB, id int64) (*User, error) {
    user := &User{}
    err := db.QueryRow(
        "SELECT id, username, email, created_at FROM users WHERE id = ?", id,
    ).Scan(&user.ID, &user.Username, &user.Email, &user.CreatedAt)
    
    if err != nil {
        return nil, err
    }
    return user, nil
}

在实际项目中,我建议使用 sqlx 库来简化结构体映射:

go get github.com/jmoiron/sqlx

使用sqlx后,查询可以简化为:

func getUsers(db *sqlx.DB) ([]User, error) {
    var users []User
    err := db.Select(&users, "SELECT * FROM users")
    return users, err
}

4. Redis操作实战

4.1 初始化Redis连接池

与MySQL类似,Redis也需要连接池管理:

import "github.com/gomodule/redigo/redis"

func initRedisPool() *redis.Pool {
    return &redis.Pool{
        MaxIdle:     10,
        MaxActive:   100,
        IdleTimeout: 240 * time.Second,
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "localhost:6379")
            if err != nil {
                return nil, err
            }
            return c, nil
        },
    }
}

4.2 常用Redis操作示例

Redis支持多种数据结构,以下是几种常见操作的示例:

// 字符串操作
func setValue(pool *redis.Pool, key, value string) error {
    conn := pool.Get()
    defer conn.Close()
    
    _, err := conn.Do("SET", key, value)
    return err
}

// 哈希表操作
func setHash(pool *redis.Pool, key, field, value string) error {
    conn := pool.Get()
    defer conn.Close()
    
    _, err := conn.Do("HSET", key, field, value)
    return err
}

// 设置过期时间
func setWithExpire(pool *redis.Pool, key, value string, expireSec int) error {
    conn := pool.Get()
    defer conn.Close()
    
    _, err := conn.Do("SETEX", key, expireSec, value)
    return err
}

4.3 使用Pipeline提升性能

当需要执行多个Redis命令时,使用Pipeline可以显著减少网络往返时间:

func batchIncr(pool *redis.Pool, keys []string) error {
    conn := pool.Get()
    defer conn.Close()
    
    for _, key := range keys {
        conn.Send("INCR", key)
    }
    return conn.Flush()
}

5. 实战中的经验与陷阱

5.1 连接泄露问题

无论是MySQL还是Redis,连接泄露都是常见问题。务必记得在使用后关闭连接:

rows, err := db.Query("SELECT * FROM users")
if err != nil {
    return err
}
defer rows.Close() // 重要!

对于Redis连接,同样需要defer Close():

conn := pool.Get()
defer conn.Close()

5.2 事务处理

在需要保证数据一致性的场景,正确使用事务至关重要:

// MySQL事务示例
func transferMoney(db *sql.DB, from, to int64, amount float64) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    
    defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()
    
    // 扣减转出账户余额
    if _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from); err != nil {
        return err
    }
    
    // 增加转入账户余额
    if _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to); err != nil {
        return err
    }
    
    return tx.Commit()
}

Redis事务使用MULTI/EXEC:

func redisTransaction(pool *redis.Pool) error {
    conn := pool.Get()
    defer conn.Close()
    
    conn.Send("MULTI")
    conn.Send("INCR", "counter1")
    conn.Send("INCR", "counter2")
    _, err := conn.Do("EXEC")
    return err
}

5.3 连接池调优

连接池参数需要根据实际负载调整。过小的连接池会导致请求排队,过大的连接池会浪费资源。以下是一些经验值:

  • MySQL连接池:

    • MaxOpenConns: CPU核心数的2-3倍
    • MaxIdleConns: 与MaxOpenConns相同
    • ConnMaxLifetime: 5-30分钟
  • Redis连接池:

    • MaxActive: 根据并发请求量调整,通常100-500
    • MaxIdle: 保持20-50个空闲连接

6. 性能优化技巧

6.1 批量操作优化

对于大量数据操作,批量处理可以显著提升性能:

// MySQL批量插入
func batchInsertUsers(db *sql.DB, users []User) error {
    valueStrings := make([]string, 0, len(users))
    valueArgs := make([]interface{}, 0, len(users)*2)
    
    for _, user := range users {
        valueStrings = append(valueStrings, "(?, ?)")
        valueArgs = append(valueArgs, user.Username)
        valueArgs = append(valueArgs, user.Email)
    }
    
    stmt := fmt.Sprintf("INSERT INTO users (username, email) VALUES %s",
        strings.Join(valueStrings, ","))
    
    _, err := db.Exec(stmt, valueArgs...)
    return err
}

6.2 使用连接复用

对于高频调用的服务,可以考虑复用预处理语句:

var (
    insertStmt *sql.Stmt
)

func init() {
    var err error
    insertStmt, err = db.Prepare("INSERT INTO users (username, email) VALUES (?, ?)")
    if err != nil {
        log.Fatal(err)
    }
}

func createUser(user *User) error {
    result, err := insertStmt.Exec(user.Username, user.Email)
    if err != nil {
        return err
    }
    
    user.ID, err = result.LastInsertId()
    return err
}

6.3 Redis Lua脚本

对于复杂操作,使用Lua脚本可以减少网络往返并保证原子性:

const script = `
local current = redis.call('GET', KEYS[1])
if current then
    return redis.call('INCRBY', KEYS[1], ARGV[1])
else
    return nil
end`

func incrementIfExists(pool *redis.Pool, key string, increment int) (interface{}, error) {
    conn := pool.Get()
    defer conn.Close()
    
    script := redis.NewScript(1, script)
    return script.Do(conn, key, increment)
}

7. 实际项目中的架构设计

在一个真实的微服务架构中,我通常会这样组织数据库访问层:

project/
├── internal/
│   ├── config/       # 配置管理
│   ├── model/        # 数据模型
│   ├── repository/   # 数据访问层
│   │   ├── mysql/    # MySQL实现
│   │   ├── redis/    # Redis实现
│   │   └── interface.go # 接口定义
│   └── service/      # 业务逻辑层

这种分层架构的好处是:

  1. 业务逻辑与数据访问解耦
  2. 可以轻松切换数据库实现
  3. 便于单元测试

例如,定义一个通用的缓存接口:

type CacheRepository interface {
    Get(key string) (string, error)
    Set(key, value string, expire time.Duration) error
    Delete(key string) error
    // 其他方法...
}

然后分别实现Redis版本和Mock版本用于测试。

8. 测试策略

8.1 单元测试

对于数据库相关代码,可以使用 sqlmock 进行测试:

go get github.com/DATA-DOG/go-sqlmock

测试示例:

func TestGetUser(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("an error '%s' was not expected", err)
    }
    defer db.Close()
    
    rows := sqlmock.NewRows([]string{"id", "username", "email", "created_at"}).
        AddRow(1, "testuser", "test@example.com", time.Now())
    
    mock.ExpectQuery("^SELECT (.+) FROM users WHERE id = ?$").
        WithArgs(1).
        WillReturnRows(rows)
    
    user, err := getUser(db, 1)
    if err != nil {
        t.Errorf("error was not expected: %s", err)
    }
    
    if user.Username != "testuser" {
        t.Errorf("expected username testuser, got %s", user.Username)
    }
}

8.2 集成测试

对于需要真实数据库的测试,可以使用Docker启动临时实例:

func TestMySQLIntegration(t *testing.T) {
    pool, err := dockertest.NewPool("")
    if err != nil {
        t.Fatalf("Could not connect to docker: %s", err)
    }
    
    resource, err := pool.Run("mysql", "5.7", []string{"MYSQL_ROOT_PASSWORD=secret"})
    if err != nil {
        t.Fatalf("Could not start resource: %s", err)
    }
    
    defer func() {
        if err := pool.Purge(resource); err != nil {
            t.Fatalf("Could not purge resource: %s", err)
        }
    }()
    
    // 等待数据库就绪
    if err := pool.Retry(func() error {
        db, err := sql.Open("mysql", 
            fmt.Sprintf("root:secret@(localhost:%s)/mysql", resource.GetPort("3306/tcp")))
        if err != nil {
            return err
        }
        return db.Ping()
    }); err != nil {
        t.Fatalf("Could not connect to docker: %s", err)
    }
    
    // 执行测试...
}

9. 监控与维护

9.1 连接池监控

定期监控连接池状态可以提前发现问题:

func printDBStats(db *sql.DB) {
    stats := db.Stats()
    log.Printf("DB Stats: InUse=%d Idle=%d WaitCount=%d WaitDuration=%s",
        stats.InUse, stats.Idle, stats.WaitCount, stats.WaitDuration)
}

// 定时执行
go func() {
    for range time.Tick(5 * time.Minute) {
        printDBStats(db)
    }
}()

9.2 Redis慢查询监控

Redis的慢查询日志可以帮助发现性能问题:

func getSlowLogs(pool *redis.Pool) ([]interface{}, error) {
    conn := pool.Get()
    defer conn.Close()
    
    return redis.Values(conn.Do("SLOWLOG", "GET", 10))
}

9.3 错误处理策略

对于数据库错误,应该区分临时性错误和永久性错误:

func isRetryableError(err error) bool {
    if err == nil {
        return false
    }
    
    // MySQL错误码参考 https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
    if mysqlErr, ok := err.(*mysql.MySQLError); ok {
        switch mysqlErr.Number {
        case 1213: // Deadlock found
        case 1205: // Lock wait timeout
            return true
        default:
            return false
        }
    }
    
    // 网络错误等通常可以重试
    return true
}

10. 高级应用场景

10.1 分布式锁实现

使用Redis实现分布式锁:

func acquireLock(pool *redis.Pool, lockKey string, timeout time.Duration) (string, error) {
    conn := pool.Get()
    defer conn.Close()
    
    token := uuid.New().String()
    end := time.Now().Add(timeout)
    
    for {
        result, err := redis.String(conn.Do("SET", lockKey, token, "NX", "PX", int(timeout/time.Millisecond)))
        if err == nil && result == "OK" {
            return token, nil
        }
        
        if time.Now().After(end) {
            return "", fmt.Errorf("acquire lock timeout")
        }
        
        time.Sleep(10 * time.Millisecond)
    }
}

func releaseLock(pool *redis.Pool, lockKey, token string) error {
    conn := pool.Get()
    defer conn.Close()
    
    script := `
    if redis.call("GET", KEYS[1]) == ARGV[1] then
        return redis.call("DEL", KEYS[1])
    else
        return 0
    end`
    
    _, err := redis.NewScript(1, script).Do(conn, lockKey, token)
    return err
}

10.2 缓存一致性策略

常见的缓存模式有:

  1. Cache Aside:先更新数据库,再删除缓存
  2. Read/Write Through:缓存层负责读写数据库
  3. Write Behind:先更新缓存,异步批量写入数据库

以下是Cache Aside模式的实现:

func GetUserWithCache(userID int64) (*User, error) {
    // 先查缓存
    user, err := getUserFromCache(userID)
    if err == nil && user != nil {
        return user, nil
    }
    
    // 缓存未命中,查数据库
    user, err = getUserFromDB(userID)
    if err != nil {
        return nil, err
    }
    
    // 回填缓存
    if err := setUserToCache(user); err != nil {
        log.Printf("failed to set cache: %v", err)
    }
    
    return user, nil
}

func UpdateUser(user *User) error {
    // 先更新数据库
    if err := updateUserInDB(user); err != nil {
        return err
    }
    
    // 删除缓存
    if err := deleteUserFromCache(user.ID); err != nil {
        log.Printf("failed to delete cache: %v", err)
    }
    
    return nil
}

10.3 分片策略

对于大规模数据,需要考虑分片策略:

// 基于用户ID的分片示例
func getShardDB(userID int64) (*sql.DB, error) {
    shardNum := userID % 4  // 假设有4个分片
    shardConfig := config.GetShardConfig(shardNum)
    
    db, err := sql.Open("mysql", 
        fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", 
            shardConfig.User, 
            shardConfig.Password,
            shardConfig.Host,
            shardConfig.Port,
            shardConfig.DBName))
    
    if err != nil {
        return nil, err
    }
    
    return db, nil
}

11. 项目实战:用户会话管理系统

让我们通过一个完整的示例来整合所学内容。这个系统需要:

  1. 在MySQL中存储用户基本信息
  2. 使用Redis管理用户会话
  3. 实现注册、登录、会话验证功能

11.1 数据库表设计

MySQL用户表:

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) NOT NULL UNIQUE,
    password_hash VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

11.2 核心代码实现

用户服务层:

type UserService struct {
    userRepo    repository.UserRepository
    sessionRepo repository.SessionRepository
}

func (s *UserService) Register(username, email, password string) (*model.User, error) {
    // 密码哈希
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return nil, fmt.Errorf("failed to hash password: %w", err)
    }
    
    user := &model.User{
        Username:     username,
        Email:        email,
        PasswordHash: string(hashedPassword),
    }
    
    if err := s.userRepo.Create(user); err != nil {
        return nil, fmt.Errorf("failed to create user: %w", err)
    }
    
    return user, nil
}

func (s *UserService) Login(username, password string) (string, error) {
    user, err := s.userRepo.FindByUsername(username)
    if err != nil {
        return "", fmt.Errorf("user not found: %w", err)
    }
    
    if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
        return "", fmt.Errorf("invalid password: %w", err)
    }
    
    // 创建会话
    sessionToken := uuid.New().String()
    if err := s.sessionRepo.Set(sessionToken, user.ID, 24*time.Hour); err != nil {
        return "", fmt.Errorf("failed to create session: %w", err)
    }
    
    return sessionToken, nil
}

func (s *UserService) ValidateSession(token string) (*model.User, error) {
    userID, err := s.sessionRepo.Get(token)
    if err != nil {
        return nil, fmt.Errorf("invalid session: %w", err)
    }
    
    user, err := s.userRepo.FindByID(userID)
    if err != nil {
        return nil, fmt.Errorf("user not found: %w", err)
    }
    
    return user, nil
}

11.3 性能优化

对于高频访问的会话验证,可以使用本地缓存+Redis的多级缓存:

type cachedSessionRepo struct {
    redisRepo   repository.SessionRepository
    localCache  *lru.Cache
    cacheTTL    time.Duration
}

func (r *cachedSessionRepo) Get(token string) (int64, error) {
    // 先查本地缓存
    if userID, ok := r.localCache.Get(token); ok {
        return userID.(int64), nil
    }
    
    // 本地缓存未命中,查Redis
    userID, err := r.redisRepo.Get(token)
    if err != nil {
        return 0, err
    }
    
    // 回填本地缓存
    r.localCache.Add(token, userID)
    
    return userID, nil
}

12. 部署与扩展

12.1 配置管理

使用Viper管理配置:

go get github.com/spf13/viper

配置文件示例(config.yaml):

mysql:
  host: "localhost"
  port: 3306
  username: "appuser"
  password: "secret"
  database: "appdb"
  
redis:
  host: "localhost"
  port: 6379
  pool_size: 100

加载配置:

func initConfig() error {
    viper.SetConfigName("config")
    viper.AddConfigPath(".")
    viper.SetConfigType("yaml")
    
    if err := viper.ReadInConfig(); err != nil {
        return fmt.Errorf("failed to read config: %w", err)
    }
    
    return nil
}

12.2 连接故障处理

实现自动重连机制:

type resilientDB struct {
    db          *sql.DB
    dsn         string
    maxRetries  int
    retryDelay  time.Duration
}

func (r *resilientDB) Exec(query string, args ...interface{}) (sql.Result, error) {
    var result sql.Result
    var err error
    
    for i := 0; i < r.maxRetries; i++ {
        result, err = r.db.Exec(query, args...)
        if err == nil {
            return result, nil
        }
        
        if !isRetryableError(err) {
            return nil, err
        }
        
        time.Sleep(r.retryDelay)
        
        // 尝试重新连接
        if err := r.reconnect(); err != nil {
            log.Printf("reconnect failed: %v", err)
            continue
        }
    }
    
    return nil, fmt.Errorf("max retries exceeded: %w", err)
}

func (r *resilientDB) reconnect() error {
    if err := r.db.Close(); err != nil {
        log.Printf("failed to close db: %v", err)
    }
    
    db, err := sql.Open("mysql", r.dsn)
    if err != nil {
        return err
    }
    
    r.db = db
    return nil
}

12.3 读写分离

对于高负载系统,可以考虑实现读写分离:

type readWriteDB struct {
    writer *sql.DB
    reader *sql.DB
}

func (db *readWriteDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
    return db.reader.Query(query, args...)
}

func (db *readWriteDB) Exec(query string, args ...interface{}) (sql.Result, error) {
    return db.writer.Exec(query, args...)
}

13. 安全最佳实践

13.1 SQL注入防护

始终使用参数化查询:

// 错误示范 - 容易导致SQL注入
db.Query("SELECT * FROM users WHERE username = '" + username + "'")

// 正确做法
db.Query("SELECT * FROM users WHERE username = ?", username)

13.2 Redis安全配置

确保Redis配置了密码保护:

func initRedisPoolWithAuth() *redis.Pool {
    return &redis.Pool{
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", "localhost:6379",
                redis.DialPassword("yourpassword"))
            if err != nil {
                return nil, err
            }
            return c, nil
        },
    }
}

13.3 敏感数据处理

密码等敏感信息应该:

  1. 使用bcrypt等算法哈希存储
  2. 不在日志中记录
  3. 使用环境变量而非代码硬编码
func hashPassword(password string) (string, error) {
    hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    if err != nil {
        return "", err
    }
    return string(hash), nil
}

func verifyPassword(hashedPassword, password string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
    return err == nil
}

14. 常见问题排查

14.1 MySQL连接超时

错误现象: driver: bad connection connection timed out

解决方案:

  1. 检查连接池设置是否合理
  2. 增加 wait_timeout interactive_timeout 参数
  3. 实现连接健康检查:
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)

14.2 Redis内存不足

错误现象: OOM command not allowed when used memory > 'maxmemory'

解决方案:

  1. 增加Redis内存限制
  2. 设置合理的淘汰策略:
    _, err := conn.Do("CONFIG", "SET", "maxmemory-policy", "allkeys-lru")
    
  3. 对大Key进行拆分

14.3 慢查询优化

对于MySQL慢查询:

  1. 使用 EXPLAIN 分析查询计划
  2. 添加适当的索引
  3. 优化复杂查询

对于Redis慢查询:

  1. 使用 SLOWLOG 命令查看慢查询
  2. 避免使用 KEYS 等阻塞命令
  3. 对大Value进行拆分

15. 未来扩展方向

15.1 使用ORM框架

对于复杂项目,可以考虑使用GORM等ORM框架:

go get gorm.io/gorm
go get gorm.io/driver/mysql

示例:

type User struct {
    gorm.Model
    Username string `gorm:"uniqueIndex"`
    Email    string `gorm:"uniqueIndex"`
}

func main() {
    db, err := gorm.Open(mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?parseTime=true"), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    db.AutoMigrate(&User{})
    
    user := User{Username: "test", Email: "test@example.com"}
    db.Create(&user)
}

15.2 引入分布式事务

对于跨数据库操作,可以考虑使用Saga模式或分布式事务框架:

// Saga模式示例
func placeOrder() error {
    // 开始Saga
    saga := saga.NewSaga()
    
    // 添加补偿操作
    saga.AddStep(
        func() error {
            return deductInventory()
        },
        func() error {
            return restoreInventory()
        })
    
    saga.AddStep(
        func() error {
            return createOrder()
        },
        func() error {
            return cancelOrder()
        })
    
    return saga.Run()
}

15.3 使用Redis模块

Redis支持通过模块扩展功能,例如:

  • RediSearch:全文搜索
  • RedisJSON:JSON文档存储
  • RedisGraph:图数据库
// 使用RedisJSON示例
_, err := conn.Do("JSON.SET", "user:1", ".", `{"name":"Alice","age":30}`)
if err != nil {
    return err
}

reply, err := redis.String(conn.Do("JSON.GET", "user:1", ".name"))
if err != nil {
    return err
}
fmt.Println(reply) // "Alice"

16. 个人经验分享

在实际项目中同时使用Redis和MySQL时,我总结了以下几点经验:

  1. 连接管理 :数据库连接是宝贵资源,一定要妥善管理。我曾经遇到过一个连接泄露的Bug,导致服务在高峰期崩溃。现在我会在服务启动时打印连接池统计信息,并定期监控。

  2. 超时设置 :网络操作必须设置合理的超时。我曾经因为没有设置查询超时,导致一个慢查询阻塞了整个服务。现在我会为所有数据库操作设置上下文超时:

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

rows, err := db.QueryContext(ctx, "SELECT * FROM large_table")
  1. 批量操作 :对于大量数据操作,批量处理可以提升几个数量级的性能。我曾经优化过一个用户导入功能,从单条插入改为批量插入后,处理时间从10分钟缩短到10秒。

  2. 缓存策略 :缓存失效是个难题。我现在的原则是:宁可多查一次数据库,也不要提供过期的数据。对于关键数据,会采用更保守的缓存策略。

  3. 测试覆盖 :数据库相关代码一定要有良好的测试覆盖。我会为每个存储库方法编写单元测试,并使用Docker Compose在CI中运行集成测试。

  4. 监控指标 :完善的监控可以快速定位问题。我会收集以下指标:

    • 查询延迟
    • 错误率
    • 连接池使用情况
    • 缓存命中率
  5. 文档注释 :数据库模式变更要有完善的文档记录。我会在迁移文件中添加详细的注释,说明变更原因和影响范围。

  6. 备份策略 :无论数据存储在Redis还是MySQL,都要有可靠的备份方案。我会定期测试备份恢复流程,确保在灾难发生时能快速恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值