Question

Exécution d'un site de rails à l'aide de SQLite3.

Environ une fois toutes les 500 demandes environ, je reçois un

ActiveRecord :: StatementInvalid (SQLite3 :: BusyException: la base de données est verrouillée: ...

Quel est le moyen de résoudre ce problème qui serait peu envahissant pour mon code?

J'utilise actuellement SQLLite parce que vous pouvez stocker la base de données dans le contrôle de code source, ce qui rend la sauvegarde naturelle et vous permet de transmettre les modifications très rapidement. Cependant, ce n'est évidemment pas vraiment configuré pour un accès simultané. Je migrerai vers MySQL demain matin.

Était-ce utile?

La solution

Par défaut, sqlite renvoie immédiatement avec une erreur bloquée, occupé si la base de données est occupée et verrouillée. Vous pouvez lui demander d'attendre et de continuer à essayer pendant un moment avant d'abandonner. Cela résout généralement le problème, sauf si vous avez 1 000 threads accédant à votre base de données, lorsque je conviens que SQLite serait inapproprié.

    // set SQLite to wait and retry for up to 100ms if database locked
    sqlite3_busy_timeout( db, 100 );

Autres conseils

Vous avez mentionné qu'il s'agissait d'un site Rails. Rails vous permet de définir le délai avant nouvelle tentative SQLite dans votre fichier de configuration database.yml:

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

La valeur de délai d'attente est spécifiée en millisecondes. Si vous augmentez cette valeur à 10 ou 15 secondes, le nombre d'exceptions BusyExceptions que vous voyez dans votre journal devrait diminuer.

Ceci n’est cependant qu’une solution temporaire. Si votre site nécessite une concurrence réelle, vous devrez migrer vers un autre moteur de base de données.

Toutes ces choses sont vraies, mais cela ne répond pas à la question suivante: pourquoi mon application Rails pose-t-elle parfois une exception SQLite3 :: BusyException en production?

@Shalmanese: à quoi ressemble l'environnement d'hébergement de production? Est-ce sur un hôte partagé? Le répertoire contenant la base de données sqlite est-il sur un partage NFS? (Probablement sur un hôte partagé).

Ce problème est probablement lié au phénomène de verrouillage de fichiers avec des partages NFS et au manque de simultanéité de SQLite.

Juste pour le compte rendu. Dans une application avec Rails 2.3.8, nous avons découvert que Rails ignorait le "timeout". option proposée par Rifkin Habsburg.

Après des recherches plus approfondies, nous avons découvert un bug éventuellement lié à Rails dev: http: //dev.rubyonrails. org / ticket / 8811 . Et après quelques recherches supplémentaires, nous avons trouvé la solution (testée avec Rails 2.3.8):

Éditez ce fichier ActiveRecord: activerecord-2.3.8 / lib / active_record / connection_adapters / sqlite_adapter.rb

Remplacez ceci:

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

avec

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

Et c'est tout! Nous n'avons pas remarqué de baisse de performances et maintenant, l'application prend en charge de nombreuses autres pétitions sans interruption (elle attend la fin du délai). Sqlite est sympa!

bundle exec rake db:reset

Cela a fonctionné pour moi, il va réinitialiser et afficher la migration en attente.

Source: ce lien

- 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(...)

SQLite peut permettre à d’autres processus d’attendre la fin du processus en cours.

J'utilise cette ligne pour établir une connexion lorsque je sais que plusieurs processus tentent d'accéder à la base de données SQL:

conn = sqlite3.connect ('nomfichier', isolation_level = 'exclusif' )

Selon la documentation de Python Sqlite:

  

Vous pouvez contrôler quel type de début   déclarations pysqlite implicitement   exécute (ou pas du tout) via le   paramètre isolation_level au   connect () appel, ou via le   propriété isolation_level de   connexions.

J'ai eu un problème similaire avec rake db: migrate. Le problème était que le répertoire de travail se trouvait sur un partage SMB. Je l'ai corrigé en copiant le dossier sur ma machine locale.

Si vous rencontrez ce problème mais que l'augmentation du délai d'attente ne change rien , il est possible que vous rencontriez un autre problème de simultanéité avec les transactions. Voici le résumé:

  1. Commencer une transaction (acquiert un verrou SHARED )
  2. Lire certaines données de la base de données (nous utilisons toujours le verrou SHARED )
  3. Pendant ce temps, un autre processus démarre une transaction et écrit des données (acquisition du verrou RESERVED ).
  4. Ensuite, vous essayez d'écrire, vous essayez maintenant de demander le verrou RESERVED
  5. SQLite lève l'exception SQLITE_BUSY immédiatement (indépendamment de votre délai d'expiration), car vos lectures précédentes risquent de ne plus être exactes au moment où vous pouvez obtenir le verrou RÉSERVÉ .

