2009-09-08 10 views
14

Io corro qualche Postgres codice di migrazione bizzarro da OpenCongress e sto ottenendo questo errore:Come si esegue una migrazione senza avviare una transazione in Rails?

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

Così mi piacerebbe provare a eseguirlo senza essere avvolta da una transazione.

+0

Informi un po 'di più la migrazione è in esecuzione, il database sei usando e quale adattatore nel caso non sia quello predefinito mysql/sqlite. In questo modo penso che una risposta più utilizzabile seguirà la tua domanda. – Ariejan

+0

Mi dispiace, ho appena visto che stai usando Postgres. – Ariejan

+0

Nel caso di questa particolare migrazione ho scoperto che il comando 'VACUUM' non è realmente necessario (fa solo la garbage collection), quindi la rimozione della chiamata ha funzionato, ma sono comunque curioso di sapere come istruire Rails a funzionare migrazioni senza transazioni. – hsribei

risposta

14

ActiveRecord::Migration ha il seguente metodo privato che viene chiamato durante l'esecuzione di migrazioni:

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

Come si può vedere questo andrà a capo la migrazione in una transazione se la connessione supporta.

In ActiveRecord::ConnectionAdapters::PostgreSQLAdapter si hanno:

def supports_ddl_transactions? 
    true 
end 

SQLite versione 2.0 e al di là anche supportare le transazioni di migrazione. In ActiveRecord::ConnectionAdapters::SQLiteAdapter si hanno:

def supports_ddl_transactions? 
    sqlite_version >= '2.0.0' 
end 

Allora, per saltare le transazioni, è necessario aggirare in qualche modo questo. Qualcosa di simile potrebbe funzionare, anche se non ho provato:

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 

È quindi possibile impostare la migrazione come segue:

class SomeMigration < ActiveRecord::Migration 
    no_transaction 

    def self.up 
    # Do something 
    end 

    def self.down 
    # Do something 
    end 
end 
+0

Non riesco a farlo funzionare ... ma un'idea intelligente! – Crisfole

+0

Dato che il mio post originale ha più di tre anni, non mi aspetto necessariamente che funzioni più. –

+1

Ho provato la maggior parte delle soluzioni su questa pagina, ma nessuna ha funzionato. Questo [gist] (https://gist.github.com/olivierlacan/ba81d56d3c9e2a506216) ha funzionato per Rails 3.2. In pratica, ho terminato/riavviato una transazione con una patch 'ddl_transaction'. –

4

La risposta di cui sopra è spezzato per Rails 3 come era ddl_transaction spostato in ActiveRecord :: Migrator. Non riuscivo a trovare un modo per scimmia rattoppare quella classe, ecco una soluzione alternativa:

ho aggiunto un file sotto 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 

Poi tutto quello che dovete fare la migrazione è:

class PopulateTrees < ActiveRecord::Migration 
    include NoMigrationTransactions 
end 

Quello che fa è transazioni disattivare quando la classe di migrazione viene caricato (si spera dopo tutti i precedenti sono stati caricati e prima di qualsiasi quelli futuri vengono caricati), poi, dopo la migrazione, il ripristino qualunque transazione vecchia funzionalità ci sono stati.

+0

Qualcuno può confermare che questo funziona per le rotaie ~> 3.2.6? L'ho provato ma non ha avuto alcun effetto. –

2

Non sto dicendo che questo è il "modo giusto" per farlo, ma ciò che ha funzionato per me è stato quello di eseguire solo quella migrazione in isolamento.

rake db:migrate:up VERSION=20120801151807 

dove 20120801151807 è il timestamp della migrazione.

Apparentemente, non utilizza una transazione quando si esegue una singola migrazione.

+0

Wow, nessuna delle soluzioni di cui sopra ha funzionato, ma questo è davvero utile. Grazie! – hurshagrawal

1

Come hacky come questo è l'aggiunta di "commit"; all'inizio del mio sql ha funzionato per me, ma questo è per SQL Server, non sono sicuro se questo funziona per postgres ...

Esempio: CREATE FULLTEXT INDEX ... è illegale all'interno di una transazione utente sql-server.

quindi ...:

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

funziona bene ... Vedremo se me ne pentirò dopo.

+0

Questo è in realtà il minimo hackish di tutti i modi che ho trovato in giro :) – Flevour

10

Un modo estremamente semplice, indipendente dalla versione di Rails (2.3, 3.2, 4.0, non importa) è semplicemente aggiungere execute("commit;") all'inizio della migrazione e quindi scrivere SQL.

Questo chiude immediatamente la transazione avviata da Rails e consente di scrivere SQL raw in grado di creare le proprie transazioni. Nell'esempio seguente, utilizzo uno .update_all e una sottoselezione LIMIT per gestire l'aggiornamento di un'enorme tabella di database.

A titolo di esempio,

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

La soluzione più semplice per le vecchie applicazioni di rotaie che non possono usare il nuovo 'disable_ddl_transaction!' – Pyrce

+2

Nota che probabilmente vorrai aggiungere 'execute ("START TRANSACTION") 'dopo il ciclo while – Pyrce

56

ora c'è un metodo disable_ddl_transaction! che permette questo, ad esempio:

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

Questa dovrebbe essere la risposta accettata! –

Problemi correlati