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
2026-04-28 20:23:33 +08:00

225 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-迁移工具]]