web-dev-qa-db-fra.com

Comment utiliser SQLite dans une application multi-thread?

Je développe une application avec SQLite en tant que base de données et j'ai un peu de difficulté à comprendre comment l'utiliser pour l'utiliser dans plusieurs threads (aucune des autres questions de Stack Overflow ne m'a vraiment aidée, malheureusement).

Mon cas d'utilisation: La base de données a une table, appelons-la "A", qui contient différents groupes de lignes (basées sur l'une de leurs colonnes). J'ai le "fil principal" de l'application qui lit le contenu de la table A. En outre, je décide de temps en temps de mettre à jour un certain groupe de lignes. Pour ce faire, je veux créer un nouveau thread, supprimer toutes les lignes du groupe et les réinsérer (c'est la seule façon de le faire dans le contexte de mon application). Cela peut arriver à différents groupes en même temps, donc je pourrais avoir 2+ threads essayant de mettre à jour la base de données.

J'utilise différentes transactions de chaque fil, I.E. au début du cycle de mise à jour de chaque thread, j'ai un début. En fait, chaque thread appelle en fait "BEGIN", supprime de la base de données toutes les lignes nécessaires pour "mettre à jour" et les insère à nouveau avec les nouvelles valeurs (c’est ainsi que cela doit être fait dans le contexte de mon script. application).

Maintenant, j'essaie de comprendre comment je vais mettre en œuvre cela. J'ai essayé de lire (autres réponses sur Stack Overflow, le site SQLite) mais je n'ai pas trouvé toutes les réponses. Voici quelques points sur lesquels je m'interroge:

  1. Dois-je appeler "open" et créer une nouvelle structure sqlite à partir de chaque thread?
  2. Dois-je ajouter un code spécial pour tout cela, ou est-il suffisant de générer différents threads, de mettre à jour les lignes, et c'est bien (puisque j'utilise différentes transactions)? 
  3. J'ai vu quelque chose qui parlait des différents types de verrous existants et du fait que je pouvais recevoir un "SQLite occupé" en appelant certaines API, mais honnêtement, je n'ai vu aucune référence qui explique complètement si je dois prendre tout cela en compte. Est ce que j'ai besoin de?

Si quelqu'un peut répondre aux questions/me diriger vers une bonne ressource, je vous en serais très reconnaissant.

UPDATE 1: D'après tout ce que j'ai lu jusqu'à présent, il semble que vous ne pouvez pas avoir deux threads qui vont écrire dans un fichier de base de données de toute façon.

Voir: http://www.sqlite.org/lockingv3.html . Dans la section 3.0: Un verrou RÉSERVÉ signifie que le processus envisage d'écrire dans le fichier de base de données à l'avenir, mais qu'il ne fait actuellement que lire le fichier. Un seul verrou RÉSERVÉ peut être actif à la fois, bien que plusieurs verrous SHARED puissent coexister avec un seul verrou RÉSERVÉ. 

Est-ce que cela signifie que je peux aussi bien ne générer qu'un seul thread pour mettre à jour un groupe de lignes à chaque fois? C'est-à-dire qu'il existe un type de thread d'interrogation qui décide que je dois mettre à jour certaines des lignes, puis crée un nouveau thread pour le faire, mais jamais plus d'un à la fois? Comme il ressemble à n'importe quel autre thread que je crée, SQLITE_BUSY sera récupéré jusqu'à la fin du premier thread.

Ai-je bien compris les choses?

BTW, merci pour les réponses jusqu'à présent, ils ont beaucoup aidé.

40
Edan Maor

Découvrez ce lien . Le moyen le plus simple consiste à verrouiller vous-même et à éviter de partager la connexion entre les threads. Une autre bonne ressource peut être trouvée ici , et se termine par:

  1. Assurez-vous de compiler SQLite avec -DTHREADSAFE = 1.

  2. Assurez-vous que chaque thread ouvre le fichier de base de données et conserve sa propre structure SQLite.

  3. Assurez-vous de gérer la possibilité probable de collision entre un ou plusieurs threads lorsqu'ils accèdent simultanément au fichier db: gérez SQLITE_BUSY de manière appropriée.

  4. Veillez à inclure dans les transactions les commandes qui modifient le fichier de base de données, telles que INSERT, UPDATE, DELETE, etc. 

22
Kristian

