如何在不启动Rails中的事务的情况下运行迁移?

时间:2009-09-08 18:50:23

标签: ruby-on-rails database postgresql transactions migration

我正在运行来自OpenCongress的一些奇怪的Postgres迁移代码,我收到此错误:

RuntimeError: ERROR     C25001  MVACUUM cannot run inside a transaction block
Fxact.c  L2649   RPreventTransactionChain: VACUUM FULL ANALYZE;

所以我想尝试运行它而不会被事务包裹。

7 个答案:

答案 0 :(得分:72)

现在有一种方法disable_ddl_transaction!允许这样做,例如:

class AddIndexesToTablesBasedOnUsage < ActiveRecord::Migration
  disable_ddl_transaction!
  def up
    execute %{
      CREATE INDEX CONCURRENTLY index_reservations_subscription_id ON reservations (subscription_id);
    }
  end
  def down
    execute %{DROP INDEX index_reservations_subscription_id}
  end
end

答案 1 :(得分:16)

ActiveRecord::Migration具有以下在运行迁移时调用的私有方法:

def ddl_transaction(&block)
  if Base.connection.supports_ddl_transactions?
    Base.transaction { block.call }
  else
    block.call
  end
end

正如您所看到的,如果连接支持迁移,这将包装在事务中。

ActiveRecord::ConnectionAdapters::PostgreSQLAdapter中你有:

def supports_ddl_transactions?
  true
end

SQLite 2.0及更高版本也支持迁移事务。 在ActiveRecord::ConnectionAdapters::SQLiteAdapter中你有:

def supports_ddl_transactions?
  sqlite_version >= '2.0.0'
end

那么,为了跳过交易,你需​​要以某种方式绕过这个。 这样的事情可能有用,虽然我还没有测试过:

class ActiveRecord::Migration
  class << self
    def no_transaction
      @no_transaction = true
    end

    def no_transaction?
      @no_transaction == true
    end
  end

  private

    def ddl_transaction(&block)
      if Base.connection.supports_ddl_transactions? && !self.class.no_transaction?
        Base.transaction { block.call }
      else
        block.call
      end
    end
end

然后,您可以按如下方式设置迁移:

class SomeMigration < ActiveRecord::Migration
  no_transaction

  def self.up
    # Do something
  end

  def self.down
    # Do something
  end
end

答案 2 :(得分:11)

一个非常简单的,与Rails版本无关(2.3,3.2,4.0,无关紧要)的方法是简单地将execute("commit;")添加到迁移的开头,然后编写SQL。

这会立即关闭Rails-started事务,并允许您编写可以创建自己的事务的原始SQL。在下面的示例中,我使用.update_all和子选择LIMIT来处理更新庞大的数据库表。

举个例子,

class ChangeDefaultTabIdOfZeroToNilOnUsers < ActiveRecord::Migration
  def self.up
    execute("commit;")
    while User.find_by_default_tab_id(0).present? do
      User.update_all %{default_tab_id = NULL}, %{id IN (
        SELECT id FROM users WHERE default_tab_id = 0 LIMIT 1000
      )}.squish!
    end
  end

  def self.down
    raise ActiveRecord::IrreversibleMigration
  end
end

答案 3 :(得分:4)

当ddl_transaction移入ActiveRecord :: Migrator时,Rails 3的上述答案已被破解。我无法想出一种方法来修补那个类,所以这里是另一种解决方案:

我在lib /

下添加了一个文件
module NoMigrationTransactions
  def self.included(base)                                                                                                                  
    base.class_eval do
      alias_method :old_migrate, :migrate

      say "Disabling transactions"

      @@no_transaction = true
      # Force no transactions
      ActiveRecord::Base.connection.instance_eval do
        alias :old_ddl :supports_ddl_transactions?

        def supports_ddl_transactions?
          false
        end
      end

      def migrate(*args)
        old_migrate(*args)

        # Restore
        if @@no_transaction
          say "Restoring transactions"
          ActiveRecord::Base.connection.instance_eval do
            alias :supports_ddl_transactions? :old_ddl
          end
        end
      end
    end
  end
end

然后您在迁移中所要做的就是:

class PopulateTrees < ActiveRecord::Migration
  include NoMigrationTransactions
end

这样做是在加载迁移类时禁用事务(希望在加载所有以前的事务之后以及加载任何未来之前),然后在迁移之后,恢复任何旧的事务处理功能。

答案 4 :(得分:3)

Rails 4 + 有一个方法disable_ddl_transaction !,您可以在迁移文件中使用它,如下所示。

class AddIndexToTable < ActiveRecord::Migration
  disable_ddl_transaction!

  def change
    add_index :table, :column, algorithm: :concurrently
  end
end

在Rails 4下面

像上面的一些答案一样,有一个简单的黑客,您可以提交交易,然后在您的迁移完成后再次开始交易,如下所示

class AddIndexToTable < ActiveRecord::Migration
  def change
    execute "COMMIT;"

    add_index :table, :column, algorithm: :concurrently

    # start a new transaction after the migration finishes successfully
    execute "BEGIN TRANSACTION;"
  end
end

如果我们无法同时创建/删除索引,这可能会有所帮助,因为这些不能在事务中执行。 如果你尝试,你会收到错误“PG :: ActiveSqlTransaction:错误:DROP INDEX CONCURRENTLY无法在事务块内运行。”

答案 5 :(得分:2)

我并不是说这是“正确的方法”,但对我来说有用的只是孤立地进行一次迁移。

rake db:migrate:up VERSION=20120801151807

其中20120801151807是迁移的时间戳。

显然,当您运行单个迁移时,它不会使用事务。

答案 6 :(得分:1)

这很糟糕,因为这是添加'commit;'到我的sql开始为我工作,但这是为SQL Server,不知道这是否适用于postgres ...

示例:CREATE FULLTEXT INDEX ...在sql-server用户事务中是非法的。

所以...

execute <<-SQL
    commit;
    create fulltext index --...yada yada yada
SQL

工作得很好......我们会看到以后是否后悔。