7.2 KiB
7.2 KiB
tags, create time
| tags | create time | |||||||
|---|---|---|---|---|---|---|---|---|
|
2026-04-28 00:00 |
多数据库支持
概述
GORM 的「驱动适配器」架构让它能够无缝切换不同的数据库后端——你写同一份 GORM 代码,只需改变 gorm.Open() 的参数就能连接不同数据库。但要注意:不同数据库的方言差异可能导致某些功能表现不一致。
flowchart TD
Code["你的 GORM 代码"] --> Adapter{"Driver Adapter"}
Adapter --> |mysql| MySQL["MySQL / MariaDB"]
Adapter --> |postgres| PG["PostgreSQL"]
Adapter --> |sqlite| SQLite["SQLite"]
Adapter --> |mssql| MSSQL["SQL Server"]
style Code fill:#4FC08D,color:#fff
style Adapter fill:#EAB308,color:#fff
style MySQL fill:#3B82F6,color:#fff
style PG fill:#8B5CF6,color:#fff
style SQLite fill:#A0AEC0,color:#fff
style MSSQL fill:#F59E0B,color:#000
驱动安装与初始化
MySQL / MariaDB
import (
"gorm.io/driver/mysql"
)
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 参数说明:
| 参数 | 必填 | 说明 |
|---|---|---|
charset |
✅ 推荐 | 必须设为 utf8mb4(支持 emoji) |
parseTime |
✅ 推荐 | 自动将 DB 时间转为 Go time.Time |
loc |
✅ 推荐 | Local 或 Asia/Shanghai,避免时区混乱 |
timeout |
可选 | 连接超时时间,如 30s |
readTimeout |
可选 | 读取超时 |
writeTimeout |
可选 | 写入超时 |
PostgreSQL
import (
"gorm.io/driver/postgres"
)
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=require TimeZone=Asia/Shanghai"
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
PostgreSQL 特有注意:
sslmode在开发环境可设为disable,生产环境保持require或verify-fullTimeZone要与服务端一致,否则时间会偏移
SQLite
import (
"gorm.io/driver/sqlite"
)
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
// 简单到只需要一个文件路径!
SQLite 注意事项:
- 默认是文件级锁,并发写入会有阻塞
- 高并发场景下建议用
file:test.db?cache=shared&_journal=WAL启用共享缓存和 WAL 模式 - 不支持外键约束——AutoMigrate 不会创建外键
SQL Server
import (
"gorm.io/driver/mssql"
)
dsn := "sqlserver://user:password@host:1433?database=dbname"
db, err := gorm.Open(mssql.Open(dsn), &gorm.Config{})
方言差异速查表
这是跨数据库迁移时最容易踩坑的部分——同样是 time.Time,在不同数据库中的列类型可能完全不同:
| 特性 | MySQL | PostgreSQL | SQLite | SQL Server |
|---|---|---|---|---|
time.Time |
DATETIME / TIMESTAMP | TIMESTAMPTZ | TEXT(序列化后) | DATETIME2 |
string |
VARCHAR(n) | VARCHAR / TEXT | TEXT | NVARCHAR(n) |
uint |
INT UNSIGNED | BIGINT | INTEGER | INT |
float64 |
DOUBLE | DOUBLE PRECISION | REAL | FLOAT |
| 自增主键 | AUTO_INCREMENT | SERIAL / BIGSERIAL | AUTOINCREMENT | IDENTITY(1,1) |
| NOW() 函数 | NOW() | NOW() | DATETIME('now') | GETUTCDATE() |
| LIMIT 语法 | LIMIT N OFFSET M | LIMIT N OFFSET M | LIMIT N OFFSET M | TOP N / OFFSET FETCH |
| 软删除默认行为 | ⚠️ InnoDB 唯一约束 bug | ✅ 正常 | ✅ 正常 | ✅ 正常 |
CREATE TABLE 差异
同一个 struct 在不同数据库下生成的建表语句不同:
type User struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"size:64;not null"`
Age int `gorm:"not null;default:0"`
CreatedAt time.Time `gorm:"autoTime"`
}
| 数据库 | 实际建表语句摘要 |
|---|---|
| MySQL | id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(64) NOT NULL, age INT NOT NULL DEFAULT 0, created_at DATETIME |
| PostgreSQL | id BIGSERIAL PRIMARY KEY, name VARCHAR(64) NOT NULL, age INT NOT NULL DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE |
| SQLite | id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER NOT NULL DEFAULT 0, created_at TEXT |
[!tip] 如何解决方言差异?
- 选择目标数据库再编码——如果你确定只部署在 MySQL 上,就用 MySQL 的特性;
- 使用兼容模式——优先使用 GORM 内置的类型映射,不要手动指定方言特有的类型;
- 多数据库测试——CI 中同时跑多个数据库版本的测试。
AutoMigrate 跨数据库问题
// MySQL
err := db.AutoMigrate(&User{}, &Order{})
// PostgreSQL
pgDB, _ := gorm.Open(postgres.Open(pgDSN), &gorm.Config{})
err = pgDB.AutoMigrate(&User{}, &Order{})
// 注意:同样一个 User struct,在 PG 中会自动用 BIGSERIAL 而非 AUTO_INCREMENT
[!warning] 迁移不可逆操作 某些操作在不同数据库中表现不同:
- MySQL
AUTO_INCREMENT→ PostgresBIGSERIAL:已有的 MySQL 表迁移到 PG 时,自增序列需要重新设置- SQLite 不支持 ALTER TABLE 添加非空列——
AutoMigrate只能新增列不能修改已有列的结构最佳实践:使用独立的迁移工具(如 Goose、golang-migrate),手写 SQL 脚本而非依赖 AutoMigrate。
查询语法差异
// 分页:MySQL / SQLite vs PostgreSQL 基本一致
db.Limit(10).Offset(20).Find(&users)
// SQL Server 的分页语法不同——GORM 内部会自动转换
// db.Limit(10).Offset(20).Find(&users)
// → SELECT TOP 10 * FROM users WHERE id NOT IN (SELECT TOP 20 id FROM users)
// UUID 字段
type Order struct {
ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid()"` // PG
// 改为 type:char(36);default:newid() // SQL Server
// 改为 type:binary(16) // MySQL
}
最佳实践总结
开发环境 vs 生产环境
func newDB(env string) (*gorm.DB, error) {
var dsn string
switch env {
case "dev":
dsn = "file:test_dev.db?cache=shared" // SQLite 快速迭代
case "staging":
dsn = os.Getenv("DATABASE_URL") // 共享 PG 实例
case "prod":
dsn = os.Getenv("PROD_DATABASE_URL") // 独立 PG 集群
}
driver := "sqlite"
if env != "dev" {
driver = "postgres"
}
return gorm.Open(getDriver(driver)(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent), // 生产环境关闭详细日志
})
}
func getDriver(name string) func(string) gorm.Dialector {
switch name {
case "postgres":
return postgres.Open
case "mysql":
return mysql.Open
case "sqlite":
return sqlite.Open
case "mssql":
return mssql.Open
default:
panic("unknown driver: " + name)
}
}
[!tip] 为什么推荐开发用 SQLite?
- 零配置:不需要启动数据库服务
- 单文件:版本控制友好(当然大表不适合 git)
- 语法兼容性:大部分 SQL 标准都能正常工作
但:SQLite 不支持并发写入和事务隔离级别调整,不能完全替代生产数据库做性能测试。