106 votes

Python sqlite3 et concurrence

J'ai un programme Python qui utilise le module "threading". Toutes les secondes, mon programme démarre un nouveau thread qui récupère des données sur le web et les stocke sur mon disque dur. J'aimerais utiliser sqlite3 pour stocker ces résultats, mais je n'arrive pas à le faire fonctionner. Le problème semble se situer au niveau de la ligne suivante :

conn = sqlite3.connect("mydatabase.db")
  • Si je place cette ligne de code dans chaque thread, j'obtiens une OperationalError m'indiquant que le fichier de la base de données est verrouillé. Je suppose que cela signifie qu'un autre thread a ouvert mydatabase.db via une connexion sqlite3 et l'a verrouillé.
  • Si je place cette ligne de code dans le programme principal et que je passe l'objet de connexion (conn) à chaque thread, j'obtiens une erreur de programmation (ProgrammingError), indiquant que les objets SQLite créés dans un thread ne peuvent être utilisés que dans ce même thread.

Auparavant, je stockais tous mes résultats dans des fichiers CSV et je ne rencontrais aucun de ces problèmes de verrouillage de fichiers. J'espère que cela sera possible avec sqlite. Des idées ?

5 votes

J'aimerais noter que les versions plus récentes de Python incluent des versions plus récentes de sqlite3 qui devraient corriger ce problème.

0 votes

@RyanFugger savez-vous quelle est la version la plus ancienne qui supporte cela ? J'utilise la version 2.7

0 votes

@RyanFugger AFAIK il n'y a pas de version préconstruite qui contient une version plus récente de SQLite3 qui a corrigé cela. Vous pouvez cependant en construire une vous-même.

209voto

Jeremiah Rose Points 1514

Contrairement à la croyance populaire, les nouvelles versions de sqlite3 faire supportent l'accès à partir de plusieurs threads.

Cette option peut être activée par le biais d'un mot-clé facultatif check_same_thread :

sqlite.connect(":memory:", check_same_thread=False)

8 votes

J'ai rencontré des exceptions imprévisibles et même des plantages de Python avec cette option (Python 2.7 sur Windows 32).

6 votes

Selon la documents En mode multithread, aucune connexion à une base de données ne peut être utilisée dans plusieurs threads. Il existe également un mode sérialisé

0 votes

Quelqu'un peut-il faire un lien vers la partie spécifique de la documentation ?

52voto

Lazin Points 4481

Vous pouvez utiliser le modèle consommateur-producteur. Par exemple, vous pouvez créer une file d'attente partagée entre plusieurs threads. Le premier thread qui récupère des données sur le web les met en file d'attente dans la file d'attente partagée. Un autre thread qui possède une connexion à la base de données retire les données de la file d'attente et les transmet à la base de données.

10 votes

FWIW : les versions ultérieures de sqlite prétendent que vous pouvez partager des connexions et des objets entre les threads (à l'exception des curseurs), mais j'ai constaté le contraire dans la pratique.

0 votes

Ici est un exemple de ce qu'Evgeny Lazin a mentionné plus haut.

6 votes

Cacher votre base de données derrière une file d'attente partagée est une très mauvaise solution à cette question parce que SQL en général et SQLite en particulier déjà sont dotés de mécanismes de verrouillage intégrés, qui sont probablement beaucoup plus perfectionnés que tout ce que vous pouvez construire vous-même de manière ad hoc.

20voto

Robert Krolik Points 81

Les éléments suivants ont été trouvés sur mail.python.org.pipermail.1239789

J'ai trouvé la solution. Je ne sais pas pourquoi la documentation de python ne contient pas un seul mot sur cette option. Nous devons donc ajouter un nouveau mot-clé argument à la fonction de connexion et nous serons en mesure de créer des curseurs à partir de celle-ci dans différents threads. Utilisez donc :

sqlite.connect(":memory:", check_same_thread = False)

fonctionne parfaitement pour moi. Bien sûr, à partir de maintenant, j'ai besoin de m'occuper d'un accès multithreading sécurisé à la base de données. En tout cas, merci à tous d'avoir essayé de m'aider.

0 votes

(Avec la GIL, il n'y a pas vraiment de véritable accès multithread à la base de données, pour autant que j'aie vu).

1 votes

AVERTISSEMENT : La documentation Python a cette de dire à propos de la check_same_thread option : "Lors de l'utilisation de plusieurs threads avec la même connexion, les opérations d'écriture doivent être sérialisées par l'utilisateur afin d'éviter la corruption des données." Donc oui, vous peut utiliser SQLite avec plusieurs threads tant que votre code garantit qu'un seul thread peut écrire dans la base de données à un moment donné. Dans le cas contraire, vous risquez de corrompre votre base de données.

16voto

nosklo Points 75862

Passer à multiprocessing . Il est bien meilleur, s'adapte bien, peut aller au-delà de l'utilisation de plusieurs cœurs en utilisant plusieurs CPU, et l'interface est la même qu'en utilisant le module de threading de python.

Ou, comme l'a suggéré Ali, utilisez simplement Mécanisme de pooling de threads de SQLAlchemy . Il s'occupe de tout automatiquement pour vous et possède de nombreuses fonctionnalités supplémentaires, pour n'en citer que quelques-unes :

  1. SQLAlchemy comprend des dialectes pour SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase et Informix ; IBM a également publié un pilote DB2. Vous n'avez donc pas besoin de réécrire votre application si vous décidez d'abandonner SQLite.
  2. Le système Unit Of Work, partie centrale de l'Object Relational Mapper (ORM) de SQLAlchemy, organise les opérations de création/insertion/mise à jour/suppression en attente dans des files d'attente et les vide en un seul lot. Pour ce faire, il effectue un "tri de dépendance" topologique de tous les éléments modifiés dans la file d'attente afin d'honorer les contraintes de clé étrangère, et regroupe les déclarations redondantes où elles peuvent parfois être regroupées encore plus loin. Cela permet d'obtenir un maximum d'efficacité et de sécurité des transactions, et de minimiser les risques de blocage.

12voto

Dustin Points 35205

Vous ne devriez pas utiliser de fils pour cela. Il s'agit d'une tâche triviale pour tordu et cela vous mènerait probablement beaucoup plus loin de toute façon.

N'utilisez qu'un seul thread et faites en sorte que l'achèvement de la demande déclenche un événement pour effectuer l'écriture.

twisted s'occupera de l'ordonnancement, des callbacks, etc... pour vous. Il vous donnera le résultat complet sous forme de chaîne de caractères, ou vous pouvez l'exécuter à travers un processeur de flux (j'ai un API twitter et un API friendfeed qui déclenchent tous deux des événements pour les appelants alors que les résultats sont encore en cours de téléchargement).

En fonction de ce que vous faites avec vos données, vous pouvez simplement transférer le résultat complet dans sqlite lorsqu'il est complet, le faire cuire et le transférer, ou le faire cuire pendant qu'il est lu et le transférer à la fin.

J'ai une application très simple qui fait quelque chose de proche de ce que vous voulez sur github. Je l'appelle pfetch (recherche parallèle). Il saisit différentes pages selon un calendrier, transmet les résultats à un fichier et exécute éventuellement un script à la fin de chacune d'elles. Il fait aussi des choses fantaisistes comme des GETs conditionnels, mais peut être une bonne base pour ce que vous faites.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X