Как выполнить миграцию без запуска транзакции в Rails?

Я запускаю какой-то странный код миграции Postgres из OpenCongress, и я получаю эту ошибку:

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

Поэтому я хотел бы попробовать запустить его, не обернувшись транзакцией.

Ответ 1

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

Теперь существует метод 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

Ответ 3

Чрезвычайно простой, независимый от Rails-версии (2.3, 3.2, 4.0, не имеет значения) способ просто добавить execute("commit;") в начало вашей миграции, а затем записать SQL.

Это немедленно закрывает транзакцию Rails-start и позволяет вам писать необработанный 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

Ответ 4

Вышеупомянутый ответ разбит на Rails 3, поскольку ddl_transaction был перемещен в ActiveRecord:: Migrator. Я не мог понять, как перейти к плану для обезьян в этом классе, так что это альтернативное решение:

Я добавил файл под 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

Это означает, что вы отключите транзакции при загрузке класса миграции (надеюсь, после загрузки всех предыдущих и до того, как будут загружены какие-либо будущие), затем после миграции восстановите все прежние возможности транзакций.

Ответ 5

Я не говорю, что это "правильный путь" для этого, но для меня работала только одна миграция в изоляции.

rake db:migrate:up VERSION=20120801151807

где 20120801151807 - это метка времени миграции.

По-видимому, он не использует транзакцию при выполнении одного переноса.

Ответ 6

Как взломанный, поскольку это добавляет 'commit;' к началу моего sql работал у меня, но для SQL Server не уверен, что это работает для postgres...

Пример: CREATE FULLTEXT INDEX ... является незаконным внутри транзакции пользователя sql-сервера.

так...

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

отлично работает... Посмотрим, пожалею ли я позже.