it-swarm-eu.dev

SQLite3 :: BusyException

Ausführen einer Rails-Site mit SQLite3.

Ungefähr einmal alle 500 Anfragen oder so bekomme ich eine

ActiveRecord :: StatementInvalid (SQLite3 :: BusyException: Datenbank ist gesperrt: ...

Wie kann ich das Problem beheben, das für meinen Code minimal invasiv ist?

Ich verwende momentan SQLLite, weil Sie die Datenbank in der Quellcodeverwaltung speichern können, wodurch das Sichern natürlich wird und Sie Änderungen sehr schnell übertragen können. Es ist jedoch offensichtlich nicht wirklich für den gleichzeitigen Zugriff eingerichtet. Ich werde morgen früh zu MySQL migrieren.

35
Shalmanese

Standardmäßig wird sqlite sofort mit einem blockierten, ausgelasteten Fehler zurückgegeben, wenn die Datenbank ausgelastet und gesperrt ist. Sie können verlangen, dass es wartet und es noch eine Weile versucht, bevor Sie aufgeben. Dies behebt normalerweise das Problem, es sei denn, Sie haben Tausende von Threads, die auf Ihre Datenbank zugreifen, wenn ich damit einverstanden bin, dass sqlite unangemessen wäre.

 // SQLite so einstellen, dass es bis zu 100 ms wartet und es erneut versucht, wenn die Datenbank gesperrt ist 
 sqlite3_busy_timeout (db, 100); 
8
ravenspoint

Sie haben erwähnt, dass dies eine Rails-Site ist. Mit Rails können Sie das SQLite-Wiederholungszeitlimit in Ihrer database.yml-Konfigurationsdatei festlegen:

production:
  adapter: sqlite3
  database: db/mysite_prod.sqlite3
  timeout: 10000

Der Zeitlimitwert wird in Millisekunden angegeben. Durch Erhöhen auf 10 oder 15 Sekunden wird die Anzahl der in Ihrem Protokoll angezeigten BusyExceptions verringert.

Dies ist jedoch nur eine vorübergehende Lösung. Wenn Ihre Site echte Parallelität benötigt, müssen Sie auf eine andere Datenbank-Engine migrieren.

53
Rifkin Habsburg

All diese Dinge sind wahr, aber sie beantworten nicht die Frage, die wahrscheinlich ist: Warum löst meine Rails-App gelegentlich eine SQLite3 :: BusyException in der Produktion aus?

@Shalmanese: Wie sieht die Hosting-Umgebung für die Produktion aus? Ist es auf einem gemeinsam genutzten Host? Befindet sich das Verzeichnis mit der SQLite-Datenbank auf einer NFS-Freigabe? (Wahrscheinlich auf einem gemeinsam genutzten Host).

Dieses Problem hat wahrscheinlich mit dem Phänomen der Dateisperrung mit NFS-Freigaben und der fehlenden Parallelität von SQLite zu tun.

3
ybakos

Nur für das Protokoll. In einer Anwendung mit Rails 2.3.8 haben wir herausgefunden, dass Rails die von Rifkin Habsburg vorgeschlagene "Timeout" -Option ignoriert.

Nach einigen weiteren Untersuchungen haben wir einen möglicherweise verwandten Fehler in Rails dev gefunden: http://dev.rubyonrails.org/ticket/8811 . Und nach einigen weiteren Untersuchungen fanden wir die Lösung (getestet mit Rails 2.3.8):

Bearbeiten Sie diese ActiveRecord-Datei: activerecord-2.3.8/lib/active_record/connection_adapters/sqlite_adapter.rb

Ersetze dies:

  def begin_db_transaction #:nodoc:
    catch_schema_changes { @connection.transaction }
  end

mit

  def begin_db_transaction #:nodoc:
    catch_schema_changes { @connection.transaction(:immediate) }
  end

Und das ist alles! Wir haben keinen Leistungsabfall bemerkt und jetzt unterstützt die App viele weitere Petitionen, ohne zu brechen (sie wartet auf das Timeout). SQLite ist schön!

2
Ignacio Huerta
bundle exec rake db:reset

Es hat bei mir funktioniert, es wird zurückgesetzt und die ausstehende Migration angezeigt.

Quelle: dieser Link

- Open the database
db = sqlite3.open("filename")

-- Ten attempts are made to proceed, if the database is locked
function my_busy_handler(attempts_made)
  if attempts_made < 10 then
    return true
  else
    return false
  end
end

-- Set the new busy handler
db:set_busy_handler(my_busy_handler)

-- Use the database
db:exec(...)
1
Brian R. Bondy

Ich hatte ein ähnliches problem mit rake db: migrate. Das Problem war, dass sich das Arbeitsverzeichnis auf einer SMB Freigabe befand. Ich habe das Problem behoben, indem ich den Ordner auf meinen lokalen Computer kopiert habe.

1
meredrica

In Sqlite können andere Prozesse warten, bis der aktuelle beendet ist.

Ich verwende diese Leitung, um eine Verbindung herzustellen, wenn ich weiß, dass möglicherweise mehrere Prozesse versuchen, auf die SQLite-Datenbank zuzugreifen:

conn = sqlite3.connect ('filename', isolation_level = 'exclusive' )

Gemäß der Python SQLite-Dokumentation:

Sie können steuern, welche Art von BEGIN-Anweisungen pysqlite implizit (oder gar nicht) ausführt, indem Sie den Parameter isolation_level an den Aufruf connect () oder die Eigenschaft isolation_level von connections übergeben.

1
alfredodeza

Die meisten Antworten sind für Rails und nicht für Raw Ruby, und OPs Frage IS für Rails, was in Ordnung ist. :)

