GORM 如何连接不同的数据库?
GORM 是 Go 语言中最流行的 ORM 框架,官方支持 MySQL、PostgreSQL、SQLite、SQL Server 四种数据库,社区还提供了 ClickHouse、TiDB 等驱动。不同数据库的连接方式各有差异,掌握正确的连接姿势和配置方法,是生产环境稳定运行的基础。
连接 MySQL
MySQL 是 GORM 中使用最广泛的数据库,连接时需要指定 DSN(Data Source Name)字符串。
基本连接
goimport ( "gorm.io/driver/mysql" "gorm.io/gorm" ) 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{})
DSN 中的 parseTime=True 和 loc=Local 是两个容易遗漏的参数——前者让 Go 自动将 datetime 类型解析为 time.Time,后者确保时区与本地一致,否则查询时间字段会报错。
MySQL 专属配置
MySQL 驱动支持一些数据库级别的定制选项:
godb, err := gorm.Open(mysql.New(mysql.Config{ DSN: dsn, DefaultStringSize: 256, // varchar 默认长度 DisableDatetimePrecision: true, // 禁用 datetime 精度(MySQL 5.6 以下) DontSupportRenameIndex: true, // 不支持重命名索引(MySQL 5.7 以下) DontSupportRenameColumn: true, // 不支持重命名列(MySQL 8.0 以下) SkipInitializeWithVersion: false, // 根据版本自动配置 }), &gorm.Config{})
这些选项主要解决旧版本 MySQL 的兼容性问题。如果你的 MySQL 版本在 8.0 以上,大部分选项可以保持默认。
连接 PostgreSQL
PostgreSQL 的 DSN 支持两种格式:键值对格式和 URL 格式。
键值对格式
goimport ( "gorm.io/driver/postgres" "gorm.io/gorm" ) dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable TimeZone=Asia/Shanghai" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
URL 格式
godsn := "postgres://gorm:gorm@localhost:5432/gorm?sslmode=disable&timezone=Asia/Shanghai" db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
URL 格式更适合从环境变量或配置中心读取,拼接更方便。
PostgreSQL 专属配置
godb, err := gorm.Open(postgres.New(postgres.Config{ DSN: dsn, PreferSimpleProtocol: true, // 禁用 prepared statement,减少往返 }), &gorm.Config{})
开启 PreferSimpleProtocol 可以在某些场景下提升性能,但会失去 prepared statement 的安全防护,建议只在内部服务中使用。
连接 SQLite
SQLite 是嵌入式数据库,无需额外部署服务,适合开发测试和小型应用。
文件数据库
goimport ( "gorm.io/driver/sqlite" "gorm.io/gorm" ) db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
内存数据库
godb, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
内存数据库常用于单元测试。注意 cache=shared 参数——没有它,每个连接会拿到独立的内存数据库,数据互不可见。
连接 SQL Server
goimport ( "gorm.io/driver/sqlserver" "gorm.io/gorm" ) dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm" db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
连接 ClickHouse
ClickHouse 是列式 OLAP 数据库,GORM 通过社区驱动支持连接:
goimport ( "gorm.io/driver/clickhouse" "gorm.io/gorm" ) dsn := "tcp://localhost:9000?database=gorm&username=default&password=&read_timeout=10&write_timeout=20" db, err := gorm.Open(clickhouse.Open(dsn), &gorm.Config{})
ClickHouse 不支持事务和部分传统 SQL 特性,使用前需确认你的查询模式是否兼容。
连接 TiDB
TiDB 兼容 MySQL 协议,因此可以直接使用 MySQL 驱动连接:
godsn := "user:password@tcp(tidb-host:4000)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
无需额外驱动,端口默认 4000。
连接池配置
GORM 底层使用 database/sql 的连接池,所有数据库共享同一套配置接口。生产环境务必调整以下参数:
gosqlDB, err := db.DB() if err != nil { panic("failed to get database connection") } sqlDB.SetMaxIdleConns(10) // 空闲连接池最大连接数 sqlDB.SetMaxOpenConns(100) // 最大打开连接数 sqlDB.SetConnMaxLifetime(time.Hour) // 连接最大存活时间 sqlDB.SetConnMaxIdleTime(10 * time.Minute) // 连接最大空闲时间
几个关键经验值:MaxOpenConns 通常设为数据库 CPU 核心数的 2-4 倍;ConnMaxLifetime 应小于数据库的 wait_timeout(MySQL 默认 8 小时),否则会拿到已被服务端关闭的连接。
GORM 全局配置
GORM 的 Config 结构体控制全局行为:
godb, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ SkipDefaultTransaction: true, // 跳过默认事务,提升性能 DisableForeignKeyConstraintWhenMigrating: true, // 迁移时禁用外键约束 PrepareStmt: true, // 预编译语句缓存 })
Logger 配置
生产环境通常需要定制日志级别:
goimport "gorm.io/gorm/logger" newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Config{ SlowThreshold: time.Second, // 慢查询阈值 LogLevel: logger.Warn, // 生产环境用 Warn IgnoreRecordNotFoundError: true, // 忽略 ErrRecordNotFound Colorful: false, // 禁用彩色输出 }, ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: newLogger, })
命名策略配置
控制表名和列名的生成规则:
goimport "gorm.io/gorm/schema" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ NamingStrategy: schema.NamingStrategy{ SingularTable: true, // 禁用表名复数(User → user 而非 users) NoLowerCase: true, // 禁用自动小写 }, })
多数据库与读写分离
多个独立数据库
同时操作多个数据库时,分别创建连接即可:
goprimaryDB, _ := gorm.Open(mysql.Open(primaryDSN), &gorm.Config{}) replicaDB, _ := gorm.Open(mysql.Open(replicaDSN), &gorm.Config{}) primaryDB.Create(&user) // 写主库 replicaDB.First(&user, 1) // 读从库
使用 DBResolver 实现自动读写分离
手动管理两个连接对象容易出错,GORM 提供了 DBResolver 插件自动路由读写请求:
goimport "gorm.io/plugin/dbresolver" db, _ := gorm.Open(mysql.Open(primaryDSN), &gorm.Config{}) db.Use(dbresolver.Register(dbresolver.Config{ Sources: []gorm.Dialector{mysql.Open(primaryDSN)}, // 写库 Replicas: []gorm.Dialector{mysql.Open(replicaDSN1), mysql.Open(replicaDSN2)}, // 读库 Policy: dbresolver.RandomPolicy{}, // 读负载均衡策略 })) db.Create(&user) // 自动路由到 Sources db.First(&user, 1) // 自动路由到 Replicas db.Table("orders").Create(&order) // 按表级别路由
DBResolver 还支持按表、按模型配置不同的数据库源,适合分库分表场景。
环境变量与安全配置
生产环境中不要硬编码数据库密码,应通过环境变量或配置中心注入:
goimport "os" dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_NAME"), ) db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
连接重试与优雅关闭
带退避的重试
服务启动时数据库可能尚未就绪,加入重试逻辑提高健壮性:
gofunc connectWithRetry(dsn string, maxRetries int) (*gorm.DB, error) { var db *gorm.DB var err error for i := 0; i < maxRetries; i++ { db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err == nil { return db, nil } time.Sleep(time.Second * time.Duration(i+1)) // 退避等待 } return nil, fmt.Errorf("after %d retries: %w", maxRetries, err) }
优雅关闭
gosqlDB, _ := db.DB() defer sqlDB.Close()
务必在应用退出时关闭连接,否则连接池中的空闲连接会一直占用数据库资源。
常见问题
连接超时怎么办? 在 DSN 中添加 timeout=10s&readTimeout=30s&writeTimeout=30s,或使用 context.WithTimeout 控制单次操作超时。
连接池设多大合适? MaxOpenConns 一般设为数据库 CPU 核数的 2-4 倍。过高会导致数据库连接数打满,过低则请求排队等待。
如何排查连接泄漏? 监控 sqlDB.Stats() 中的 InUse 和 Idle 字段,如果 InUse 持续增长不回落,通常是因为没有正确关闭 *sql.Rows 或 *sql.Stmt。
切换数据库需要改代码吗? 只需更换 driver 和 DSN,GORM 的查询 API 在各数据库间通用。但要注意不同数据库的 SQL 方言差异,如 PostgreSQL 的 RETURNING、MySQL 的 ON DUPLICATE KEY UPDATE。