Transaction ACID (Atomique , Cohérente, Isolée, Durable)

Des mécanismes permettent d'obtenir une suite d'opérations qui soit globalement à la fois atomique, cohérente, isolée et durable (ACID).

  • atomique : la suite d'opérations est indivisible, en cas d'échec en cours d'une des opérations, la suite d'opérations doit être complétement annulée (rollback) quel que soit le nombre d'opérations déja bien réalisées.
  • cohérente : le contenu de la base de données à la fin de la transaction doit être cohérent sans pour autant que chaque opération durant la transaction donne un contenu cohérent. Un contenu final incohérent entraînera l'échec et l'annulation de toutes opérations de la transaction.
  • isolée : lorsque deux transactions A et B sont exécutées en même temps, les modifications effectuées par A ne sont ni visibles par B, ni modifiables par B tant que la transaction A n'est pas terminée et validée (commit).
  • durable : d'un point de vue technique, une transaction terminée ne peut pas être remise en cause, annulée ou recouverte. Lorsque deux transactions sont exécutées en même temps, le résultat de la première transaction ne pourra pas être recouvert par la deuxième. Toute tentative de recouvrement entraînera l'annulation des opérations de la transaction fautive.

Transactions avec SQL

-- on lance la transaction
START TRANSACTION;
 
-- on exécute toutes les requêtes que l'on veut
-- SELECT / UPDATE / INSERT / DELETE ...
 
-- on valide la transaction
COMMIT;
 
-- ou en cas d'erreur
-- on annule la transaction
ROLLBACK ;

Transactions MySQL

MyIsam ne supporte ni les clefs étrangères, ni les transactions, pour permettre les transactions il faut utiliser le moteur de stockage InnoDB.

PHP

$pdo->beginTransaction();
$pdo->query('UPDATE table SET colonne = colonne + 1');
$pdo->commit();
// ou en cas d'erreur
$pdo->rollback();

Visiblement, le PDO::getAttribute() ne nous donne aucune information sur l'état de la transaction (si une transaction à déjà été lancé ou non avec PDO::beginTransaction()) :

echo "--------------------------------------------\n";
echo "PDO::ATTR_AUTOCOMMIT:".$pdo->getAttribute(PDO::ATTR_AUTOCOMMIT)."\n";
echo "PDO::ATTR_CASE:".$pdo->getAttribute(PDO::ATTR_CASE)."\n";
echo "PDO::ATTR_CLIENT_VERSION:".$pdo->getAttribute(PDO::ATTR_CLIENT_VERSION)."\n";
echo "PDO::ATTR_CONNECTION_STATUS:".$pdo->getAttribute(PDO::ATTR_CONNECTION_STATUS)."\n";
echo "PDO::ATTR_DRIVER_NAME:".$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)."\n";
echo "PDO::ATTR_ERRMODE:".$pdo->getAttribute(PDO::ATTR_ERRMODE)."\n";
echo "PDO::ATTR_ORACLE_NULLS:".$pdo->getAttribute(PDO::ATTR_ORACLE_NULLS)."\n";
echo "PDO::ATTR_PERSISTENT:".$pdo->getAttribute(PDO::ATTR_PERSISTENT)."\n";
//echo "PDO::ATTR_PREFETCH:".$pdo->getAttribute(PDO::ATTR_PREFETCH)."\n";	// MySQL : PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute'
echo "PDO::ATTR_SERVER_INFO:".$pdo->getAttribute(PDO::ATTR_SERVER_INFO)."\n";
echo "PDO::ATTR_SERVER_VERSION:".$pdo->getAttribute(PDO::ATTR_SERVER_VERSION)."\n";
//echo "PDO::ATTR_TIMEOUT:".$pdo->getAttribute(PDO::ATTR_TIMEOUT)."\n"; // MySQL : PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[IM001]: Driver does not support this function: driver does not support that attribute'
echo "--------------------------------------------\n";
//--------------------------------------------
//PDO::ATTR_AUTOCOMMIT:1
//PDO::ATTR_CASE:0
//PDO::ATTR_CLIENT_VERSION:mysqlnd 5.0.7-dev - 091210 - $Revision: 300533 $
//PDO::ATTR_CONNECTION_STATUS:mysql51.devel via TCP/IP
//PDO::ATTR_DRIVER_NAME:mysql
//PDO::ATTR_ERRMODE:2
//PDO::ATTR_ORACLE_NULLS:0
//PDO::ATTR_PERSISTENT:
//PDO::ATTR_SERVER_INFO:Uptime: 141279  Threads: 2  Questions: 1128850  Slow queries: 2950  Opens: 104575  Flush tables: 2  Open tables: 64  Queries per second avg: 7.990
//PDO::ATTR_SERVER_VERSION:5.1.51-0.dotdeb.1-log
//--------------------------------------------
Attention !

En ATTR_AUTOCOMMIT === false (sans auto commit), si l'on fait une requête sans le beginTransaction(), elle sera ignorée, et il n'est pas possible de faire un commit() car aucune transaction n'est active. Par contre, elle sera commitée si on ouvre une nouvelle transaction sur ce même connecteur (aussi bien avec $pdo->query("START TRANSACTION"); que $pdo->beginTransaction(); )

Singleton PDO

Doctrine

Doctrine 2 permet aussi (et vous encourage) de prendre en charge le contrôle des transactions vous-même.

Beaucoup de gens utilisent le mode autocommit sans être vraiment conscients de ce qu'il fait. Il ne veut pas dire qu'il n'y a pas de transaction à moins que vous émettez sur START/BEGIN TRANSACTION ou PDO::beginTransaction(). Cela veut dire après chaque requête unique la transaction est validée automatiquement et une nouvelle a commence. Des méthodes telles que PDO::beginTransaction() vont simplement suspendre le mode de validation automatique pour une courte durée (jusqu'à ce que vous appelez PDO::commit()/PDO::rollback()).

Pour résumer, il est impossible de communiquer vers votre base de données en dehors d'une transaction.

Doctrine 2 peut aider beaucoup ici. Vous pouvez modifier vos objets partout, persistent et supprimer des objets en tout lieu et cela en une seul fois lorsque que vous appelez EntityManager::flush(). Doctrine 2 fera efficacement toutes les mises à jour en une seule transaction.

Selon le PDF Doctrine ORM for PHP

L'invocation directe de PDO::beginTransaction(), PDO::commit () ou PDO::rollback() ou les méthodes correspondantes sur l'instance \DBAL\Driver\Connection Doctrine particulière en cours d'utilisation court-circuite la transaction transparente de nidification qui est fourni par Doctrine\DBAL\Connection et peut donc corrompre le niveau d'imbrication, provoquant des erreurs avec des limites de transaction brisées qui peuvent être difficiles à déboguer.