2009-09-08 10 views
14

biegnę jakieś dziwaczne PostgreSQL kod migracji z OpenCongress i dostaję ten błąd:Jak uruchomić migrację bez uruchamiania transakcji w Railsach?

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

Więc chciałbym spróbować uruchomić go bez uzyskiwania owinięty przez transakcję.

+0

Proszę powiedzieć nieco więcej o migracji, którego używasz, baza jesteś używając i który adapter na wypadek, gdyby nie był domyślnym mysql/sqlite. W ten sposób myślę, że odpowiedź na twoje pytanie będzie bardziej odpowiednia. – Ariejan

+0

Niestety, widziałem, że używasz PostgreSQL. – Ariejan

+0

W przypadku tej konkretnej migracji dowiedziałem się, że polecenie "VACUUM" nie jest naprawdę konieczne (służy tylko do zbierania śmieci), więc usunięcie tego połączenia zadziałało, ale wciąż jestem ciekawy, jak polecić Railsom uruchamianie migracje bez transakcji. – hsribei

Odpowiedz

14

ActiveRecord::Migration ma następującą prywatną metodę, która jest wywoływana podczas uruchamiania migracje:

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

Jak widać to będzie zawijać migrację w transakcji, jeśli połączenie obsługuje.

W ActiveRecord::ConnectionAdapters::PostgreSQLAdapter masz:

def supports_ddl_transactions? 
    true 
end 

SQLite w wersji 2.0, a także obsługuje transakcji poza migracji. W ActiveRecord::ConnectionAdapters::SQLiteAdapter masz:

def supports_ddl_transactions? 
    sqlite_version >= '2.0.0' 
end 

Tak więc, aby pominąć transakcje, trzeba jakoś obejść to. Coś jak to może działać, choć nie mam go Testowany:

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 

Następnie można skonfigurować migrację następująco:

class SomeMigration < ActiveRecord::Migration 
    no_transaction 

    def self.up 
    # Do something 
    end 

    def self.down 
    # Do something 
    end 
end 
+0

Nie można tego uruchomić ... ale sprytny pomysł! – Crisfole

+0

Biorąc pod uwagę, że mój pierwotny post ma ponad trzy lata, niekoniecznie oczekiwałbym, że to zadziała. –

+1

Próbowałem większość rozwiązań na tej stronie, ale żaden nie działał. Ten [gist] (https://gist.github.com/olivierlacan/ba81d56d3c9e2a506216) pracował dla Rails 3.2. Zasadniczo zakończył/wznowił transakcję za pomocą łatki 'ddl_transaction'. –

4

Powyższa odpowiedź jest podzielony na 3 Rails jako ddl_transaction było przeniósł się do ActiveRecord :: Migrator. Nie mogłem wymyślić sposób na małpy załatać tę klasę, więc tutaj jest alternatywnym rozwiązaniem:

Dodałem plik pod 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 

Następnie wszystko co musisz zrobić w migracji jest:

class PopulateTrees < ActiveRecord::Migration 
    include NoMigrationTransactions 
end 

Co to transakcje wyłączyć gdy klasa migracji jest załadowany (mam nadzieję, że po tym jak wszystkie poprzednie te zostały załadowane i przed jakimkolwiek te są ładowane), a następnie po migracji, przywrócić cokolwiek stary transakcja możliwości nie było.

+0

Czy ktoś może potwierdzić, że działa to dla szyn ~ 3.2.6? Próbowałem, ale nie miało to żadnego skutku. –

2

Nie mówię, że to jest "właściwa droga", ale to, co sprawdziło się, to przeprowadzić tylko jedną migrację w izolacji.

rake db:migrate:up VERSION=20120801151807 

gdzie 20120801151807 jest datą migracji.

Najwyraźniej nie korzysta z transakcji podczas pojedynczej migracji.

+0

Wow, żadne z powyższych rozwiązań nie zadziałało, ale jest to bardzo pomocne. Dzięki! – hurshagrawal

1

Jak hacky, jak to jest dodanie 'commit;' na początku mojego sql pracował dla mnie, ale to dla SQL Server, nie wiem, czy to działa dla Postgres ...

Przykład: CREATE FULLTEXT INDEX ... jest nielegalne w transakcji użytkownika sql-server.

, więc ...:

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

działa dobrze ... Zobaczymy, czy później tego będę żałować.

+0

To jest faktycznie najmniej hackish ze wszystkich sposobów, które znalazłem w pobliżu :) – Flevour

10

Niezwykle prosty, niezależny od wersji Rails (2.3, 3.2, 4.0, nie ma znaczenia) sposób na to, aby po prostu dodać execute("commit;") do początku migracji, a następnie napisać SQL.

Natychmiast zamyka transakcję Rails-started i umożliwia zapisanie surowego kodu SQL, który może tworzyć własne transakcje. W poniższym przykładzie używam .update_all i podselekcję LIMIT do obsługi aktualizacji ogromnej tabeli bazy danych.

Jako przykład

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

Najczystsze obejście starych aplikacji szyny, które nie mogą używać nowej 'disable_ddl_transaction!' – Pyrce

+2

Uwaga prawdopodobnie chcesz dodać 'execute ("START TRANSACTION") 'po pętli while – Pyrce

56

Jest obecnie metoda disable_ddl_transaction! że pozwala to na przykład:

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

To powinna być zaakceptowana odpowiedź! –

Powiązane problemy