Une solution à ce problème consiste à appliquer une correction à l'adaptateur sqlite active_record afin d'obtenir un verrou RESERVED directement au début de la transaction en complétant le code : immédiat option au pilote. Cela réduira un peu les performances, mais au moins toutes vos transactions respecteront votre délai d'attente et se produiront l'une après l'autre. Voici comment faire cela en utilisant prepend (Ruby 2.0+) et le placer dans un initialiseur:

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

Lisez la suite ici: https://rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-cases-despite-setting-sqlite3_busy_timeout

La plupart des réponses concernent les rails plutôt que les rubis bruts, et les questions OP sur les rails, ce qui est correct. :)

Je souhaite simplement laisser cette solution ici si un utilisateur de ruby ??brut rencontre ce problème et n'utilise pas de configuration yml.

Une fois la connexion établie, vous pouvez la définir comme suit:

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.

À quelle table accède-t-on lorsque le verrou est rencontré?

Avez-vous des transactions de longue durée?

Pouvez-vous déterminer quelles demandes étaient encore en cours de traitement lorsque le verrou a été rencontré?

Argh - le fléau de mon existence au cours de la semaine dernière. Sqlite3 verrouille le fichier db quand un processus écrit dans la base de données. IE toute requête de type UPDATE / INSERT (également sélectionner count (*) pour une raison quelconque). Cependant, il gère très bien plusieurs lectures.

Ainsi, je suis finalement suffisamment frustré pour écrire mon propre code de verrouillage de threads autour des appels de base de données. En veillant à ce que l'application ne puisse avoir qu'un seul thread écrivant dans la base de données à tout moment, j'ai été en mesure de faire évoluer 1000 threads.

Et oui, c'est lent comme l'enfer. Mais c’est aussi assez rapide et correct , ce qui est une belle propriété à avoir.

J'ai trouvé un blocage sur l'extension sqlite3 ruby ??et je le répare ici: essayez-le et voyez si cela résout votre problème.

    https://github.com/dxj19831029/sqlite3-ruby

J'ai ouvert une demande d'extraction, plus aucune réponse d'eux.

Quoi qu'il en soit, une exception occupée est attendue, comme décrit dans sqlite3.

Soyez conscient de cette condition: sqlite occupé

    The presence of a busy handler does not guarantee that it will be invoked when there is 
    lock contention. If SQLite determines that invoking the busy handler could result in a 
    deadlock, it will go ahead and return SQLITE_BUSY or SQLITE_IOERR_BLOCKED instead of 
    invoking the busy handler. Consider a scenario where one process is holding a read lock 
    that it is trying to promote to a reserved lock and a second process is holding a reserved 
    lock that it is trying to promote to an exclusive lock. The first process cannot proceed 
    because it is blocked by the second and the second process cannot proceed because it is 
    blocked by the first. If both processes invoke the busy handlers, neither will make any 
    progress. Therefore, SQLite returns SQLITE_BUSY for the first process, hoping that this 
    will induce the first process to release its read lock and allow the second process to 
    proceed.

Si vous remplissez cette condition, le délai d'attente n'est plus valide. Pour l'éviter, ne placez pas select dans begin / commit. ou utilisez le verrou exclusif pour begin / commit.

J'espère que ça aide. :)

il s'agit souvent d'une erreur consécutive de plusieurs processus accédant à la même base de données, c'est-à-dire si l'option "Autoriser une seule instance" " le drapeau n'a pas été défini dans RubyMine

Essayez d’exécuter ce qui suit, cela pourrait aider:

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

De: Ruby: SQLite3 :: BusyException: la base de données est verrouillée:

Ceci peut effacer n'importe quelle transaction bloquant le système

Je pense que cela se produit lorsqu'une transaction arrive à expiration. Vous devriez vraiment utiliser un " réel " base de données. Quelque chose comme Drizzle ou MySQL. Pourquoi préférez-vous SQLite aux deux options précédentes?

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top