Daher möchte ich diese Lösung hier belassen, falls ein unbehandelter Ruby-Benutzer dieses Problem hat und keine yml-Konfiguration verwendet.

Nachdem Sie die Verbindung instanziiert haben, können Sie sie folgendermaßen einstellen:

db = SQLite3::Database.new "#{path_to_your_db}/your_file.db"
db.busy_timeout=(15000) # in ms, meaning it will retry for 15 seconds before it raises an exception.
#This can be any number you want. Default value is 0.
1
Elindor

Wenn Sie dieses Problem haben, aber wenn Sie das Timeout erhöhen, ändert sich nichts , liegt möglicherweise ein anderes Problem im Zusammenhang mit Transaktionen vor. Hier eine Zusammenfassung:

  1. Transaktion starten (erwirbt eine SHARED Sperre)
  2. Liest einige Daten aus der Datenbank (wir verwenden immer noch die SHARED Sperre)
  3. Währenddessen startet ein anderer Prozess eine Transaktion und schreibt Daten (Erwerb der RESERVED Sperre).
  4. Wenn Sie dann versuchen zu schreiben, versuchen Sie jetzt, die RESERVED Sperre anzufordern
  5. SQLite löst die SQLITE_BUSY-Ausnahme sofort aus (unabhängig von Ihrer Zeitüberschreitung), da Ihre vorherigen Lesevorgänge möglicherweise nicht mehr korrekt sind, wenn die RESERVED -Sperre aktiviert wird.

Eine Möglichkeit, dies zu beheben, besteht darin, den SQLite-Adapter active_record so zu patchen, dass er direkt zu Beginn der Transaktion eine RESERVED -Sperre erhält, indem die Option :immediate an den Treiber angefügt wird. Dies verringert die Leistung ein wenig, aber mindestens alle Ihre Transaktionen berücksichtigen Ihre Zeitüberschreitung und werden nacheinander ausgeführt. Hier ist, wie dies mit prepend (Ruby 2.0+) gemacht wird:

module SqliteTransactionFix
  def begin_db_transaction
    log('begin immediate transaction', nil) { @connection.transaction(:immediate) }
  end
end

module ActiveRecord
  module ConnectionAdapters
    class SQLiteAdapter < AbstractAdapter
      prepend SqliteTransactionFix
    end
  end
end

