This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/GORM/13-多数据库支持.md
T

225 lines
7.2 KiB
Markdown
Raw Normal View History

2026-04-28 20:23:33 +08:00
---
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-迁移工具]]