web-dev-qa-db-fra.com

SQLite3 :: BusyException

Lancer un site Rails en utilisant 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: ...

Comment résoudre ce problème avec une intrusion minimale dans mon code?

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

35
Shalmanese

Par défaut, sqlite renvoie immédiatement avec une erreur bloquée, 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é.

 // définissez SQLite pour attendre et réessayer jusqu'à 100 ms si la base de données est verrouillée 
 sqlite3_busy_timeout (db, 100); 
8
ravenspoint

Vous avez mentionné qu'il s'agissait d'un site Rails. Rails vous permet de définir le délai d'attente 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. L'augmenter à 10 ou 15 secondes devrait réduire le nombre d'exceptions BusyExceptions que vous voyez dans votre journal.

Ce 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.

53
Rifkin Habsburg

Toutes ces choses sont vraies, mais cela ne répond pas à la question probablement suivante: pourquoi mon application Rails soulève-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 dû au phénomène de verrouillage de fichiers avec des partages NFS et au manque de simultanéité de SQLite.

3
ybakos

Juste pour info. Dans une application avec Rails 2.3.8, nous avons découvert que Rails ignorait l'option de "temporisation" suggérée par Rifkin Habsburg.

Après des recherches plus poussées, nous avons découvert un bug éventuellement lié à Rails dev: http://dev.rubyonrails.org/ticket/8811 . Et après quelques recherches, nous avons trouvé la solution (testé 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 is Nice!

2
Ignacio Huerta
bundle exec rake db:reset

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

2
Balaji Radhakrishnan

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(...)
1
Brian R. Bondy

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.

1
meredrica

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 Python Sqlite:

Vous pouvez contrôler le type d'instructions BEGIN que pysqlite s'exécute implicitement (ou pas du tout) via le paramètre isolation_level de l'appel connect () ou via la propriété isolation_level des connexions.

1
alfredodeza

La plupart des réponses concernent Rails plutôt que Ruby brut, et les questions OP IS pour Rails, ce qui convient. :)

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

Après avoir établi la connexion, vous pouvez le configurer 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.
1
Elindor

Si vous avez ce problème mais l'augmentation du délai d'attente ne change rien , vous pouvez avoir un autre problème de simultanéité avec les transactions, le voici en résumé:

  1. Commencer une transaction (acquiert un verrou PARTAG&EACUTE;)
  2. Lire des données de la base de données (nous utilisons toujours le verrou PARTAG&EACUTE;)
  3. Pendant ce temps, un autre processus démarre une transaction et écrit des données (en acquérant le verrou RESERVED).
  4. Ensuite, vous essayez d'écrire, vous essayez maintenant de demander le verrou RESERVED
  5. SQLite lève l'exception SQLITE_BUSY immediatement (indépendamment de votre délai d'attente) car vos lectures précédentes risquent de ne plus être exactes au moment où elles peuvent obtenir le verrou RESERVED.

Une solution consiste à appliquer un correctif à l'adaptateur active_record sqlite afin d'obtenir un verrou R&EACUTE;SERV&EACUTE; au début de la transaction en ajoutant l'option :immediate 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+), placez-le 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 plus ici: https://Rails.lighthouseapp.com/projects/8994/tickets/5941-sqlite3busyexceptions-are-raised-immediately-in-some-case-despite-setting-sqlite3_busy_timeout_timeout

1
Adrien Jarthon

À 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é?

0
David Medinets

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

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

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

Cela peut effacer la toute transaction bloquant le système

0
John

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.

Donc, je suis finalement assez 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 jusqu'à 1 000 threads.

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

0
Voltaire

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 de tirage, pas de réponse de leur part.

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

Soyez conscient avec cette condition: sqlite busy

 
 La présence d'un gestionnaire occupé ne garantit pas qu'il sera invoqué lorsqu'il existe un conflit de verrouillage 
. Si SQLite détermine que l'appel du gestionnaire occupé peut entraîner un blocage (.____.), Il retournera et retournera SQLITE_BUSY ou SQLITE_IOERR_BLOCKED au lieu de 
 Invoquant le gestionnaire occupé. Imaginons un scénario dans lequel un processus détient un verrou en lecture 
 Qu'il tente de passer à un verrou réservé et un second processus qui détient un verrou réservé 
 Qu'il tente de promouvoir en verrou exclusif. . Le premier processus ne peut pas continuer 
 Car il est bloqué par le deuxième et le deuxième processus ne peut pas continuer car il est 
 Bloqué par le premier. Si les deux processus appellent les gestionnaires occupés, aucun d'eux ne progressera 
. Par conséquent, SQLite renvoie SQLITE_BUSY pour le premier processus, en espérant que cela 
 Incitera le premier processus à libérer son verrou de lecture et à permettre au deuxième processus de 
 Continuer. 
 

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 cela t'aides. :)

0
xijing dai

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

0
Anno2001