回滚一个失败的Rails迁移

85

如何回滚一个失败的Rails迁移?我期望rake db:rollback可以撤销失败的迁移,但实际上它撤销的是前一个迁移(失败的迁移减去一个)。而rake db:migrate:down VERSION=myfailedmigration也无效。我遇到过几次这种情况,非常令人沮丧。这里有一个简单的测试,可以重现这个问题:

class SimpleTest < ActiveRecord::Migration
  def self.up
    add_column :assets, :test, :integer
    # the following syntax error will cause the migration to fail
    add_column :asset, :test2, :integer
  end

  def self.down
    remove_column :assets, :test
    remove_column :assets, :test2
  end
end

结果:

==  SimpleTest: 进行迁移 =====================================================
-- 添加列(:assets, :test, :integer)
   -> 0.0932秒
-- 添加列(:asset, :error)
rake中止!
发生错误,所有后续的迁移都被取消:
错误的参数数量 (2 for 3)

好的,让我们回滚:

$ rake db:rollback
==  AddLevelsToRoles: 正在还原 ===============================================
-- 移除列(:roles, :level)
   -> 0.0778秒
==  AddLevelsToRoles: 已还原 (0.0779秒) ======================================

嗯?这是我在SimpleTest之前执行的最后一次迁移,而不是失败的迁移。 (并且哦,如果迁移输出包含版本号会很好。)

所以让我们尝试运行失败的SimpleTest迁移的down命令:

$ rake db:migrate:down VERSION=20090326173033
$

没有任何反应,也没有输出。但也许它已经运行了迁移?因此让我们修复SimpleTest迁移中的语法错误,然后再尝试运行它。

$ rake db:migrate:up VERSION=20090326173033
==  SimpleTest: 进行迁移 =====================================================
-- 添加列(:assets, :test, :integer)
rake中止!
Mysql::Error: Duplicate column name 'test': ALTER TABLE `assets` ADD `test` int(11)

不行。显然migrate:down命令没有生效。它没有失败,只是没有执行。

除了手动进入数据库删除该重复表并运行测试外,没有任何摆脱重复表的方法。肯定有更好的方法。


理论上,我认为Rails可以“记住”已经应用的操作,并为您撤消可逆操作。但它没有 :( - David Cook
9个回答

84

很遗憾,MySQL不支持事务性数据库定义更改,因此您必须手动清理失败的迁移。

Rails 2.2包括针对PostgreSQL的事务性迁移。Rails 2.3包括针对SQLite的事务性迁移。

虽然这对于您当前的问题没有太大帮助,但如果在将来的项目中可以选择数据库,我建议选择支持事务性DDL的数据库,因为它可以使迁移更加愉快。

更新 - 在2017年,Rails 4.2.7和MySQL 5.7上仍然如此,由Alejandro Babio在另一个答案中报告。


2
太好了,谢谢。我将使用PGSQL进行新项目,所以知道这是一个选择很好。 - insane.dreamer
这仍然是最好的答案,所以我认为它值得获得悬赏。 - nathanvda

22

大家好,以下是实际操作步骤。我不知道以上回答所说的内容是什么意思。

  1. 找出已完成迁移的部分并将其注释掉。
  2. 另外,注释或删除导致迁移失败的部分。
  3. 再次运行迁移。现在它将完成未损坏的迁移部分,并跳过已经完成的部分。
  4. 取消在步骤1中注释掉的迁移代码。

如果想要确认是否正确,可以向下迁移再重新迁移上去。


2
我做的事情非常类似,但我用“修复迁移中出现的问题”取代了步骤2。 - Don Kirkby
2
值得强调的是最后一点——运行 bundle exec rake db:migrate:redo。它会向后和向前走一步,这样你就可以验证你最新的迁移是否全部运行通过了。每当你需要推送一个迁移以及一些代码更新时,这都是一个好习惯。 - mahemoff

20

要转到指定版本,只需使用:

rake db:migrate VERSION=(the version you want to go to)

但是如果迁移过程中出现失败,您需要先清理它。一种方法是:

  • 编辑迁移的down方法,只撤销up的部分工作
  • 迁移到先前的状态(从哪里开始)
  • 修复迁移(包括撤销对down的更改)
  • 再次尝试

谢谢。是的,我知道我可以重新迁移一直到失败的迁移,但在我有长时间迁移历史的情况下,这有时可能会有问题。理想情况下,它们应该全部执行得很好,但更常见的情况是它们在中途失败,然后就会出现更大的混乱 :-) - insane.dreamer

