用 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 本质上是一个连接字符串,告诉程序去哪连数据库、用什么账号。以 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.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 配置、连接池参数,才能写出健壮的数据访问层。下一章开始,我们分别看各数据库的连接细节。