Imaginez un site marchand sur lequel olivier@email.fr a un compte et sur lequel il fait un achat d'ordinateur, au moment de la validation et du paiement, nous écrivons en base de données :

mysql_query("INSERT INTO commandes (produit,prix,compte) VALUES ('Ordinateur',699,'olivier@email.fr')");
...
mysql_query("UPDATE comptes SET montant = montant + 699 WHERE email = 'olivier@email.fr')");

Question : que se passe-t-il si la connexion à la base de données est perdu lors de la première requête (problème réseau par exemple) ou bien que le nombre de visiteurs sur votre site marchand est tellement important que le nombre de connexion base de données maximum est atteint et que PHP retourne une erreur sur la première requête SQL (du style "unable to connect to ...) ?

Ce qui va se passer : vous allez avoir une erreur de type warning (si vous les avez désactivées en prod, elle sera passée sous silence et juste inscrite dans le fichier de log de php), la deuxième requête va alors être exécutée, et par chance, la connexion refonctionne (les problèmes de charge peuvent être très volatile). Vous allez alors vous retrouver avec un problème d'intégrité en base de données (la commande n'aura pas été ajouté mais le montant des achats du compte aura été mis à jour). Les effets de bord peuvent être alors multiples et dépendent de la logique qui suit. Si vous affichez dans l'espace client de l'utilisateur le montant de ses achats, vous aurez des valeurs erronées et il manquera une des commandes de l'utilisateur.

Comment faire ?

Il est nécessaire de vérifier systématiquement le statut de l'exécution de la requête que vous avez effectuée :

$result = mysql_query("INSERT INTO commandes (produit,prix,compte) VALUES ('Ordinateur',699,'olivier@email.fr')");
if (false === $result) {
    // une erreur d'exécution de la requête SQL a été détecté, il est nécessaire de ne pas continuer le processus et de traiter l'erreur
    // une façon apropriée est de lancer une exception, qui sera "catchée" par la méthode ou la fonction appelante
    throw new RuntimeException("Unable to add commande to database: ".mysql_error());
}
...
$result = mysql_query("UPDATE comptes SET montant = montant + 699 WHERE email = 'olivier@email.fr')");
if (false === $result) {
    // une erreur d'exécution de la requête SQL a été détecté, il est nécessaire de ne pas continuer le processus et de traiter l'erreur
    // une façon apropriée est de lancer une exception, qui sera "catchée" par la méthode ou la fonction appelante
    throw new RuntimeException("Unable to update comptes in database: ".mysql_error());
}
...

D'accord, mais que se passe-t-il si l'erreur survient sur la deuxième requête SQL ? nous allons bien détecter l'erreur mais la première requête en base (l'insertion) aura déjà été effectuée et nous aurons donc une inconsistance en base de données si nous ne réalisons pas la seconde requête. Il est donc nécessaire d'introduire la notion de transaction, c'est à dire d'une séquence de requêtes considérées comme atomique (indissociable) :

$result = mysql_query("BEGIN");
if (false === $result) {
    // impossible de commencer la transaction, on doit afficher une page d'erreur générique au client par exemple
    throw new RuntimeException("Unable to begin transaction: ".mysql_error());
}

$result = mysql_query("INSERT INTO commandes (produit,prix,compte) VALUES ('Ordinateur',699,'olivier@email.fr')");
if (false === $result) {
    // une erreur d'exécution de la requête SQL a été détecté, il est nécessaire de ne pas continuer le processus et de traiter l'erreur
    // une façon apropriée est de lancer une exception, qui sera "catchée" par la méthode ou la fonction appelante
    mysql_query("ROLLBACK");
    throw new RuntimeException("Unable to add commande to database: ".mysql_error());
}
...
$result = mysql_query("UPDATE comptes SET montant = montant + 699 WHERE email = 'olivier@email.fr')");
if (false === $result) {
    // une erreur d'exécution de la requête SQL a été détecté, il est nécessaire de ne pas continuer le processus et de traiter l'erreur
    // une façon apropriée est de lancer une exception, qui sera "catchée" par la méthode ou la fonction appelante
    mysql_query("ROLLBACK");
    throw new RuntimeException("Unable to update comptes in database: ".mysql_error());
}
...

$result = mysql_query("COMMIT");
if (false === $result) {
    // impossible de commiter la transaction. aucune donnée ne sera inserée/modifiée en base
    // il est nécessaire d'afficher une page d'erreur à l'utilisateur pour lui indiquer que sa commande n'a pas été prise en compte
    throw new RuntimeException("Unable to commit transaction to database: ".mysql_error());
}

Ce type de technique est intéressant car il sécurise et rend votre application plus robuste. Un point cependant a noter est que l'application deviendra alors moins silencieuse au problème et le moindre problème de connexion ou de charge sera tout de suite mis en exergue et déclenchera des exceptions. A vous donc de vérifier systématiquement dans vos méthodes/fonctions appelantes qu'il n'y a pas eu une exception.

Les fonctions mysql_query/mysql_error ont été utilisée pour l'exemple, mais cette technique s'applique sans soucis à des frameworks/lirairies d'abstraction de connexion au base de données tels que PDO, Zend_Db (Zend Framework), ... Il faut toujours vérifier dans la documentation la valeur ou le type de retour d'une fonction/méthode d'appel à la base base de données en cas d'erreur (les fonctions standards PHP retourne souvent FALSE au lieu d'une ressource si un problème est survenu en plus de lancer un warning).

Et vous quelles sont vos techniques pour gérer ce genre de problème ?