web-dev-qa-db-fra.com

Python sqlite3 et la concurrence

J'ai un programme Python qui utilise le module "threading". Une fois par seconde, mon programme démarre un nouveau thread qui extrait des données du Web et les stocke sur mon disque dur. J'aimerais utiliser sqlite3 pour stocker ces résultats, mais je ne parviens pas à le faire fonctionner. Le problème semble concerner la ligne suivante:

conn = sqlite3.connect("mydatabase.db")
  • Si je mets cette ligne de code dans chaque thread, j'obtiens un OperationalError me disant que le fichier de base de données est verrouillé. J'imagine que cela signifie qu'un autre thread a mydatabase.db ouvert via une connexion sqlite3 et l'a verrouillée.
  • Si je mets cette ligne de code dans le programme principal et passe l'objet de connexion (conn) à chaque thread, j'obtiens un 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 n’avais aucun de ces problèmes de verrouillage de fichier. Espérons que cela sera possible avec sqlite. Des idées?

73
RexE

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

38
Lazin

Contrairement aux idées reçues, les nouvelles versions de sqlite3 do prennent en charge les accès à partir de plusieurs threads.

Ceci peut être activé avec l'argument facultatif check_same_thread:

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

Ce qui suit se trouve sur mail.python.org.pipermail.1239789

J'ai trouvé la solution. Je ne sais pas pourquoi la documentation python n'a pas un seul mot sur cette option. Nous devons donc ajouter un nouvel argument de mot-clé à la fonction de connexion .__ et nous pourrons en créer des curseurs dans différents threads. Alors utilisez:

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

fonctionne parfaitement pour moi. Bien sûr, à partir de maintenant, je dois veiller à assurer un accès multithreading sécurisé à la base de données. Quoi qu'il en soit, merci d'avoir essayé d'aider.

16
Robert Krolik

Vous ne devriez pas utiliser de fil du tout pour cela. Ceci est une tâche triviale pour twisted et cela vous mènerait probablement beaucoup plus loin de toute façon.

Utilisez un seul thread et faites en sorte que la requête complète déclenche un événement pour effectuer l'écriture.

twisted se chargera de la planification, des rappels, etc ... pour vous. Il vous donnera le résultat complet sous forme de chaîne ou vous pouvez l'exécuter via un processeur de flux (j'ai une API Twitter et une API friendfeed qui déclenchent tous les deux des événements en tant qu'appelants. toujours en cours de téléchargement).

Selon ce que vous faites avec vos données, vous pouvez simplement transférer le résultat complet dans sqlite au fur et à mesure de son achèvement, le cuire et le vider, ou le cuire pendant sa lecture et le vider à 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 (extraction parallèle). Il saisit différentes pages d'un calendrier, transmet les résultats dans un fichier et exécute éventuellement un script à la fin de chacune d'elles. Il propose également des options sophistiquées telles que les GET conditionnels, mais pourrait tout de même constituer une bonne base pour tout ce que vous faites.

13
Dustin

Basculer vers multitraitement . Il est bien meilleur, évolue bien, peut aller au-delà de l’utilisation de plusieurs cœurs en utilisant plusieurs processeurs, et l’interface est identique à celle du module de threading python.

Ou, comme suggéré par ALi, utilisez simplement le mécanisme de pool de threads de SQLAlchemy . Il gérera automatiquement tout pour vous et comportera de nombreuses fonctionnalités supplémentaires, pour ne citer que celles-ci:

  1. SQLAlchemy inclut des dialectes pour SQLite, Postgres, MySQL, Oracle, MS-SQL, Firebird, MaxDB, MS Access, Sybase et Informix; IBM a également publié un pilote DB2. Il n'est donc pas nécessaire de réécrire votre application si vous décidez de vous éloigner de SQLite.
  2. Le système Unit Of Work, une partie centrale de l'ORM (Object Relational Mapper) 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 de la file d'attente afin de respecter les contraintes de clé étrangère et regroupe les instructions redondantes dans lesquelles elles peuvent parfois être encore davantage traitées en lot. Ceci produit une efficacité maximale et une sécurité des transactions, tout en minimisant les risques de blocages.
11
nosklo

Ou si vous êtes paresseux, comme moi, vous pouvez utiliser SQLAlchemy . Il gérera le thread pour vous ( en utilisant un thread local et un regroupement de connexions ) et la façon dont il le fait est même configurable .

Pour un bonus supplémentaire, si/quand vous réalisez/décidez que l'utilisation de Sqlite pour une application concurrente sera un désastre, vous n'aurez pas à changer votre code pour utiliser MySQL, ou Postgres, ou toute autre chose. Vous pouvez simplement basculer.

7
Ali Afshar

Vous devez utiliser session.close() après chaque transaction avec la base de données pour pouvoir utiliser le même curseur dans le même thread sans utiliser le même curseur dans les multi-threads à l'origine de cette erreur.

2
Hazem Khaled

J'aime la réponse d'Evgeny - Les files d'attente sont généralement le meilleur moyen d'implémenter la communication inter-thread. Pour être complet, voici quelques autres options:

  • Fermez la connexion à la base de données lorsque les threads créés l'utilisent. Cela corrigerait votre OperationalError, mais l'ouverture et la fermeture de connexions de ce type constituent généralement un non-non, en raison de la surcharge de performances.
  • N'utilisez pas de fils enfants. Si la tâche une fois par seconde est raisonnablement légère, vous pouvez vous en sortir, aller chercher et stocker, puis dormir jusqu'au bon moment. Cela n’est pas souhaitable, car les opérations d’extraction et de stockage peuvent prendre plus de 1 seconde et vous perdez l’avantage des ressources multiplexées que vous possédez avec une approche multithread.
0
James Brady
0
Alexandr

Scrapy semble être une réponse possible à ma question. Sa page d'accueil décrit ma tâche exacte. (Bien que je ne sois pas sûr de la stabilité du code.)

0
RexE

Vous devez concevoir la simultanéité pour votre programme. SQLite a des limitations claires et vous devez les respecter, voir le FAQ (également la question suivante).

0
iny

Je ne pouvais trouver aucune référence dans aucune des réponses ci-dessus, j'ai donc écrit un test pour tout comparer.

J'ai essayé 3 approches

  1. Lecture et écriture séquentielles à partir de la base de données SQLite
  2. Utiliser un ThreadPoolExecutor pour lire/écrire
  3. Utiliser un ProcessPoolExecutor pour lire/écrire

Les résultats et les points à retenir de l’indice de référence sont les suivants

  1. Les lectures séquentielles/écritures séquentielles fonctionnent le mieux
  2. Si vous devez traiter en parallèle, utilisez ProcessPoolExecutor pour lire en parallèle.
  3. N'effectuez aucune écriture à l'aide de ThreadPoolExecutor ou de ProcessPoolExecutor car vous rencontrerez des erreurs de base de données verrouillées et vous devrez réessayer d'insérer le bloc.

Vous pouvez trouver le code et la solution complète pour les tests de performance dans ma SO réponse ICI J'espère que cela vous aidera!

0
PirateApp

Je voudrais jeter un oeil sur le module Python y_serial pour la persistance des données: http://yserial.sourceforge.net

qui gère les problèmes de blocage liés à une base de données SQLite unique. Si la demande en accès simultané devient lourde, vous pouvez facilement configurer la classe Farm de nombreuses bases de données pour répartir la charge sur le temps stochastique.

J'espère que cela aidera votre projet ... il devrait être assez simple à mettre en œuvre en 10 minutes.

0
code43