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

7.2 KiB
Raw Blame History

tags, create time
tags create time
GORM
Go
ORM
MySQL
PostgreSQL
SQLite
SQL Server
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-full
  • TimeZone 要与服务端一致,否则时间会偏移

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] 如何解决方言差异?

  1. 选择目标数据库再编码——如果你确定只部署在 MySQL 上,就用 MySQL 的特性;
  2. 使用兼容模式——优先使用 GORM 内置的类型映射,不要手动指定方言特有的类型;
  3. 多数据库测试——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 → Postgres BIGSERIAL:已有的 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 不支持并发写入和事务隔离级别调整,不能完全替代生产数据库做性能测试。

关联笔记