数据库连接与配置


第三章:数据库连接与配置

3.1 支持的数据库

GORM 官方支持以下数据库:

数据库 驱动包 DSN 示例
MySQL gorm.io/driver/mysql user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
PostgreSQL gorm.io/driver/postgres host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai
SQLite gorm.io/driver/sqlite test.db:memory:
SQL Server gorm.io/driver/sqlserver sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm
ClickHouse gorm.io/driver/clickhouse tcp://localhost:9000?database=gorm&username=gorm&password=gorm&read_timeout=10&write_timeout=20

3.2 MySQL 连接详解

基础连接

package main

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

func main() {
    dsn := "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("数据库连接失败: " + err.Error())
    }
}

DSN 参数说明

参数 说明 建议值
charset 字符集 utf8mb4(支持 emoji)
parseTime 解析时间类型 True
loc 时区 LocalAsia/Shanghai
timeout 连接超时 10s
readTimeout 读取超时 30s
writeTimeout 写入超时 30s
interpolateParams 参数插值 true(减少预处理)
multiStatements 多语句执行 按需开启

高级 DSN 配置

dsn := "user:password@tcp(127.0.0.1:3306)/dbname?" +
    "charset=utf8mb4&" +
    "parseTime=True&" +
    "loc=Asia%2FShanghai&" +
    "timeout=10s&" +
    "readTimeout=30s&" +
    "writeTimeout=30s&" +
    "maxAllowedPacket=0&" +  // 0 表示使用驱动默认最大值
    "allowNativePasswords=true"

使用现有连接

import (
    "database/sql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

sqlDB, err := sql.Open("mysql", dsn)
// 可以在这里设置 sqlDB 的连接池参数

db, err := gorm.Open(mysql.New(mysql.Config{
    Conn: sqlDB,
}), &gorm.Config{})

3.3 PostgreSQL 连接详解

基础连接

import "gorm.io/driver/postgres"

dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

DSN 参数说明

参数 说明
host 主机地址
port 端口(默认 5432)
user 用户名
password 密码
dbname 数据库名
sslmode SSL 模式:disable, require, verify-ca, verify-full
TimeZone 时区
search_path schema 搜索路径
application_name 应用名称(用于 pg_stat_activity)

使用高级配置

dsn := "postgres://user:password@localhost:5432/dbname?" +
    "sslmode=disable&" +
    "connect_timeout=10&" +
    "application_name=myapp&" +
    "search_path=public,myschema"

db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

3.4 SQLite 连接详解

文件模式

import "gorm.io/driver/sqlite"

// 文件数据库
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

内存模式

// 纯内存数据库(连接关闭后数据丢失)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})

// 命名内存数据库(多个连接可共享)
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})

高级选项

db, err := gorm.Open(sqlite.Open("gorm.db?_fk=1"), &gorm.Config{})
// _fk=1 启用外键约束(SQLite 默认关闭)

3.5 连接池配置

import (
    "time"
    "gorm.io/gorm"
)

func setupConnectionPool(db *gorm.DB) {
    sqlDB, err := db.DB()
    if err != nil {
        panic(err)
    }

    // 设置空闲连接池中的最大连接数
    sqlDB.SetMaxIdleConns(10)

    // 设置打开数据库连接的最大数量
    sqlDB.SetMaxOpenConns(100)

    // 设置连接可复用的最大时间
    sqlDB.SetConnMaxLifetime(time.Hour)

    // 设置连接在池中保持空闲的最大时间(Go 1.15+)
    sqlDB.SetConnMaxIdleTime(30 * time.Minute)
}

连接池参数建议

场景 MaxIdleConns MaxOpenConns ConnMaxLifetime
小型应用 5-10 20-50 1h
中型应用 10-25 50-100 1h
大型应用 25-50 100-200 30m
高并发 50+ 200+ 30m

3.6 日志配置

内置日志级别

import (
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "log"
    "os"
    "time"
)

func main() {
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
        logger.Config{
            SlowThreshold:             time.Second,   // 慢 SQL 阈值
            LogLevel:                  logger.Info,   // 日志级别
            IgnoreRecordNotFoundError: true,          // 忽略 ErrRecordNotFound
            Colorful:                  true,          // 彩色打印
        },
    )

    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
        Logger: newLogger,
    })
}

日志级别

级别 说明
logger.Silent 不打印任何日志
logger.Error 仅打印错误日志
logger.Warn 打印警告和错误
logger.Info 打印所有 SQL(开发推荐)

自定义日志

import (
    "context"
    "time"
    "gorm.io/gorm/logger"
)

type CustomLogger struct {
    logger.Interface
}

func (l *CustomLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
    // 自定义追踪逻辑,如发送到日志收集系统
    sql, rows := fc()
    duration := time.Since(begin)
    
    // 记录到文件或监控
    if duration > 100*time.Millisecond {
        // 慢查询告警
    }
}

