2009-09-08 8 views
14

estoy ejecutando un código de migración Postgres extraña de OpenCongress y estoy recibiendo este error:¿Cómo ejecuto una migración sin iniciar una transacción en Rails?

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

así que me gustaría intentar ejecutarlo sin ser envuelto por una transacción.

+0

Informe a un poco más sobre la migración que se está ejecutando, la base de datos que está usando y qué adaptador en caso de que no sea el predeterminado mysql/sqlite. De esa manera, creo que una respuesta más útil seguirá su pregunta. – Ariejan

+0

Lo siento, acabo de ver que estás usando Postgres. – Ariejan

+0

En el caso de esta migración en particular, descubrí que el comando 'VACUUM' no es realmente necesario (solo hace la recolección de basura), por lo que eliminar esa llamada funcionó, pero todavía tengo curiosidad por saber cómo instruir a Rails para ejecutar migraciones sin transacciones. – hsribei

Respuesta

14

ActiveRecord::Migration tiene el siguiente método privado que es llamada cuando se ejecuta migraciones:

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

Como se puede ver esto va a envolver la migración en una transacción si la conexión es compatible con ella.

En ActiveRecord::ConnectionAdapters::PostgreSQLAdapter tiene:

def supports_ddl_transactions? 
    true 
end 

SQLite versión 2.0 y más allá también apoyan las operaciones de migración. En ActiveRecord::ConnectionAdapters::SQLiteAdapter tiene:

def supports_ddl_transactions? 
    sqlite_version >= '2.0.0' 
end 

Así pues, para saltar transacciones, tiene que sortear de alguna manera esto. Algo como esto podría funcionar, aunque no lo he probado:

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 

A continuación, puede configurar la migración de la siguiente manera:

class SomeMigration < ActiveRecord::Migration 
    no_transaction 

    def self.up 
    # Do something 
    end 

    def self.down 
    # Do something 
    end 
end 
+0

No se pudo hacer funcionar esto ... ¡pero idea inteligente! – Crisfole

+0

Dado que mi publicación original tiene más de tres años, no necesariamente esperaría que esto siga funcionando. –

+1

He intentado la mayoría de las soluciones en esta página, pero ninguna ha funcionado. Esta [esencia] (https://gist.github.com/olivierlacan/ba81d56d3c9e2a506216) funcionó para Rails 3.2. Básicamente terminó/reinició una transacción con un parche 'ddl_transaction'. –

4

La respuesta anterior se rompe para Rails 3 como era ddl_transaction movido a ActiveRecord :: Migrator. No pude encontrar una manera de mono parchear esa clase, asi que aquí hay una solución alternativa:

he añadido un archivo en 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 

Entonces todo lo que tiene que hacer en su migración es:

class PopulateTrees < ActiveRecord::Migration 
    include NoMigrationTransactions 
end 

lo que esto hace es desactivar las transacciones cuando se carga la clase de migración (esperemos que después de todos los anteriores se cargaron y se cargan antes de cualquier los futuros), a continuación, después de la migración, restaurar lo capacidades de transacción de edad había.

+0

¿Alguien puede confirmar que esto funciona para rieles ~> 3.2.6? Lo intenté pero no tuvo efecto. –

2

No digo que esta sea la "manera correcta" de hacerlo, pero lo que funcionó para mí fue ejecutar solo esa migración de forma aislada.

rake db:migrate:up VERSION=20120801151807 

20120801151807 donde es la marca de tiempo de la migración.

Aparentemente, no usa una transacción cuando ejecuta una sola migración.

+0

Guau, ninguna de las soluciones anteriores funcionó, pero esto es realmente útil. ¡Gracias! – hurshagrawal

1

Tan hacky como esto agrega 'commit;' al principio de mi sql trabajado para mí, pero eso es para SQL Server, no estoy seguro de si esto funciona para postgres ...

Ejemplo: CREATE FULLTEXT INDEX ... es ilegal dentro de una transacción de usuario sql-server.

tan ...:

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

funciona bien ... Veremos si me arrepiento más tarde.

+0

Esto es realmente el menos hackish de todas las formas que he encontrado alrededor :) – Flevour

10

Una manera muy simple, Rails-version-independent (2.3, 3.2, 4.0, no importa) sobre esto es simplemente agregar execute("commit;") al comienzo de su migración, y luego escribir SQL.

Esto cierra inmediatamente la transacción Rails-started, y le permite escribir SQL sin formato que puede crear sus propias transacciones. En el siguiente ejemplo, uso un .update_all y una subselección LIMIT para gestionar la actualización de una gran tabla de base de datos.

A modo de ejemplo,

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

Más limpio de todas las aplicaciones de rieles antiguos que no pueden usar el nuevo 'disable_ddl_transaction!' – Pyrce

+2

Tenga en cuenta que probablemente quiera agregar 'execute ("START TRANSACTION") 'después del ciclo while – Pyrce

56

Ahora hay un método que permite disable_ddl_transaction! esto, por ejemplo:

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

¡Esta debería ser la respuesta aceptada! –

Cuestiones relacionadas