--- tags: [GORM, Go, ORM, MySQL, PostgreSQL, SQLite, SQL Server] create time: 2026-04-28 00:00 --- # 多数据库支持 ## 概述 GORM 的「驱动适配器」架构让它能够无缝切换不同的数据库后端——你写同一份 GORM 代码,只需改变 `gorm.Open()` 的参数就能连接不同数据库。但要注意:**不同数据库的方言差异**可能导致某些功能表现不一致。 ```mermaid 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 ```go 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 ```go 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-full` - `TimeZone` 要与服务端一致,否则时间会偏移 ### SQLite ```go 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 ```go 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 在不同数据库下生成的建表语句不同: ```go 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] 如何解决方言差异? > 1. **选择目标数据库再编码**——如果你确定只部署在 MySQL 上,就用 MySQL 的特性; > 2. **使用兼容模式**——优先使用 GORM 内置的类型映射,不要手动指定方言特有的类型; > 3. **多数据库测试**——CI 中同时跑多个数据库版本的测试。 ## AutoMigrate 跨数据库问题 ```go // 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` → Postgres `BIGSERIAL`:已有的 MySQL 表迁移到 PG 时,自增序列需要重新设置 > - SQLite 不支持 ALTER TABLE 添加非空列——`AutoMigrate` 只能新增列不能修改已有列的结构 > > **最佳实践**:使用独立的迁移工具(如 Goose、golang-migrate),手写 SQL 脚本而非依赖 AutoMigrate。 ## 查询语法差异 ```go // 分页: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 生产环境 ```go 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 不支持并发写入和事务隔离级别调整,不能完全替代生产数据库做性能测试。 ## 关联笔记 - [[01-安装与初始化]] - [[02-模型定义]] - [[17-迁移工具]]