Go反射无法直接生成跨库兼容DDL,因其仅提取结构体元数据,不理解MySQL/PostgreSQL/SQLite等方言差异;必须分层:反射负责结构体→列定义映射,方言适配器负责将中间表示渲染为对应SQL。

Go 反射本身不能生成跨库兼容的 DDL SQL——DDL(如 CREATE TABLE)语义在 MySQL、PostgreSQL、SQLite 之间差异极大,反射只读结构体元数据,不理解数据库方言;真要跨库,必须分层:反射仅负责「结构体 → 列定义映射」,DDL 生成必须由方言适配器完成。
为什么直接用 reflect 拼 DDL 必然失败
反射能拿到 ID int `db:"id"` 这样的字段名和标签,但无法回答这些问题:
- MySQL 的
AUTO_INCREMENT对应 PostgreSQL 是SERIAL或GENERATED ALWAYS AS IDENTITY - SQLite 不支持
NOT NULL DEFAULT CURRENT_TIMESTAMP,得拆成两步或改用触发器 - PostgreSQL 要求主键字段显式加
PRIMARY KEY,而 MySQL 允许INT PRIMARY KEY AUTO_INCREMENT合并在一个字段声明里 - 字段类型映射根本不是一一对应:
time.Time在 MySQL 是DATETIME,在 PostgreSQL 是TIMESTAMP WITH TIME ZONE,SQLite 只能存字符串或整数
硬拼会导致迁移脚本在某个库跑通、换一个就报语法错误,且错误发生在运行时,无法静态检查。
正确分层:反射只做 Schema Mapping,不碰 SQL 字符串
目标是把结构体转成中间表示(IR),再由方言引擎渲染。例如:
type User struct {
ID int64 `db:"id" sql:"pk,autoinc"`
Name string `db:"name" sql:"size(255)"`
CreatedAt time.Time `db:"created_at" sql:"default:now"`
}
反射阶段只提取出结构化描述:
Column{Name: "id", Type: "int64", Constraints: ["pk", "autoinc"]}Column{Name: "name", Type: "string", Options: map[string]string{"size": "255"}}Column{Name: "created_at", Type: "time.Time", Default: "now"}
关键实操点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
reflect.StructTag.Get("sql")解析约束,而非硬编码逻辑到反射遍历里 - 跳过非导出字段:
if !field.IsExported() { continue },避免 panic - 对嵌入字段(
Embedded: true)需递归处理,但只展开带sql:",inline"标签的,否则破坏命名空间 - 别在反射循环里调
v.Interface()——DDL 不需要值,只需要类型和标签,取值纯属冗余且可能触发 nil panic
MySQL/PostgreSQL/SQLite 三库适配的关键差异点
同一份 IR 渲染为不同 DDL 时,必须隔离处理以下项:
-
主键生成:
mysql用INT NOT NULL AUTO_INCREMENT PRIMARY KEY;postgres用SERIAL PRIMARY KEY或INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY;sqlite用INTEGER PRIMARY KEY AUTOINCREMENT -
时间默认值:
mysql→CURRENT_TIMESTAMP;postgres→CURRENT_TIMESTAMP(但需加WITH TIME ZONE才安全);sqlite→CURRENT_TIMESTAMP(仅支持该字面量) -
字符串长度:
mysql和postgres都支持VARCHAR(255);sqlite忽略长度限制,声明为TEXT即可 -
布尔类型:
mysql用TINYINT(1);postgres用BOOLEAN;sqlite无原生 bool,用INTEGER CHECK(value IN (0,1))
建议封装三个方言实现,共用同一套反射解析层,避免把 if/else 塞进反射代码里。
绕不开的陷阱:time.Time 和 JSON 字段的跨库表达
这两个类型最易踩坑,因为各库原生支持度不一:
-
time.Time:MySQL 5.6+ 支持DATETIME(6),PostgreSQL 支持TIMESTAMP WITH TIME ZONE,SQLite 只能存 ISO8601 字符串或 Unix 时间戳整数 —— 反射阶段必须允许用户通过sql:"type:varchar"显式覆盖类型 -
map[string]interface{}或json.RawMessage:MySQL 5.7+ 有JSON类型;PostgreSQL 有JSONB(推荐);SQLite 3.38+ 有JSON1扩展但非默认启用 —— 若目标库不支持,反射层应拒绝生成 DDL,而不是默默降级为TEXT
真正难的不是“怎么用反射”,而是“怎么让反射结果在不同库上都合理落地”。一旦忽略方言边界,生成的 DDL 看似通用,实则只在一个环境里能跑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










