2010-10-17 17 views
11

C'est difficile à expliquer (et très bizarre), alors supportez-moi. Je vais expliquer le problème, et le correctif pour cela, mais je voudrais voir si quelqu'un peut expliquer pourquoi cela fonctionne comme ça fonctionne :)Poignée de la base de données DBI avec AutoCommit définie sur 0 ne renvoyant pas les données correctes avec SELECT?

J'ai une application web qui utilise mod_perl. Il utilise la base de données MySQL et j'écris régulièrement des données dans une base de données. Il est modulaire, donc il a aussi son propre type de base de données, où je gère la connexion, les mises à jour, etc. database :: db_connect() subroutine est utilisé pour se connecter à la base de données, et AutoCommit est 0.

J'ai créé une autre application Perl (démon autonome), qui extrait périodiquement des données de la base de données et effectue diverses tâches en fonction des données renvoyées. J'y inclus le module database.pm, donc je n'ai pas besoin de réécrire/dupliquer tout.

Problème Je rencontre est:

application se connecte à la base de données au démarrage, puis passe en boucle pour toujours, la récupération des données de base de données toutes les X secondes. Cependant, si les données dans la base de données sont mises à jour, mon application est toujours retournée "anciennes" données, que j'ai obtenu sur la connexion initiale/requête à la base de données. Par exemple - J'ai 3 lignes, et la colonne "Nom" a les valeurs 'a', 'b' et 'c' - pour chaque enregistrement. Si je mets à jour l'une des lignes (en utilisant le client mysql depuis la ligne de commande, par exemple) et que je change de nom de 'c' en 'x', mon démon autonome n'obtiendra pas ces données - il recevra quand même un/b/c MySQL J'ai capturé le trafic db avec tcpdump, et je pouvais vraiment voir que MySQL renvoyait vraiment ces données. J'ai essayé d'utiliser SQL_NO_CACHE avec SELECT (puisque je n'étais pas sûr de ce qui se passait), mais cela n'a pas aidé non plus.

Ensuite, j'ai modifié la chaîne de connexion DB dans mon démon autonome et défini AutoCommit sur 1. Soudain, l'application a commencé à obtenir des données correctes.

Je suis perplexe, car je pensais que AutoCommit n'affecte que les types d'instructions INSERT/UPDATE et n'a aucun effet sur l'instruction SELECT. Mais apparemment, et je ne comprends pas pourquoi. Est-ce que quelqu'un sait pourquoi l'instruction SELECT ne retournera pas les lignes 'mises à jour' de la base de données lorsque AutoCommit est définie sur 0 et pourquoi retournera-t-elle les lignes mises à jour lorsque AutoCommit est défini sur 1?

Voici un code simplifié (vérification des erreurs, etc.) que j'utilise dans un démon autonome, et qui ne renvoie pas les lignes mises à jour.

#!/usr/bin/perl 

use strict; 
use warnings; 
use DBI; 
use Data::Dumper; 
$|=1; 

my $dsn = "dbi:mysql:database=mp;mysql_read_default_file=/etc/mysql/database.cnf"; 
my $dbh = DBI->connect($dsn, undef, undef, {RaiseError => 0, AutoCommit => 0}); 
$dbh->{mysql_enable_utf8} = 1; 

while(1) 
{ 
    my $sql = "SELECT * FROM queue"; 
    my $stb = $dbh->prepare($sql); 
    my $ret_hashref = $dbh->selectall_hashref($sql, "ID"); 
    print Dumper($ret_hashref); 
    sleep(30); 
} 

exit; 

La modification de AutoCommit à 1 résout ce problème. Pourquoi?

Merci de

de P.S: Je ne sais pas si quelqu'un se soucie, mais la version DBI est 1,613, DBD :: mysql est 4.017, Perl est 5.10.1 (sur Ubuntu 10.04).

+0

Le paramètre 'auto_commit' est-il activé ou désactivé dans votre client mysql en ligne de commande (où vous avez fait l'opération' UPDATE')? – Ether

+0

Il est activé (il est activé par défaut et je ne l'ai pas modifié). Je peux voir les 'nouvelles' données mises à jour depuis le client mysql, ou toute autre 'session' (nouvelle session DBI connectée, ou tout autre client se connectant à DB) - seule la session avec AutoCommit 0 ne peut pas accéder aux données mises à jour . – sentinel

Répondre

14

Je suppose que vous utilisez des tables InnoDB et pas celles de MyISAM.Comme décrit dans InnoDB transaction model, tous vos requêtes (y compris SELECT) ont lieu dans une transaction. Lorsque AutoCommit est activé, une transaction est démarrée pour chaque requête et, si elle réussit, elle est implicitement validée (en cas d'échec, le comportement peut varier mais la transaction est garantie). Vous pouvez voir les validations implicites dans le binlog de MySQL. En définissant AutoCommit sur false, vous devez gérer les transactions par vous-même. Le niveau d'isolation de transaction par défaut est REPEATABLE READ, ce qui signifie que toutes les requêtes SELECT liront le même instantané (celui qui a été établi au début de la transaction).

En plus de la solution donnée dans l'autre réponse (ROLLBACK avant de commencer à lire), voici quelques solutions:

Vous pouvez choisir un autre niveau d'isolation des transactions, comme READ COMMITTED, ce qui rend vos SELECT requêtes lire une nouveau cliché à chaque fois. Vous pouvez également laisser AutoCommit à true (le paramètre par défaut) et démarrer vos propres transactions en émettant BEGIN WORK. Cela désactivera temporairement le comportement AutoCommit jusqu'à ce que vous émettiez une instruction COMMIT ou ROLLBACK après quoi chaque requête obtient à nouveau sa propre transaction (ou vous en démarrez une autre avec BEGIN WORK). Personnellement, je choisirais cette dernière méthode, car elle semble plus élégante.

+0

Ceci est vraiment une réponse incroyable, et je vous remercie beaucoup d'avoir pris le temps de l'expliquer. J'ai lu la documentation et j'ai essayé de la comprendre, mais je n'ai pas vraiment rencontré ce que vous avez mentionné ici (je lisais probablement des documents erronés, alors;). Merci encore une fois, cela explique vraiment beaucoup. – sentinel

+0

Une autre question (en supposant même que vous lisiez encore ceci :). Nous utilisons des procédures stockées du côté de MySQL, donc je ne fais aucune transaction dans le code Perl. Puis-je utiliser AutoCommit = 1 et émettre "BEGIN WORK" juste avant d'appeler la procédure stockée à partir du code Perl? – sentinel

+0

Oui, vous pouvez le faire. Vous pouvez également placer la transaction dans une procédure stockée (mais * pas * dans une fonction stockée), mieux adaptée à votre flux de travail. – kixx

4

Je pense que lorsque vous désactivez la validation automatique, vous lancez également une transaction. De plus, lorsque vous lancez une transaction, vous pouvez être protégé contre les modifications apportées par d'autres personnes jusqu'à ce que vous les validiez ou que vous l'annuliez. Donc, si je pense au courant semi-est correcte, et puisque vous êtes seulement les données d'interroger, ajouter un rollback avant l'opération de sommeil (pas de point dans la tenue des verrous que vous n'utilisez pas, etc):

$dbh->rollback; 
+0

Merci pour la réponse :) – sentinel