【问题标题】:How do I run a migration without starting a transaction in Rails?如何在 Rails 中不启动事务的情况下运行迁移?
【发布时间】:2009-09-08 18:50:23
【问题描述】:

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

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

所以我想尝试在不被事务包裹的情况下运行它。

【问题讨论】:

  • 请详细说明您正在运行的迁移、您正在使用的数据库以及哪个适配器,以防它不是默认的 mysql/sqlite 适配器。这样我认为你的问题会得到一个更有用的答案。
  • 抱歉,刚刚看到您使用的是 Postgres。
  • 在这个特定迁移的情况下,我发现VACUUM 命令并不是真正必要的(它只进行垃圾收集),因此删除该调用有效,但我仍然很好奇知道如何指示 Rails 在没有事务的情况下运行迁移。

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


【解决方案1】:

现在有一个方法 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

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

    • 鉴于我的原始帖子已经超过三年了,我不一定希望这能再工作了。
    • 我已经尝试了此页面上的大多数解决方案,但都没有奏效。这个gist 确实适用于 Rails 3.2。基本上使用ddl_transaction 补丁结束/重新启动事务。
    • 请注意,引用的要点有一个错误——ddl_transaction 的覆盖无效,因为该方法是私有的。
    【解决方案3】:

    一种非常简单的、独立于 Rails 版本(2.3、3.2、4.0,没关系)的方法是简单地将 execute("commit;") 添加到迁移的开头,然后编写 SQL。

    这会立即关闭 Rails 启动的事务,并允许您编写可以创建自己的事务的原始 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
    

    【讨论】:

    • 对无法使用新 disable_ddl_transaction! 的旧 Rails 应用程序最干净的解决方法
    • 请注意,您可能希望在 while 循环之后添加 execute("START TRANSACTION")
    【解决方案4】:

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

    class AddIndexToTable < ActiveRecord::Migration
      disable_ddl_transaction!
    
      def change
        add_index :table, :column, algorithm: :concurrently
      end
    end
    

    铁轨下 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: ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block.”

    【讨论】:

      【解决方案5】:

      由于 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
      

      它的作用是在加载迁移类时禁用事务(希望在加载所有先前的之后和加载任何未来的之前),然后在迁移之后恢复任何旧的事务功能。

      【讨论】:

      • 谁能确认这适用于rails ~> 3.2.6?我试过了,但没有效果。
      【解决方案6】:

      我并不是说这是执行此操作的“正确方法”,但对我有用的是单独运行那一次迁移。

      rake db:migrate:up VERSION=20120801151807
      

      其中 20120801151807 是迁移的时间戳。

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

      【讨论】:

      • 哇,以上解决方案都不起作用,但这真的很有帮助。谢谢!
      【解决方案7】:

      添加 'commit;' 就像 hacky到我的 sql 开头为我工作,但那是 SQL Server,不确定这是否适用于 postgres...

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

      所以...:

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

      工作正常...我们会看看我以后会不会后悔。

      【讨论】:

      • 这实际上是我发现的所有方法中最不老套的:)
      • hum 几乎但是现在我得到了 ALTER TYPE ... ADD 不能从 postgres 的函数或多命令字符串执行
      猜你喜欢
      • 1970-01-01
      • 2021-10-22
      • 2018-09-07
      • 2020-02-23
      • 2015-11-06
      • 1970-01-01
      • 2013-06-03
      • 1970-01-01
      • 2013-06-15
      相关资源
      最近更新 更多