2009-09-08 189 views
14

,我从OpenCongress一些奇怪的Postgres迁移代码和我得到这个错误:如何在Rails中启动事务而不启动事务?

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

所以我想尝试没有通过交易得到裹运行它。

+0

请告诉更多的关于你正在运行的迁移,你的数据库如果它不是默认的mysql/sqlite的话,使用哪个适配器。这样我认为一个更有用的答案会跟随你的问题。 – Ariejan 2009-09-09 14:36:56

+0

对不起,只是看到你在使用Postgres。 – Ariejan 2009-09-09 14:37:27

+0

在这个特定迁移的情况下,我发现了'VACUUM'命令是不是真的有必要(它只做垃圾收集),以便消除这一呼吁的工作,但我还是想知道如何指导Rails的运行没有交易的迁移。 – hsribei 2009-09-12 15:19:02

回答

14

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 
+0

无法得到这个工作......但聪明的想法! – Crisfole 2012-11-29 17:07:06

+0

鉴于我的原始帖子已超过三年,我不一定会期望这个工作了。 – 2013-01-29 21:00:41

+1

我已经尝试了此页面上的大部分解决方案,但都没有成功。这[要点](https://gist.github.com/olivierlacan/ba81d56d3c9e2a506216)没有为Rails 3.2的工作。基本上通过'ddl_transaction'补丁结束/重新启动一个事务。 – 2015-02-16 22:07:03

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 

这样做是禁止交易时迁移类被加载(以前的不同加载希望后任何将来的加载之前),那么迁移之后,恢复旧的任何交易能力有。

+0

任何人都可以确认这适用于rails〜> 3.2.6吗?我试过了,但没有效果。 – 2012-10-28 12:40:20

2

我并不是说这是做到这一点的“正确方法”,但对我而言,唯一的办法就是单独运行一次迁移。

rake db:migrate:up VERSION=20120801151807 

其中20120801151807是迁移的时间戳。

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

+0

哇,上述解决方案都没有工作,但这真的很有帮助。谢谢! – hurshagrawal 2012-12-21 16:42:37

1

由于这是hacky,因此增加了'commit;'到我的sql的开始为我工作,但这是为SQL Server,不知道这是否适用于postgres ...

例如:CREATE FULLTEXT INDEX ...是sql-server用户事务内部是非法的。

so ...:

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

工作正常......我们稍后会看到我是否后悔。

+0

这实际上是最hackish的所有我身边发现无法使用新的'disable_ddl_transaction方式:) – Flevour 2013-09-25 13:25:30

10

一个非常简单的,独立于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 
+0

最干净的变通旧的Rails应用程序!' – Pyrce 2015-01-26 19:09:52

+2

注意你可能要添加'执行( “START TRANSACTION”)'while while循环后 – Pyrce 2015-01-26 19:41:13

56

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

这应该是公认的答案! – 2015-06-19 13:22:45

相关问题