Rails项目如何将integer类型主键及关联字段全部改为bigint
解决方案
1. 列举所有integer类型主键的方法
方法1:Rails控制台批量查询
直接在项目控制台执行以下代码,可遍历所有模型匹配integer类型主键:
# 预加载所有模型 Rails.application.eager_load! integer_pk_tables = [] ActiveRecord::Base.descendants.each do |model| next if model.abstract_class? || model.table_name.blank? pk_column = model.columns_hash[model.primary_key] if pk_column&.type == :integer && pk_column&.sql_type != 'bigint' integer_pk_tables << { table: model.table_name, primary_key: model.primary_key, type: pk_column.sql_type } end end # 输出结果 pp "Integer类型主键的表清单:" pp integer_pk_tables
方法2:直接通过数据库SQL查询
- PostgreSQL版本:
SELECT tc.table_name, kcu.column_name AS primary_key, c.data_type FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.columns c ON c.table_name = tc.table_name AND c.column_name = kcu.column_name WHERE tc.constraint_type = 'PRIMARY KEY' AND c.data_type = 'integer' ORDER BY tc.table_name;
- MySQL版本:
SELECT TABLE_NAME AS table_name, COLUMN_NAME AS primary_key, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND COLUMN_KEY = 'PRI' AND DATA_TYPE = 'int';
2. 批量修改迁移脚本
注意:迁移前务必全量备份数据库,大表建议在业务低峰期执行,避免锁表影响线上服务
首先生成迁移文件:rails g migration convert_int_pks_and_fks_to_bigint
然后替换迁移文件内容为以下代码:
class ConvertIntPksAndFksToBigint < ActiveRecord::Migration[5.1] disable_ddl_transaction! # 大表场景使用非事务模式,避免长事务锁库 def up # 第一步:转换所有integer类型主键为bigint integer_pk_tables.each do |table, pk| # PostgreSQL场景请删除auto_increment参数 change_column table, pk, :bigint, auto_increment: true end # 第二步:转换所有显式声明的外键 connection.foreign_keys.each do |fk| from_column = fk.column from_table = fk.from_table column_type = connection.columns(from_table).find { |c| c.name == from_column }&.sql_type next unless column_type.in?(%w[int integer]) # 先删除外键约束,修改字段类型后再重建约束 remove_foreign_key from_table, name: fk.name change_column from_table, from_column, :bigint # MySQL场景可加algorithm: :inplace减少锁表时间 add_foreign_key from_table, fk.to_table, column: from_column, name: fk.name end # 第三步:转换未声明外键、符合xxx_id命名规范的关联字段 connection.tables.each do |table| connection.columns(table).each do |col| next unless col.name.end_with?('_id') && col.sql_type.in?(%w[int integer]) && col.name != connection.primary_key(table) change_column table, col.name, :bigint end end end def down # 不建议回滚,如有需要可基于备份恢复数据 raise ActiveRecord::IrreversibleMigration end private # 提前获取所有integer类型主键的表 def integer_pk_tables @integer_pk_tables ||= begin res = [] connection.tables.each do |table| pk = connection.primary_key(table) next unless pk col = connection.columns(table).find { |c| c.name == pk } res << [table, pk] if col&.sql_type.in?(%w[int integer]) end res end end end
执行迁移即可完成批量转换:rails db:migrate
额外注意事项
- 如果使用MySQL < 8.0或者MariaDB版本,修改主键会触发表重建,10G以上大表建议使用pt-online-schema-change这类无锁变更工具执行
- 迁移完成后可重新执行第一部分的查询脚本,验证所有integer类型的主键、关联字段都已完成转换
内容的提问来源于stack exchange,提问作者oma
相关产品推荐
相关产品推荐

