2009-03-26 6 views
77

Come si ripristina una migrazione di binari non riuscita? Mi aspetto che il rake db:rollback annullerebbe la migrazione fallita, ma no, ripristina la precedente migrazione (la migrazione fallita meno uno). E rake db:migrate:down VERSION=myfailedmigration non funziona neanche. Mi sono imbattuto in questo alcune volte ed è molto frustrante. Ecco un semplice test che ho fatto di riprodurre il problema:Rollback di una migrazione di Rails non riuscita

class SimpleTest < ActiveRecord::Migration 
    def self.up 
    add_column :assets, :test, :integer 
    # the following syntax error will cause the migration to fail 
    add_column :asset, :test2, :integer 
    end 

    def self.down 
    remove_column :assets, :test 
    remove_column :assets, :test2 
    end 
end 

risultato:

 
== SimpleTest: migrating ===================================================== 
-- add_column(:assets, :test, :integer) 
    -> 0.0932s 
-- add_column(:asset, :error) 
rake aborted! 
An error has occurred, all later migrations canceled: 

wrong number of arguments (2 for 3) 

ok, lascia rotolare indietro:

 
$ rake db:rollback 
== AddLevelsToRoles: reverting =============================================== 
-- remove_column(:roles, :level) 
    -> 0.0778s 
== AddLevelsToRoles: reverted (0.0779s) ====================================== 

