225 lines
7.2 KiB
Markdown
225 lines
7.2 KiB
Markdown
|
|
---
|
|||
|
|
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-迁移工具]]
|