1. GORM AutoMigrate 把 Vastbase 打崩了
现象
应用启动时 Vastbase 进程崩溃重启,所有连接收到 connection reset by peer,应用 panic。
根因
GORM 的 AutoMigrate 每次启动都执行上百条无意义的 ALTER TABLE ... SET DEFAULT 语句。
PostgreSQL 扛得住这种重复 DDL,Vastbase 扛不住,进程直接挂。
不是内存不够,不是连接数超限,不是并发问题(AutoMigrate 是串行的)。
是 Vastbase 在 ~10 秒内连续执行 100+ 条 DDL 后内部崩溃。
复现环境:
- 镜像:
registry.cn-hangzhou.aliyuncs.com/qiluo-images/vastbase_g100:20250514160619 - 版本:Vastbase G100 V2.2 (Build 15) Release,编译于 2025-04-03,Commit 25780
为什么会有上百条无意义 DDL
GORM 在 AutoMigrate 时做以下事情:
- 从
information_schema.columns查出每列当前的column_default - 用
parseDefaultValueValue函数清洗这个值 - 和 Go struct tag 中的
default:xxx做字符串比较 - 不一致就执行
ALTER TABLE ... ALTER COLUMN ... SET DEFAULT
问题在第 2 步。parseDefaultValueValue 做了两件事:
|
|
Vastbase 与 PostgreSQL 在原始返回值上只有一处差异:
| 类型 | DDL 定义 | PostgreSQL 返回 | Vastbase 返回 |
|---|---|---|---|
| text | DEFAULT '' |
''::text |
''::text |
| int8 | DEFAULT 0 |
0 |
0 |
| bool | DEFAULT true |
true |
true |
| text | DEFAULT 'zh' |
'zh'::text |
'zh'::text |
| jsonb | DEFAULT '{}' |
'{}'::jsonb |
'{}'::jsonb |
| text[] | DEFAULT '{}' |
'{}'::text[] |
'{}'::text[] |
| timestamptz | DEFAULT CURRENT_TIMESTAMP |
CURRENT_TIMESTAMP |
now() |
只有 timestamptz 不一样:PostgreSQL 存 CURRENT_TIMESTAMP,Vastbase 存 now()。
GORM 的比较逻辑(MigrateColumn)
GORM 按数据类型分支比较,每种类型的比较策略不同:
|
|
三类数据库经过 GORM 处理后的最终对比
| 类型 | DB 返回 → parseDefaultValueValue 结果 | struct tag DefaultValue | PostgreSQL | Vastbase | SQLite |
|---|---|---|---|---|---|
text 'zh' |
zh |
'zh' |
✅(String 双重比较兜底) | ✅ | ✅ |
text '' |
`` (空串) | '' |
✅(String 双重比较兜底) | ✅ | ✅ |
int8 0 |
0 |
0 |
✅ | ✅ | ✅ |
bool true |
true |
true |
✅ | ✅ | ✅ |
jsonb '{}' |
{} |
'{}' |
❌(default 分支直接比较) | ❌ | — |
text[] '{}' |
{} |
'{}' |
❌ | ❌ | — |
| timestamptz | PG: CURRENT_TIMESTAMP / VB: now() |
CURRENT_TIMESTAMP |
✅ | ❌ | ✅ |
结论:
- Vastbase 独有问题:timestamptz(
now()vsCURRENT_TIMESTAMP) - GORM 通病:jsonb/数组(PostgreSQL 上也触发,只是不崩)
60 张表 × 2 个 timestamptz 列 = 120 条 DDL → Vastbase 崩溃。
解决方案
自定义 GORM Dialector,只抹平 Vastbase 与 PostgreSQL 的唯一差异:
|
|
修复后 Vastbase 上的 AutoMigrate 行为与 PostgreSQL 完全一致。
2. GORM jsonb/数组 DEFAULT 每次都 ALTER(已知 Bug)
这个问题在 PostgreSQL 上也存在,不是 Vastbase 特有的。
Issue 链路
| Issue/PR | 状态 | 描述 |
|---|---|---|
| go-gorm/gorm#7553 | Open | jsonb 列每次 AutoMigrate 都执行 SET DEFAULT(2025-08-11) |
| go-gorm/gorm#7590 | Closed | serializer tag 触发同样问题 |
| go-gorm/gorm#7591 | Merged | 修了 schema.String 分支的引号比较(gorm v1.31.2 已包含) |
| go-gorm/gorm#7770 | Closed(未合并) | 尝试修 default 分支(jsonb 等非 String 类型),被关闭 |
| go-gorm/gorm#7806 | Merged | 修了 DEFAULT NULL 无限循环,未覆盖 jsonb |
| go-gorm/postgres#225 | Open | v1.5.4 改变了 jsonb 默认值生成(2023-11-21) |
为什么一直没修
- jinzhu 先合并了 #7591,修了
schema.String分支 → text 列的问题解决了 - jsonb 走的是
MigrateColumn中的default分支(不是schema.String),#7591 的修复对它无效 - 有人提了 #7770,给
default分支加了同样的strings.Trim去引号逻辑,但被关闭了
根因在代码里一目了然:
|
|
jsonb 字段的 GORMDataType 是自定义类型名(如 jsonb),不是 String,所以走 default 分支。
处理建议
- PostgreSQL 上无害,忽略即可
- Vastbase 上如果 DDL 总量(timestamptz + jsonb)足以让服务端崩溃,先修 timestamptz(上一节方案),jsonb 部分等 GORM 上游修
- 关注 go-gorm/gorm#7553,上游修复后升级 GORM 即可
- 如果等不了,可以在自定义 Migrator 中补上 jsonb 的归一化
3. SQL 语法差异
Vastbase 不支持部分 PostgreSQL 高版本语法,需要在驱动层做 SQL 重写。
3.1 make_interval 不支持
|
|
3.2 数组切片 arr[start:] 不支持
Vastbase 不支持 PostgreSQL 的数组切片语法 arr[start:]。River 用这个语法裁剪 attempted_by 数组:
|
|
裁剪的目的是防止数组无限增长,但 attempted_by 受 max_attempts(默认 25)自然约束,不裁剪也不会超过 25 个元素。语义上无影响。
3.3 ON CONFLICT … WHERE 不支持
|
|
注意去掉 WHERE 可能影响条件化唯一索引的语义,需要评估。
3.4 WITH … INSERT … ON CONFLICT DO UPDATE 不支持
Vastbase 不支持 CTE 与 ON CONFLICT DO UPDATE 组合使用。改写方式是将 CTE 内联为 FROM 子查询。
3.5 xid 类型不支持与 integer 直接比较
|
|
3.6 DSN 特殊字符
Vastbase DSN 中密码的特殊字符需要 URL 编码。
4. 连接行为差异
4.1 认证方式
Vastbase 默认用 sha256 认证(pg_hba.conf),pgx 驱动天然支持,无需适配。
4.2 密集 DDL 时的稳定性
Vastbase 在短时间内密集执行 DDL 时容易崩溃。
应对:减少不必要的 DDL,合理设置连接池参数。
4.3 vastbase 兼容模式
vastbase 支持多种数据库的协议连接,部署的时候固定,如果选择了 mysql 模式,则 postgresql 连接会出现很多故障。
SHOW dbcompatibility; 查看兼容模式
| 返回值 | 兼容模式 | 说明 |
|---|---|---|
A |
Oracle | 兼容 Oracle 语法和协议 |
B |
MySQL | 兼容 MySQL 语法和部分协议 |
C |
TD (Teradata) | 兼容 Teradata 语法 |
PG / 空 |
PostgreSQL | 原生 openGauss/PG 模式 |