13

我同意尽可能使用PostgreSQL。但是,当你被迫使用MySQL时,你可以先在测试数据库上尝试迁移以避免大部分问题:

rake db:migrate RAILS_ENV=test

你可以回滚到先前的状态,然后再次尝试

rake db:schema:load RAILS_ENV=test

更像是一种解决方法而不是答案,但这是一个好主意,我以前没有想到过。 - Emily
这是一个很好的答案 - 建议使用不同的工作流程来避免问题。 - David Cook

11
在2015年,使用Rails 4.2.1和MySQL 5.7时,标准的rake操作无法修复失败的迁移,而在2009年是可以的。MySQL不支持回滚DDL语句(参见MySQL 5.7手册)。Rails对此无能为力。
此外,我们可以检查Rails是如何完成工作的:迁移取决于连接适配器对:supports_ddl_transactions?做出响应,可能会包含在事务中。在查找Rails源代码(v 4.2.1)中的此操作后,我发现只有Sqlite3PostgreSql支持事务,并且默认情况下不支持事务编辑 因此,对于原始问题的当前答案是:必须手动修复失败的MySQL迁移。

我不太理解这个答案:除了更新版本号之外,它对原始接受的答案没有任何贡献。 - nathanvda
1
非常正确,针对原始问题。对于为Andrew Grimm开始的赏金:“想知道自2009年3月提出问题以来情况是否有所改变。”这是一个当前的答案,并提供了一种检查未来任何变化的方法。 - Alejandro Babio

8

简单的方法是将所有操作都包裹在一个事务中:

class WhateverMigration < ActiveRecord::Migration

 def self.up
    ActiveRecord::Base.transaction do
...
    end
  end

  def self.down
    ActiveRecord::Base.transaction do
...
    end
  end

end

正如Luke Francl所指出的那样,“MySql的MyISAM表不支持事务” - 这就是为什么你可能会考虑普遍避免使用MySQL或至少特别避免MyISAM。
如果你使用MySQL的InnoDB,那么上述方法将完全可行。在up或down中的任何错误都将回滚。
请注意,某些类型的操作无法通过事务撤销。通常,表更改(删除表、删除或添加列等)无法回滚。

5
这并不是MyISAM或InnoDB的问题。InnoDB支持事务,但不支持事务性数据库定义(DDL)更改。在PostgreSQL中,你可以删除一个表,然后回滚(撤销)该更改! - Luke Francl
1
Luke是正确的,MySQL不支持DDL更改的事务。我必须自己考虑清理工作,例如向表添加和删除列。 - Leon Guan

1
Alejandro Babio的回答提供了目前最好的答案。
我想补充一个细节:
当“myfailedmigration”迁移失败时,它不被视为已应用,这可以通过运行“rake db:migrate:status”进行验证,它将显示类似以下的输出:
$  rake db:migrate:status
database: sample_app_dev

 Status   Migration ID    Migration Name
--------------------------------------------------
   up      20130206203115  Create users
   ...
   ...
   down    20150501173156  Test migration

在失败的迁移中执行 add_column :assets, :test, :integer 的剩余影响将需要通过数据库级别的 alter table assets drop column test; 查询来撤销。

1

我打错了一个字(在“add_column”中):

def self.up

add_column :medias, :title, :text
add_colunm :medias, :enctype, :text

end

def self.down

remove_column :medias, :title
remove_column :medias, :enctype   

end

然后你的问题是(无法撤销部分失败的迁移)。在一些失败的谷歌搜索之后,我运行了这个命令:

def self.up

remove_column :medias, :title
add_column :medias, :title, :text
add_column :medias, :enctype, :text

end

def self.down

remove_column :medias, :title
remove_column :medias, :enctype

end

正如您所看到的,我只是手动添加了更正行,然后再次将其删除,然后才进行了检查。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接