You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails迁移回滚时如何高效截断超长String字段数据?

解决Rails迁移回滚时数据过长导致崩溃的问题

我正在执行一项增大String字段长度限制的任务,已编写如下Rails迁移文件:

class IncreaseCompanyNumberLimit < ActiveRecord::Migration[6.1]
  def up
    if Company.columns.find { |c| c.name == 'company_number' }.limit < 25
      change_column :company, :company_number, :string, limit: 25
    end  
  end

  def down
    change_column :company, :company_number, :string, limit: 20
  end
end

遇到的问题:执行up迁移后,若有用户输入了长度超过20的company_number,执行down迁移时会因现有数据过长而崩溃。需要寻找一种高效的方法截断超出down方法设置的长度限制的数据,且涉及的表数据量较大。


解决方案:先截断数据再修改字段长度

由于表数据量大,避免用ActiveRecord的Ruby层面操作(比如find_each循环更新),直接用原生SQL处理是最高效的方式。修改down方法如下:

1. 基础版(适合数据量较大但未到超大规模)

class IncreaseCompanyNumberLimit < ActiveRecord::Migration[6.1]
  def up
    if Company.columns.find { |c| c.name == 'company_number' }.limit < 25
      change_column :companies, :company_number, :string, limit: 25
    end  
  end

  def down
    # 根据数据库类型选择对应的字符串截断函数
    # MySQL/MariaDB 用 LEFT (注意区分字节数/字符数:CHAR_LENGTH是字符数,LENGTH是字节数)
    execute <<-SQL
      UPDATE companies 
      SET company_number = LEFT(company_number, 20) 
      WHERE CHAR_LENGTH(company_number) > 20;
    SQL

    # PostgreSQL 用 SUBSTRING 或 LEFT
    # execute <<-SQL
    #   UPDATE companies 
    #   SET company_number = SUBSTRING(company_number FROM 1 FOR 20) 
    #   WHERE LENGTH(company_number) > 20;
    # SQL

    # 执行字段长度修改
    change_column :companies, :company_number, :string, limit: 20
  end
end

2. 分批更新版(适合超大规模数据,避免锁表超时)

如果表中数据量极大(数百万级以上),单次UPDATE可能会锁表时间过长影响业务,此时可以分批执行更新:

class IncreaseCompanyNumberLimit < ActiveRecord::Migration[6.1]
  def up
    if Company.columns.find { |c| c.name == 'company_number' }.limit < 25
      change_column :companies, :company_number, :string, limit: 25
    end  
  end

  def down
    batch_size = 1000
    last_id = 0

    loop do
      # MySQL/MariaDB 版本
      result = execute <<-SQL
        UPDATE companies 
        SET company_number = LEFT(company_number, 20) 
        WHERE id > #{last_id} 
          AND CHAR_LENGTH(company_number) > 20
        LIMIT #{batch_size};
      SQL

      break if result.affected_rows == 0

      # 更新last_id,获取当前批次的最大ID
      last_id = execute("SELECT MAX(id) FROM companies WHERE id > #{last_id};").first["MAX(id)"] || last_id + batch_size
    end

    # 修改字段长度
    change_column :companies, :company_number, :string, limit: 20
  end
end

关键注意事项

  • 执行迁移前务必备份数据,避免截断操作导致的不可逆数据丢失;
  • 根据使用的数据库调整SQL函数(比如字符长度判断、截断函数的差异);
  • 分批更新时,可根据数据库性能调整batch_size的大小,平衡执行效率和锁表时间。

内容的提问来源于stack exchange,提问作者Charles Dayman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:05:25