Lesen Sie hier mehr: https://Rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-sind-sofort-einige-Fälle-unabhängig-einstellbar-sqlite3_busy_timeout

1
Adrien Jarthon

Auf welche Tabelle wird zugegriffen, wenn die Sperre festgestellt wird?

Haben Sie langfristige Transaktionen?

Können Sie herausfinden, welche Anforderungen noch verarbeitet wurden, als die Sperre festgestellt wurde?

0
David Medinets

Versuchen Sie Folgendes auszuführen, es kann helfen:

ActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;") 

Von: Ruby: SQLite3 :: BusyException: Datenbank ist gesperrt:

Dies kann alle Transaktionen auflösen, die das System aufhalten

0
John

Argh - der Fluch meiner Existenz in der letzten Woche. Sqlite3 sperrt die Datenbankdatei, wenn ein Prozess schreibt in die Datenbank. IE jede Abfrage vom Typ UPDATE/INSERT (aus irgendeinem Grund auch count (*) auswählen). Es verarbeitet jedoch problemlos mehrere Lesevorgänge.

So wurde ich schließlich genug frustriert, um meinen eigenen Thread-Sperrcode um die Datenbankaufrufe zu schreiben. Durch die Sicherstellung, dass die Anwendung zu jedem Zeitpunkt nur einen Thread in die Datenbank schreiben kann, konnte ich auf Tausende von Threads skalieren.

Und ja, es ist langsam wie die Hölle. Aber es ist auch schnell genug und richtig , was eine nette Eigenschaft zu haben ist.

0
Voltaire

Ich habe einen Deadlock bei der Ruby-Erweiterung von sqlite3 gefunden und hier behoben: probieren Sie es aus und prüfen Sie, ob dies Ihr Problem behebt.

 
 https://github.com/dxj19831029/sqlite3-Ruby[.____.

Ich habe eine Pull-Anfrage geöffnet, keine Antwort mehr von ihnen.

Wie auch immer, es wird eine ausgelastete Ausnahme erwartet, wie in sqlite3 selbst beschrieben.

Seien Sie sich bewusst mit dieser Bedingung: sqlite beschäftigt

 
 Die Anwesenheit eines beschäftigten Bearbeiters garantiert nicht, dass er aufgerufen wird, wenn 
 Sperrenkonflikte vorliegen. Wenn SQLite feststellt, dass das Aufrufen des beschäftigten Handlers zu einem 
 Deadlock führen kann, wird SQLITE_BUSY oder SQLITE_IOERR_BLOCKED zurückgegeben, anstatt 
 Den beschäftigten Handler aufzurufen. Stellen Sie sich ein Szenario vor, in dem ein Prozess eine Lesesperre 
 Hält, die zu einer reservierten Sperre heraufgestuft werden soll, und ein zweiter Prozess eine reservierte 
 Sperre hält, die zu einer exklusiven Sperre heraufgestuft werden soll . Der erste Prozess kann nicht fortgesetzt werden, da er vom zweiten Prozess blockiert wird, und der zweite Prozess kann nicht fortgesetzt werden, weil er vom ersten Prozess blockiert wird. Wenn beide Prozesse die beschäftigten Handler aufrufen, macht keiner 
 Fortschritte. Daher gibt SQLite SQLITE_BUSY für den ersten Prozess zurück, in der Hoffnung, dass dies 
 Den ersten Prozess veranlasst, seine Lesesperre aufzuheben, und dem zweiten Prozess ermöglicht, 
 Fortzufahren. 
 

Wenn Sie diese Bedingung erfüllen, ist das Timeout nicht mehr gültig. Um dies zu vermeiden, setzen Sie select nicht in begin/commit. oder verwenden Sie die exklusive Sperre für begin/commit.

Hoffe das hilft. :)

0
xijing dai

dies ist häufig ein konsekutiver Fehler mehrerer Prozesse, die auf dieselbe Datenbank zugreifen, d. h. wenn das Flag "Nur eine Instanz zulassen" in RubyMine nicht gesetzt wurde

0
Anno2001