Buffalo改表结构必须用pop迁移文件而非仅改models/struct,否则buffalo db migrate无法感知变化导致上线后panic;新增字段用pop generate fizz,改类型需注意MySQL与PostgreSQL语法差异,删字段须谨慎并优先用rename_column,所有操作需同步更新struct tag、actions及上下游调用方。

Buffalo 用 pop 迁移改表结构,不是直接改 models/ 下的 struct
Buffalo 默认用 pop 做数据库迁移,改表结构必须走 migration 文件,不能只改 models/user.go 里的字段——否则 buffalo db migrate 不会感知变化,上线后结构不一致,查数据直接 panic。
常见错误现象:改完 struct 本地跑得通,部署后报 column "xxx" does not exist 或 scan error on column index X,就是没跑迁移。
- 新增字段:运行
buffalo pop generate fizz add_email_to_users,在生成的pop/fizz/xxx_add_email_to_users.fizz里写add_column("users", "email", "string") - 改字段类型:MySQL 用
modify_column("users", "age", "int");PostgreSQL 必须用execute("ALTER TABLE users ALTER COLUMN age TYPE integer USING age::integer"),因为 pop 的modify_column在 PG 里不生效 - 删字段:先确认无业务读写该字段,再写
remove_column("users", "legacy_flag");线上慎用,建议先rename_column观察一周
迁移文件里别写硬编码 SQL,优先用 fizz DSL
fizz 是 pop 自带的跨数据库迁移语言,写 add_column、create_table 能自动适配 MySQL/PostgreSQL/SQLite。一旦混入 execute("CREATE INDEX ..."),就失去可移植性——比如 PostgreSQL 要加 CONCURRENTLY,MySQL 不认。
性能影响:大表加索引或改类型时,fizz 默认不加 IF NOT EXISTS 或并发选项。线上执行前务必手动补上:
- 加唯一索引:写
execute("CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users(email)")(仅 PG) - MySQL 大字段变更:先
execute("ALTER TABLE users MODIFY COLUMN bio TEXT"),避免 fizz 生成的modify_column在大表上锁表
本地开发改了迁移,上线前必须检查 buffalo db migrate -e production 是否能成功
很多团队本地用 SQLite 开发、线上用 PostgreSQL,fizz 语法虽通用,但字段类型映射有差异:比如 "string" 在 SQLite 是 text,在 PG 是 varchar(255)。上线前不验证,可能卡在 invalid input syntax for type json 这类隐式转换错误。
容易踩的坑:
-
buffalo db migrate默认用development环境,上线前漏掉-e production参数,实际没跑生产迁移 - 迁移文件名带时间戳,但多人协作时可能冲突。解决办法:提交前运行
buffalo pop migrate -d回滚最新一条,再buffalo pop migrate重放,确保顺序一致 - 字段改名用
rename_column("users", "name", "full_name"),别手写execute("ALTER TABLE ... RENAME ...")——pop 不会更新内部 schema cache,后续pop.Find可能仍找旧字段名
改完表结构后,记得同步更新 models/ 和 actions/ 里的代码
迁移只是改库,struct 和 handler 逻辑要人工对齐。例如给 users 加了 status 字段,但 models/user.go 没加对应字段,pop.Find 会静默忽略该列;如果 actions/users.go 的 Create handler 没在 u := &models.User{} 里初始化 Status,新用户 status 就是空字符串而非预期的 "active"。
关键点:
- struct 字段 tag 必须和迁移一致:
Status string `json:"status" db:"status"`,少一个db:tag,pop 就不会映射到列 - 非空字段上线后要设默认值:迁移里写
add_column("users", "status", "string", {"null": false, "default": "'active'"}),否则已有行插入失败 - JSON 字段在 PostgreSQL 要显式声明
db:"status->json",否则 pop 当普通字符串处理,入库是转义后的字符串而非原生 JSON
实际改表最麻烦的永远不是语法,而是字段语义变更后上下游调用方是否同步更新——比如把 phone 字段拆成 country_code 和 number,API 返回结构变了,前端、APP、内部服务全得跟着动。这类事没法靠工具发现,只能靠人盯。