eh? quella era la mia ultima migrazione prima di SimpleTest, non la migrazione fallita. (E, oh, sarebbe bello se l'uscita di migrazione incluso il numero di versione.)

Quindi, consente di provare a eseguire il basso per la SimpleTest migrazione non riuscita:

 
$ rake db:migrate:down VERSION=20090326173033 
$ 

Non succede nulla, e nessuna uscita neanche. Ma forse ha gestito la migrazione comunque? Pertanto, risolviamo l'errore di sintassi nella migrazione di SimpleTest e proviamo a eseguirlo di nuovo.

 
$ rake db:migrate:up VERSION=20090326173033 
== SimpleTest: migrating ===================================================== 
-- add_column(:assets, :test, :integer) 
rake aborted! 
Mysql::Error: Duplicate column name 'test': ALTER TABLE `assets` ADD `test` int(11) 

No. Ovviamente la migrazione: down non ha funzionato. Non sta fallendo, non è solo in esecuzione.

Nessun modo per sbarazzarsi di quella tabella duplicata se non quella di accedere manualmente al database e rimuoverlo, quindi eseguire il test. Deve esserci un modo migliore di quello.

risposta

73

Purtroppo, è necessario pulire manualmente migrazioni falliti per MySQL. MySQL non supporta le modifiche alle definizioni del database transazionale.

Rails 2.2 include migrazioni transazionali per PostgreSQL. Rails 2.3 include migrazioni transazionali per SQLite.

Questo non ti aiuta veramente per il tuo problema in questo momento, ma se hai una scelta di database su progetti futuri, ti consiglio di usarne uno con supporto per DDL transazionale perché rende le migrazioni molto più piacevoli.

Aggiornamento - questo è ancora vero nel 2017, su Rails 4.2.7 e MySQL 5.7, riportato da Alejandro Babio in un'altra risposta qui.

+1

Eccellente, grazie. Farò nuovi progetti con PGSQL quindi è bene sapere che è un'opzione. –

+0

Questa è ancora la migliore risposta, quindi questo merita la bozza di imho. – nathanvda

20

per andare a una versione specificata basta usare:

rake db:migrate VERSION=(the version you want to go to) 

Ma se una migrazione non riesce parzialmente, dovrete per ripulirlo prima. Un modo potrebbe essere:

  • modificare il metodo della migrazione down per annullare solo la parte del up che ha funzionato
  • migrare torna allo stato precedente (punto di partenza)
  • fissare la migrazione (compresi annullare le modifiche apportate al down)
  • riprovare
+0

Grazie. Sì, so che potrei ri-migrare fino alla migrazione fallita, ma nei casi in cui ho una lunga storia di migrazioni a volte può essere problematico. Idealmente dovrebbero eseguire tutto bene, ma il più delle volte li ho fatti fallire in parte, e poi c'è un gran casino :-) –

9

Il modo più semplice per farlo è quello di avvolgere tutte le azioni in una transazione:

class WhateverMigration < ActiveRecord::Migration 

def self.up 
    ActiveRecord::Base.transaction do 
... 
    end 
    end 

    def self.down 
    ActiveRecord::Base.transaction do 
... 
    end 
    end 

end 

Come osservato Luca Francl, "MySQL [s 'tabelle MyISAM non] transazioni di supporto" - ed è per questo che potresti considerare di evitare MySQL in generale o almeno MyISAM in particolare.

Se si utilizza InnoDB di MySQL, quanto sopra funzionerà correttamente. Eventuali errori in alto o in basso verranno ripristinati.

ESSERE CONSAPEVOLE alcuni tipi di azioni non possono essere ripristinate tramite transazioni. In genere, non è possibile eseguire il rollback delle modifiche alla tabella (eliminazione di una tabella, rimozione o aggiunta di colonne, ecc.).

+5

Non è una questione di MyISAM o InnoDB. InnoDB supporta le transazioni, ma non supporta le modifiche alla definizione del database transazionale (DDL). In PostgreSQL puoi rilasciare una tabella e quindi ripristinare quella modifica! –

+1

Luke è corretto, mysql non supporta la transazione sulle modifiche DDL. Devo considerare la pulizia da solo, come aggiungere e rimuovere una colonna dai tavoli. –

1

ho avuto un errore di battitura (in "add_column"):

def self.up

add_column :medias, :title, :text 
add_colunm :medias, :enctype, :text 

fine

def

self.down

remove_column :medias, :title 
remove_column :medias, :enctype 

fine

e allora il vostro problema (non può annullare la migrazione in parte fallito). dopo un po 'fallito googling mi sono imbattuto in questo modo:

def self.up

remove_column :medias, :title 
add_column :medias, :title, :text 
add_column :medias, :enctype, :text 

fine

def self.down

remove_column :medias, :title 
remove_column :medias, :enctype 

fine

come si posso vedere io Ho appena aggiunto la linea di correzione a mano e poi l'ho rimosso di nuovo, prima di verificarlo.

12

Sono d'accordo che dovresti usare PostgreSQL quando possibile. Tuttavia, quando si è bloccato con MySQL, è possibile evitare la maggior parte di questi problemi, cercando la migrazione sul database di test prima:

rake db:migrate RAILS_ENV=test 

È possibile ripristinare lo stato precedente e riprovare con

rake db:schema:load RAILS_ENV=test 
+0

Più di una soluzione alternativa a una risposta, ma questa è una buona idea che non mi era mai venuta in mente prima. – Emily

18

OK, gente, ecco come lo si fa effettivamente. Non so di cosa parlino le risposte di cui sopra.

  1. Calcolare quale parte della migrazione su ha funzionato. Commenta quelli fuori.
  2. Inoltre commenta/rimuove la parte della migrazione che si è interrotta.
  3. Eseguire di nuovo la migrazione. Ora completerà le parti non rotte della migrazione, saltando le parti che sono già state eseguite.
  4. Decommenta i bit della migrazione si commentate nel passaggio 1.

È possibile migrare verso il basso e il backup di nuovo se si desidera verificare che hai subito.

+2

Faccio qualcosa di molto simile, ma sostituisco il passaggio 2 con "Correggi la parte della migrazione che si è rotta". –

+1

Vale la pena sottolineare l'ultimo punto: esegui 'bundle exec rake db: migrate: redo'. Andrà indietro di un passo e un passo avanti, in modo da poter verificare che l'ultima migrazione venga eseguita fino in fondo. Questa è una buona pratica ogni volta che devi inviare una migrazione insieme ad alcuni aggiornamenti del codice. – mahemoff

8

a 2015 con Rails 4.2.1 e MySQL 5.7, una migrazione fallito non può essere fissata con le azioni di spoglia standard che forniscono rotaie, come era 2009.

MySql non supporta smantellamento delle statments DDL (a MySQL 5.7 Manual). E Rails non può fare nulla con quello.

Inoltre, possiamo verificare come Rails sta facendo il lavoro: una migrazione è wrapped in a transaction a seconda di come la scheda di connessione risponde a :supports_ddl_transactions?. Dopo una ricerca di questa azione sull'origine dei binari (v 4.2.1), ho trovato che solo Sqlite3 e PostgreSql supporta le transazioni, e per default non è supportato.

Modifica Quindi la risposta corrente alla domanda originale: una migrazione MySQL fallita deve essere risolta manualmente.

+0

Non capisco questa risposta: tranne che dall'aggiornamento dei numeri di versione non aggiunge nulla alla risposta originale accettata. – nathanvda

+1

Molto vero, per la domanda originale. Per la taglia iniziata per Andrew Grimm: "Vuoi sapere se la situazione è cambiata da quando la domanda è stata posta nel marzo 2009." È una risposta corrente e fornisce un metodo per verificare eventuali cambiamenti in futuro. –

1

La risposta sopra riportata di Alejandro Babio fornisce la migliore risposta attuale.

Un ulteriore dettaglio voglio aggiungere:

Quando la migrazione myfailedmigration fallisce, non è considerato come applicato, e ciò può essere verificato eseguendo rake db:migrate:status, che mostrano output simile al seguente:

$ rake db:migrate:status 
database: sample_app_dev 

Status Migration ID Migration Name 
-------------------------------------------------- 
    up  20130206203115 Create users 
    ... 
    ... 
    down 20150501173156 Test migration 

L'effetto residuo di add_column :assets, :test, :integer in esecuzione sulla migrazione non riuscita dovrà essere invertito a livello di database con una query alter table assets drop column test;.