3.7 GORM 配置选项

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    // 跳过默认事务
    SkipDefaultTransaction: false,
    
    // 命名策略
    NamingStrategy: schema.NamingStrategy{
        TablePrefix:   "t_",      // 表名前缀
        SingularTable: true,      // 使用单数表名
        NameReplacer:  strings.NewReplacer("CID", "Cid"), // 名称替换
    },
    
    // 全表更新保护
    AllowGlobalUpdate: false,   // 默认 false,防止无 WHERE 的 UPDATE/DELETE
    
    // 禁用自动 Ping
    DisableAutomaticPing: false,
    
    // 禁用嵌套事务
    DisableNestedTransaction: false,
    
    // 允许返回非受影响记录的错误
    AllowErrorNonConforming: false,
    
    // 禁用外键约束(指定 OnDelete/OnUpdate 时)
    DisableForeignKeyConstraintWhenMigrating: false,
    
    // 忽略迁移时的关联引用
    IgnoreRelationshipsWhenMigrating: false,
    
    // 禁用自动递增偏好(某些数据库需要)
    DisableAutoIncrementPreference: false,
    
    // 自定义日志
    Logger: logger.Default,
})

3.8 多数据库配置

读写分离

type DBPool struct {
    Write *gorm.DB
    Read  *gorm.DB
}

func NewDBPool() *DBPool {
    // 主库(写)
    writeDB, err := gorm.Open(mysql.Open(writeDSN), &gorm.Config{})
    // 从库(读)
    readDB, err := gorm.Open(mysql.Open(readDSN), &gorm.Config{})
    
    return &DBPool{
        Write: writeDB,
        Read:  readDB,
    }
}

// 使用
pool.Write.Create(&user)    // 写操作
pool.Read.First(&user, 1)   // 读操作

动态切换数据库

db.Use(dbresolver.Register(dbresolver.Config{
    // 主库
    Sources: []gorm.Dialector{mysql.Open("main_dsn")},
    // 从库
    Replicas: []gorm.Dialector{
        mysql.Open("replica1_dsn"),
        mysql.Open("replica2_dsn"),
    },
    // 负载均衡策略
    Policy: dbresolver.RandomPolicy{},
}).
    Register(dbresolver.Config{
        Sources:  []gorm.Dialector{mysql.Open("user_main_dsn")},
        Replicas: []gorm.Dialector{mysql.Open("user_replica_dsn")},
    }, &User{}, &Order{}). // 特定模型使用特定配置
    SetConnMaxIdleTime(time.Hour).
    SetConnMaxLifetime(24 * time.Hour).
    SetMaxIdleConns(100).
    SetMaxOpenConns(200))

3.9 连接健康检查

import (
    "context"
    "time"
)

func CheckDBHealth(db *gorm.DB) error {
    sqlDB, err := db.DB()
    if err != nil {
        return err
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    return sqlDB.PingContext(ctx)
}

// 统计信息
func GetDBStats(db *gorm.DB) sql.DBStats {
    sqlDB, _ := db.DB()
    return sqlDB.Stats()
}

3.10 完整配置示例

package database

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

var DB *gorm.DB

func InitDB() error {
    var err error
    
    // DSN 配置
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
        "root",
        "password",
        "127.0.0.1",
        3306,
        "myapp",
    )

    // GORM 配置
    config := &gorm.Config{
        Logger: logger.New(
            log.New(os.Stdout, "\r\n", log.LstdFlags),
            logger.Config{
                SlowThreshold: time.Second,
                LogLevel:      logger.Info,
                Colorful:      true,
            },
        ),
        NamingStrategy: schema.NamingStrategy{
            TablePrefix:   "app_",
            SingularTable: true,
        },
        DisableForeignKeyConstraintWhenMigrating: true,
    }

    // 连接数据库
    DB, err = gorm.Open(mysql.Open(dsn), config)
    if err != nil {
        return fmt.Errorf("数据库连接失败: %w", err)
    }

    // 配置连接池
    sqlDB, err := DB.DB()
    if err != nil {
        return err
    }
    
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour)
    sqlDB.SetConnMaxIdleTime(30 * time.Minute)

    // 检查连接
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    if err = sqlDB.PingContext(ctx); err != nil {
        return fmt.Errorf("数据库 ping 失败: %w", err)
    }

    return nil
}

func CloseDB() error {
    if DB != nil {
        sqlDB, err := DB.DB()
        if err != nil {
            return err
        }
        return sqlDB.Close()
    }
    return nil
}

3.11 练习题

  1. 配置 MySQL 连接,开启慢查询日志(阈值 500ms)
  2. 编写连接池配置,适应高并发场景(1000 QPS)
  3. 实现一个数据库健康检查接口,返回连接池统计信息

3.12 小结

本章详细讲解了 GORM 的数据库连接配置,包括各类数据库的 DSN 格式、连接池优化、日志配置等。合理的连接配置是保证应用性能和稳定性的关键。


本文代码地址:https://github.com/LittleMoreInteresting/gorm_study

欢迎关注公众号,一起学习进步!

如有疑问关注公众号给我留言
wx

关注公众号

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo