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

Hel*_*iro 20 database migration postgresql transactions ruby-on-rails

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

RuntimeError: ERROR     C25001  MVACUUM cannot run inside a transaction block
Fxact.c  L2649   RPreventTransactionChain: VACUUM FULL ANALYZE;
Run Code Online (Sandbox Code Playgroud)

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

dav*_*000 77

现在有一种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
Run Code Online (Sandbox Code Playgroud)


Pet*_*net 16

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

def ddl_transaction(&block)
  if Base.connection.supports_ddl_transactions?
    Base.transaction { block.call }
  else
    block.call
  end
end
Run Code Online (Sandbox Code Playgroud)

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

ActiveRecord::ConnectionAdapters::PostgreSQLAdapter你有:

def supports_ddl_transactions?
  true
end
Run Code Online (Sandbox Code Playgroud)

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

def supports_ddl_transactions?
  sqlite_version >= '2.0.0'
end
Run Code Online (Sandbox Code Playgroud)

那么,要跳过事务,你需要以某种方式绕过这个.这样的东西可能有用,虽然我还没有测试过:

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
Run Code Online (Sandbox Code Playgroud)

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

class SomeMigration < ActiveRecord::Migration
  no_transaction

  def self.up
    # Do something
  end

  def self.down
    # Do something
  end
end
Run Code Online (Sandbox Code Playgroud)


pro*_*ons 11

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

这会立即关闭Rails-started事务,并允许您编写可以创建自己的事务的原始SQL.在下面的示例中,我使用an .update_all和subselect 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
Run Code Online (Sandbox Code Playgroud)

  • 注意你可能想在while循环后添加`execute("START TRANSACTION")` (2认同)

Yas*_*gar 8

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

class AddIndexToTable < ActiveRecord::Migration
  disable_ddl_transaction!

  def change
    add_index :table, :column, algorithm: :concurrently
  end
end
Run Code Online (Sandbox Code Playgroud)

下轨 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
Run Code Online (Sandbox Code Playgroud)

这在我们不能同时创建/删除索引的情况下很有帮助,因为这些不能在事务中执行。如果您尝试,您将收到错误“PG::ActiveSqlTransaction: ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block”。