JeuWeb - Crée ton jeu par navigateur
Probleme de transaction .. :( - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Probleme de transaction .. :( (/showthread.php?tid=4596)

Pages : 1 2


Probleme de transaction .. :( - Unkof - 18-02-2010

Bonjour à tous,

J'ai un problème de transaction que je n'arrive pas à solutionner, il y a sûrement quelque chose que je n'ai pas compris.

J'ai un script de gestion d'évènement central pour mon jeu qui est structuré comme ca :

Code PHP :
<?php 
mysqli_autocommit
($connect,FALSE);
$begin=mysqli_query($connect,"BEGIN");
$requete=mysqli_query($connect,"SELECT Id,Traite,Operation FROM event WHERE Cfini<='$tempsactuel' AND Traite=0 ORDER BY Cfini ASC");
while(
$result=mysqli_fetch_array($requete))
{
if(
$result['Operation']=="PRODUNITE"
{
if(
$result['Traite']==0)
{
$majtraite=mysqli_query($connect,"UPDATE event SET Traite=1 WHERE Id='$idoperation' LIMIT 1");
// .... On effectue diverses opérations
// ....
// ... ayé c'est fini
$requetecom=mysqli_query($connect,"DELETE FROM event WHERE Id='$idoperation' LIMIT 1");
}
}
}

Ce code peut être exécuté par n'importe quel joueur connecté, mais il est aussi exécuté par une tache CRON pour éviter les engorgements des évènements arrivant à terme par exemple la nuit.

La tache cron appelle donc un script qui traite lui aussi les évènements via (exactement le même qu'exécute le joueur ..) :

/usr/local/php5/bin/php -f /home/test/evenements.php

Chaque évènement à bien sûr un ID qui lui est propre.

Mon problème, c'est que j'ai journalisé les evènements traité, et je me suis apercu que de temps en temps, j'ai mon CRON et un Joueur qui exécutent le même évènement (même ID) et que mon évènement est donc exécuté 2 fois !?

Quelqu'un peut m'expliquer où est l'erreur ? .... parce que là, je sèche.

Je croyais avoir compris le fonctionnement des transactions, mais à première vu, je me suis trompé ... Confused

PS:Bien sûr, les tables sont en INNODB.


RE: Probleme de transaction .. :( - Anthor - 18-02-2010

Les transactions sont concurrentielles, elles ne sont pas faites pour éviter deux exécutions simultanées, mais pour s'assurer qu'on exécute tout un bloc de requêtes ou aucune.

C'est à toi d'effectuer la transaction à l'intérieur de ta boucle, et de rollback si l'event ne peux être delete. Sachant que la commande delete te renvoi le nombre de lignes affectées.
Si c'est 0, alors il faut rollback le contenu de la boucle.


RE: Probleme de transaction .. :( - Anthor - 18-02-2010

Par exemple :
Code PHP :
<?php 
mysqli_autocommit
($connect,FALSE);

$requete = mysqli_query($connect, "SELECT Id,Traite,Operation FROM event WHERE Cfini<='$tempsactuel' AND Traite=0 ORDER BY Cfini ASC");

while(
$result = mysqli_fetch_array($requete) )
{
if(
$result['Operation'] == "PRODUNITE" )
{
if(
$result['Traite']==0)
{
$begin = mysqli_query($connect, "START TRANSACTION");

$majtraite = mysqli_query($connect, "UPDATE event SET Traite=1 WHERE Id='$idoperation' LIMIT 1");

// .... On effectue diverses opérations
// ....
// ... ayé c'est fini

if( $requetecom = mysqli_query($connect, "DELETE FROM event WHERE Id='$idoperation' LIMIT 1") )
{
$commit = mysqli_query($connect, "COMMIT");
}
else
{
$rollback = mysqli_query($connect, "ROLLBACK");
}
}
}
}



RE: Probleme de transaction .. :( - Unkof - 18-02-2010

Ok, merci Anthor, je vais essayer ca, je pense avoir compris ce que tu veux dire.

Y a un truc que j'ai du mal à comprendre, est ce qu'il y a une différence d'exécution entre ton code, et ca (est ce que ca poserait un problème en cas d'exécution multiple ?), j'ai simplement sorti la gestion du champs Traite==0, et enlevé le $result['Traite']==0 dans la condition d'exécution :

Code PHP :
<?php 
mysqli_autocommit
($connect,FALSE);

$requete = mysqli_query($connect, "SELECT Id,Traite,Operation FROM event WHERE Cfini<='$tempsactuel' ORDER BY Cfini ASC");

while(
$result = mysqli_fetch_array($requete) )
{
if(
$result['Operation'] == "PRODUNITE" )
{
$begin = mysqli_query($connect, "START TRANSACTION");

$majtraite = mysqli_query($connect, "UPDATE event SET Traite=1 WHERE Id='$idoperation' LIMIT 1");

// .... On effectue diverses opérations
// ....
// ... ayé c'est fini

if( $requetecom = mysqli_query($connect, "DELETE FROM event WHERE Id='$idoperation' LIMIT 1") )
{
$commit = mysqli_query($connect, "COMMIT");
}
else
{
$rollback = mysqli_query($connect, "ROLLBACK");
}
}
}

En essayant d'être clair, est ce que ce code ne fonctionnerait pas lui aussi ?

Si il ne fonctionne pas, peux tu m'expliquer précisément pourquoi ?

Merci en tout cas de ton exemple Smile


RE: Probleme de transaction .. :( - Anthor - 18-02-2010

A la limite dans ce cas précis, mettre l'UPDATE avec Traite = 1 n'est pas utile.


RE: Probleme de transaction .. :( - php_addict - 18-02-2010

les transaction s'empilent les unes apres les autres non ? imaginons que j'ai 20 transactions lancées au meme moment, elles vont s'empiler... dans ce cas chaque transactions doit verifier si l'action n'a pas été DELETE , non ?

tu as plusieurs transaction qui peuvent s'empiller et elles ne verifient pas si l'action à été DELETE:

reprenons 20 transactions lancées au meme moment, elles s'empilent:
- la 1ere s'exectute et DELETE l'action selon ton code
- la 2eme fait quoi? vu qu'elle a été lancée?

sur mon post --> http://www.jeuweb.org/pdo-transactions-lock-table-et-lock-share-mode-t-6246.html , au lieu de DELETE les actions, je met un flag "resolu=1' que je teste lors de la transaction, cela semble fonctionner...

en plus cela fait un log des actions resolues...là une tache CRON peut être sympa pour effacer les actions resolues trop vieilles...


RE: Probleme de transaction .. :( - Anthor - 18-02-2010

Non, une transaction sert uniquement à faire qu'un bloc de requêtes soit exécuté en entier ou pas du tout.

Comme je l'ai dit, et je l'ai vu dans ton code php_addict, tu ne traites en aucun cas ton rollback. Car ton select, même si il renvoi 0 ligne, il ne lance aucune exception ^^
Ensuite, le flag sert si tu update et non delete l'événement comme dans le cas présent.

Dans le cas d'un update, on testerait le nombre de ligne retourné, ou le flag et on lancerait nous même l'exception. C'est pour cela que les transactions doivent être le plus courte possible.


RE: Probleme de transaction .. :( - Unkof - 18-02-2010

Anthor:Oui oui, j'ai oublié d'enlever l'update traite=1 dans ma précipitation. Merci pour la précision .. Smile

php_addict:La deuxième continue son exécution, c'est le problème que j'ai.

Le code d'Anthor est logique, et effectivement, en faisant un rollback sur un delete qui n'a pas donné de résultat == 1, ca doit règler le problème.

Je n'avais pas bien cerné le fonctionnement du transactionnel Smile ... mais maintenant je pense que c'est ok Smile.

Je modifie demain, et je me relance dans des tests, mais je pars confiant ... Smile

Merci, merci, et encore merci, ca commencait à m'empêcher de dormir cette histoire .. lol.


RE: Probleme de transaction .. :( - Unkof - 26-02-2010

Bon .. ben ... 9 jours après, toujours les mêmes problèmes.

J'ai repris exactement ce que tu m'as dit Anthor :

Code :
mysqli_autocommit($connect,FALSE);

$requete = mysqli_query($connect, "SELECT Id,Traite,Operation FROM event WHERE Cfini<='$tempsactuel' AND Traite=0 ORDER BY Cfini ASC");

while( $result = mysqli_fetch_array($requete) )
{
    if( $result['Operation'] == "PRODUNITE" )
    {                                                              
        if($result['Traite']==0)
        {
            $begin = mysqli_query($connect, "START TRANSACTION");
            
            $majtraite = mysqli_query($connect, "UPDATE event SET Traite=1 WHERE Id='$idoperation' LIMIT 1");
            
            // .... On effectue diverses opérations
            // ....
            // ... ayé c'est fini
            
            if( $requetecom = mysqli_query($connect, "DELETE FROM event WHERE Id='$idoperation' LIMIT 1") )
            {
                $commit = mysqli_query($connect, "COMMIT");
            }
            else
            {
                $rollback = mysqli_query($connect, "ROLLBACK");
            }
        }
    }
}

Toujours certaines opérations qui se dupliquent lors d'accès concurrentiels ...

Si quelqu'un à une idée ... je suis preneur ... Sad ...


RE: Probleme de transaction .. :( - Anthor - 26-02-2010

Code PHP :
<?php 
if( $requetecom = mysqli_query($connect, "DELETE FROM event WHERE Id='$idoperation' LIMIT 1") )

Ceci est toujours vrai tant que tu n'as pas d'erreur SQL. Je t'ai dit de regarder le nombre de résultats effectivement supprimé. Avec mysqli, tu dois utiliser mysqli_affected_rows, mysqli_query ne retournant que TRUE ou FALSE ou un objet

Citation :Returns FALSE on failure. For successful SELECT, SHOW, DESCRIBE or EXPLAIN queries mysqli_query() will return a result object. For other successful queries mysqli_query() will return TRUE.

Avec un bon ORM tu n'aurais pas ce problème, car le nombre de lignes affectées est retourné par défaut.