我正在运行来自OpenCongress的一些奇怪的Postgres迁移代码,我收到此错误:
RuntimeError: ERROR C25001 MVACUUM cannot run inside a transaction block
Fxact.c L2649 RPreventTransactionChain: VACUUM FULL ANALYZE;
所以我想尝试运行它而不会被事务包裹。
答案 0 :(得分:72)
现在有一种方法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
答案 1 :(得分:16)
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 :(得分:11)
一个非常简单的,与Rails版本无关(2.3,3.2,4.0,无关紧要)的方法是简单地将execute("commit;")
添加到迁移的开头,然后编写SQL。
这会立即关闭Rails-started事务,并允许您编写可以创建自己的事务的原始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
答案 3 :(得分:4)
当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
这样做是在加载迁移类时禁用事务(希望在加载所有以前的事务之后以及加载任何未来之前),然后在迁移之后,恢复任何旧的事务处理功能。
答案 4 :(得分:3)
Rails 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无法在事务块内运行。”
答案 5 :(得分:2)
我并不是说这是“正确的方法”,但对我来说有用的只是孤立地进行一次迁移。
rake db:migrate:up VERSION=20120801151807
其中20120801151807是迁移的时间戳。
显然,当您运行单个迁移时,它不会使用事务。
答案 6 :(得分:1)
这很糟糕,因为这是添加'commit;'到我的sql开始为我工作,但这是为SQL Server,不知道这是否适用于postgres ...
示例:CREATE FULLTEXT INDEX ...
在sql-server用户事务中是非法的。
所以...
execute <<-SQL
commit;
create fulltext index --...yada yada yada
SQL
工作得很好......我们会看到以后是否后悔。