Rails向PostgreSQL添加枚举列为何用change_table而非add_column?是否安全?
Rails 7 枚举列:change_table vs add_column 区别及生产环境安全说明
一、change_table 和 add_column 到底差在哪?
说白了,这俩写法底层完全一样——都是给表新增枚举列,执行的SQL没有任何区别。只是写法风格不同:
add_column是直给式写法,适合单独加一列的场景:add_column :articles, :current_status, :enum, enum_type: "status", default: "draft", null: falsechange_table是块式写法,如果你要一次性做多个表结构修改(比如同时加两列、改个索引),用这个写法会更整洁,把所有表操作打包在一块。
如果只是单独加一个枚举列,两种写法在功能、性能上没任何差异。
二、为什么Strong Migrations要我加safety_assured?
Strong Migrations的提示纯粹是因为它搞不定change_table块里的内容解析,没法自动判断你的操作是不是安全的,所以需要你手动确认没问题后,用safety_assured块把代码包起来。
但你这个操作本身是安全的:你只是新增一个带默认值的枚举列,基于PostgreSQL原生枚举的特性,这个操作是无锁、不重写整表的,对大表性能影响极小:
- 枚举类型是提前创建好的,新增列只是把列和已有枚举类型关联
- 设置默认值
draft且null: false时,PG会自动给现有行填充默认值,新版本PG里这个操作是在线完成的,不会阻塞读写
所以放心把代码包进去就行:
def up create_enum :status, ["draft", "published", "archived", "trashed"] safety_assured do change_table :articles do |t| t.enum :current_status, enum_type: "status", default: "draft", null: false end end end
三、超大表的额外优化建议
如果你的articles表是千万级以上的超大表,怕一次性填充默认值有IO压力,可以分三步迁移:
- 先新增允许空值的枚举列,不设默认值:
def up create_enum :status, ["draft", "published", "archived", "trashed"] safety_assured do change_table :articles do |t| t.enum :current_status, enum_type: "status" end end end - 用后台任务批量更新现有行,比如用
Article.find_in_batches来逐批设置current_status为draft,避免一次性加载所有数据撑爆内存 - 最后修改列,设置
null: false和默认值:def up safety_assured do change_table :articles do |t| t.change :current_status, :enum, enum_type: "status", default: "draft", null: false end end end
这样能把数据写入压力分散开,避免影响生产环境的正常服务。
内容的提问来源于stack exchange,提问作者melanie
相关产品推荐
相关产品推荐

