数据库连接

用 GORM 操作数据库,第一步就是建立连接。这个环节看似简单,但配置不当往往会埋下隐患——连接泄漏、超时、字符编码问题,都是常见坑。

基本连接方式

GORM 通过 gorm.Open() 函数建立数据库连接,基本签名如下:

func Open(driverName string, dialector Dialector, opts ...Option) (db *DB, err error)

实际使用时,我们更多采用具体数据库的 Dialector 来初始化:

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

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    panic("数据库连接失败")
}

这里的 dsn 是数据源名称(Data Source Name),不同数据库格式各异。

DSN 是什么

DSN 本质上是一个连接字符串,告诉程序去哪连数据库、用什么账号。以 MySQL 为例:

username:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local

拆开看:

  • username:password - 认证信息
  • tcp(host:port) - 网络地址
  • /dbname - 目标数据库
  • ?charset=utf8mb4... - 连接参数

PostgreSQL 的 DSN 长得不一样:

host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai

GORM 配置项

gorm.Config 结构体控制 GORM 的行为,常用配置:

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    SkipDefaultTransaction: true,
    PrepareStmt:            true,
    Logger:                 logger.Default.LogMode(logger.Info),
})

几个关键配置说明:

SkipDefaultTransaction

默认情况下,GORM 会在创建、更新、删除操作外面包一层事务。这保证了操作的原子性,但也带来性能开销。如果你确定不需要事务保护,可以设为 true

PrepareStmt

预编译语句缓存。开启后,相同的 SQL 只需编译一次,后续直接复用。对高频操作有明显性能提升。

Logger

日志配置。开发环境建议开启详细日志,生产环境可以调低级别:

import "gorm.io/gorm/logger"

newLogger := logger.New(
    log.New(os.Stdout, "\r\n", log.LstdFlags),
    logger.Config{
        SlowThreshold: time.Second,
        LogLevel:      logger.Warn,
        Colorful:      false,
    },
)

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    Logger: newLogger,
})

连接测试

拿到 *gorm.DB 后,别急着往下写。先测一下连接是否正常:

sqlDB, err := db.DB()
if err != nil {
    panic("获取底层连接失败")
}

err = sqlDB.Ping()
if err != nil {
    panic("数据库连接测试失败")
}
fmt.Println("数据库连接成功")

db.DB() 返回底层的 *sql.DB,这是 Go 标准库的数据库连接对象。通过它可以访问原生方法,也能配置连接池。

延迟初始化模式

生产项目通常不会在 main 函数里直接连数据库,而是封装成独立的初始化函数:

var DB *gorm.DB

func InitDB() error {
    dsn := "root:123456@tcp(127.0.0.1:3306)/myapp?charset=utf8mb4&parseTime=True&loc=Local"
    
    var err error
    DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        return err
    }
    
    sqlDB, _ := DB.DB()
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetMaxIdleConns(10)
    
    return nil
}

其他模块通过全局变量 DB 访问数据库,避免到处传参。

错误处理

连接失败的原因很多:网络不通、认证失败、数据库不存在。区分处理有助于定位问题:

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
    if strings.Contains(err.Error(), "connection refused") {
        log.Fatal("数据库服务未启动")
    }
    if strings.Contains(err.Error(), "Access denied") {
        log.Fatal("用户名或密码错误")
    }
    if strings.Contains(err.Error(), "Unknown database") {
        log.Fatal("数据库不存在")
    }
    log.Fatalf("连接失败: %v", err)
}

实际项目中,错误信息应该更友好,避免暴露技术细节给用户。

多数据库连接

有时需要同时连接多个数据库,比如主从分离、读写分离、多租户场景:

var mainDB, logDB *gorm.DB

func InitDatabases() error {
    var err error
    
    mainDB, err = gorm.Open(mysql.Open(mainDSN), &gorm.Config{})
    if err != nil {
        return err
    }
    
    logDB, err = gorm.Open(mysql.Open(logDSN), &gorm.Config{})
    if err != nil {
        return err
    }
    
    return nil
}

使用时根据业务选择对应的连接:

mainDB.Create(&user)
logDB.Create(&operationLog)

连接关闭

程序退出时要关闭连接,释放资源:

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

配合 defer 或信号处理:

func main() {
    if err := InitDB(); err != nil {
        log.Fatal(err)
    }
    defer CloseDB()
    
    // 业务逻辑...
}

小结

数据库连接是 GORM 的起点。掌握 DSN 格式、Config 配置、连接池参数,才能写出健壮的数据访问层。下一章开始,我们分别看各数据库的连接细节。