Quelques étapes lors du démarrage avec SQLlite pour une utilisation multithread:

  1. Assurez-vous que sqlite est compilé avec l'indicateur multi-threaded.
  2. Vous devez appeler open sur votre fichier sqlite pour créer une connexion sur chaque thread. Ne partagez pas les connexions entre les threads.
  3. SQLite a un modèle de thread très conservateur, lorsque vous effectuez une opération d'écriture, qui inclut l'ouverture des transactions sur le point d'effectuer une opération INSERT/UPDATE/DELETE, les autres threads seront bloqués jusqu'à la fin de cette opération.
  4. Si vous n'utilisez pas de transaction, les transactions sont implicites. Par conséquent, si vous démarrez un INSERT/DELETE/UPDATE, sqlite tentera d'acquérir un verrou exclusif et complétera l'opération avant de le publier.
  5. Si vous effectuez une instruction BEGIN EXCLUSIVE, elle obtiendra un verrou exclusif avant d'effectuer des opérations dans cette transaction. Un COMMIT ou ROLLBACK relâche le verrou.
  6. Votre sqlite3_step, sqlite3_prepare et certains autres appels peuvent renvoyer SQLITE_BUSY ou SQLITE_LOCKED. SQLITE_BUSY signifie généralement que sqlite doit acquérir le verrou. La plus grande différence entre les deux valeurs de retour:
    • SQLITE_LOCKED: si vous obtenez cela à partir d'une instruction sqlite3_step, vous DEVEZ appeler sqlite3_reset sur le descripteur d'instruction. Vous ne devriez l'obtenir que lors du premier appel à sqlite3_step. Ainsi, une fois que reset est appelé, vous pouvez réellement "réessayer" votre appel sqlite3_step. Sur d'autres opérations, c'est la même chose que SQLITE_BUSY
    • SQLITE_BUSY: Il n'est pas nécessaire d'appeler sqlite3_reset. Réessayez simplement votre opération après avoir attendu un peu que le verrou soit libéré.
27
Snazzer

Je me rends compte que c’est un vieux fil et que les réponses sont bonnes, mais j’ai étudié la question récemment et suis tombé sur une analyse intéressante de différentes implémentations. Il aborde principalement les points forts et les points faibles du partage de connexion, de la transmission de messages, des connexions thread-locales et du pooling de connexions. Jetez un coup d'oeil ici: http://dev.yorhel.nl/doc/sqlaccess

9
adechiaro

Vérifiez ce code depuis le wiki SQLite .

J'ai fait quelque chose de similaire avec C et j'ai téléchargé le code ici .

J'espère que c'est utile.

3
Macarse

La sécurité des threads est activée par défaut dans les versions modernes de SQLite. SQLITE_THREADSAFE compilation indique si le code est inclus ou non dans SQLite pour lui permettre de fonctionner en toute sécurité dans un environnement multithread. La valeur par défaut est SQLITE_THREADSAFE=1. Cela signifie mode sérialisé . Dans ce mode:

Dans ce mode (qui est la valeur par défaut lorsque SQLite est compilé avec SQLITE_THREADSAFE = 1), la bibliothèque SQLite sérialisera elle-même l'accès aux connexions de base de données et aux instructions préparées, de sorte que l'application puisse utiliser la même connexion à la base de données ou la même instruction préparée dans différents threads en même temps. 

Utilisez la fonction sqlite3_threadsafe() pour vérifier l’indicateur de compilation SQLITE_THREADSAFE de la bibliothèque SQLite.

Le comportement de sécurité du thread de bibliothèque par défaut peut être modifié via sqlite3_config(). Utilisez les indicateurs SQLITE_OPEN_NOMUTEX et SQLITE_OPEN_FULLMUTEX dans sqlite3_open_v2() pour ajuster le mode de threading des connexions de base de données individuelles.

1
Nikita

Résumé

Les transactions dans SQLite sont sérialisables.

Les modifications apportées dans une connexion de base de données sont invisibles pour toutes les autres connexions de base de données antérieures à la validation.

Une requête voit toutes les modifications effectuées sur la même connexion à la base de données avant le début de la requête, que ces modifications aient été validées ou non.

Si des modifications sont apportées à la même connexion à la base de données après le début de l'exécution d'une requête, mais avant la fin de la requête, il n'est pas défini si la requête verra ou non ces modifications.

Si des modifications sont apportées à la même connexion à la base de données après le début de l'exécution d'une requête, mais avant la fin de celle-ci, celle-ci peut renvoyer une ligne modifiée plusieurs fois ou peut-être une ligne précédemment supprimée.

Pour les besoins des quatre éléments précédents, deux connexions de base de données utilisant le même cache partagé et activant PRAGMA read_uncommitted sont considérées comme étant la même connexion de base de données, et non des connexions de base de données séparées.


En plus des informations ci-dessus sur l'accès multi-thread, il pourrait être intéressant de jeter un œil à cette page sur l'isolation , car beaucoup de choses ont changé depuis cette question initiale et l'introduction du journal à écriture anticipée (WAL).

Il semble qu'une approche hybride consistant à ouvrir plusieurs connexions à la base de données offre des garanties de concurrence suffisantes, compensant les frais liés à l'ouverture d'une nouvelle connexion et offrant l'avantage de permettre des transactions d'écriture multithreads. 

0
Adrian_H