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

发布于 2024-08-04 18:05:19 字数 334 浏览 2 评论 0原文

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

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

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

I'm running some bizarre Postgres migration code from OpenCongress and I'm getting this error:

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

So I'd like to try running it without getting wrapped by a transaction.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

本宫微胖 2024-08-11 18:05:19

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

There's now a method disable_ddl_transaction! that allows this, e.g.:

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
長街聽風 2024-08-11 18:05:19

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

ActiveRecord::Migration has the following private method that gets called when running migrations:

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

As you can see this will wrap the migration in a transaction if the connection supports it.

In ActiveRecord::ConnectionAdapters::PostgreSQLAdapter you have:

def supports_ddl_transactions?
  true
end

SQLite version 2.0 and beyond also support migration transactions.
In ActiveRecord::ConnectionAdapters::SQLiteAdapter you have:

def supports_ddl_transactions?
  sqlite_version >= '2.0.0'
end

So then, to skip transactions, you need to somehow circumvent this.
Something like this might work, though I haven't tested it:

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

You could then set up your migration as follows:

class SomeMigration < ActiveRecord::Migration
  no_transaction

  def self.up
    # Do something
  end

  def self.down
    # Do something
  end
end
飘然心甜 2024-08-11 18:05:19

一个非常简单的、与 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

An extremely simple, Rails-version-independent (2.3, 3.2, 4.0, doesn't matter) way about this is to simply add execute("commit;") to the beginning of your migration, and then write SQL.

This immediately closes the Rails-started transaction, and allows you to write raw SQL that can create its own transactions. In the below example, I use an .update_all and a subselect LIMIT to handle updating a huge database table.

As an example,

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
泪痕残 2024-08-11 18:05:19

轨道 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 无法在事务块内运行。”

Rails 4 +
There is a method disable_ddl_transaction!, you can use it in your migration file like below.

class AddIndexToTable < ActiveRecord::Migration
  disable_ddl_transaction!

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

Below Rails 4

Like some of answers above, there is a simple hack, you can commit the transaction and then after your migration has completed you again the begin the transaction, like below

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

This can be helpful in case where we cant create/drop index concurrently, as these cannot be executed in a transaction.
If you try you will get error "PG::ActiveSqlTransaction: ERROR: DROP INDEX CONCURRENTLY cannot run inside a transaction block."

时光倒影 2024-08-11 18:05:19

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

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

The above answer is broken for Rails 3 as ddl_transaction was moved into ActiveRecord::Migrator. I could not figure out a way to monkey patch that class, so here is an alternate solution:

I added a file under 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

Then all you have to do in your migration is:

class PopulateTrees < ActiveRecord::Migration
  include NoMigrationTransactions
end

What this does is disable transactions when the migration class is loaded (hopefully after all previous ones were loaded and before any future ones are loaded), then after the migration, restore whatever old transaction capabilities there were.

抠脚大汉 2024-08-11 18:05:19

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

rake db:migrate:up VERSION=20120801151807

其中 20120801151807 是迁移的时间戳。

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

I'm not saying this is the "right way" to do it, but what worked for me was to run just that one migration in isolation.

rake db:migrate:up VERSION=20120801151807

where 20120801151807 is the timestamp of the migration.

Apparently, it doesn't use a transaction when you run a single migration.

说谎友 2024-08-11 18:05:19

添加“commit;”就像黑客一样到我的sql的开头对我有用,但那是针对SQL Server的,不确定这是否适用于postgres...

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

所以...:

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

效果很好...我们会看看我以后是否会后悔。

As hacky as this is adding 'commit;' to the beginning of my sql worked for me, but that's for SQL Server, not sure if this works for postgres...

Example: CREATE FULLTEXT INDEX ... is illegal inside a sql-server user transaction.

so...:

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

works fine... We'll see if I regret it later.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文