## Transactions distribuées / XA

> Note: Requis pour la version

> Les fonctions relatives à XA ont été introduites en PECL mysqlnd_ms version 1.6.0-alpha.

> Note: Béta-testeur

> Cette fonctionnalité est actuellement en cours de développement. Il peut y avoir des bogues ou des limitations dans les fonctionnalités. Ne pas utiliser en environnement de production, malgré tout, les premiers tests indiquent une qualité raisonnable.

> Veuillez contacter le groupe de développement si vous êtes intéressé par cette fonctionnalité. Nous sommes à la recherche de retour de véritables environnements pour compléter cette fonctionnalité.

Les transactions XA sont des méthodes standardisées pour l'exécution de transactions via plusieurs ressources. Ces ressources peuvent êre des bases de données ou tout autre système transactionnel. Le serveur MySQL supporte les requêtes SQL XA qui permettent aux utilisateurs de mener à bien une transaction distribuée SQL qui touchent plusieurs serveurs de base de données ou tout autre système supportant les requêtes SQL. Dans un tel cas, il en est de la responsabilité de l'utilisateur de coordonner les serveurs participants.

PECL/mysqlnd_ms peut agir comme un coordinateur de transaction pour une transaction global (distribuée, XA) menée sur des serveurs MySQL seulement. Comme coordinateur de transaction, le plugin surveille tous les serveurs participants à la transaction globale, et envoie les requêtes SQL appropriées en toute transparence aux participants. Les transactions globales sont contrôlées avec mysqlnd_ms_xa_begin(), mysqlnd_ms_xa_commit() et mysqlnd_ms_xa_rollback(). Les détails SQL sont cachées de l'application ainsi que la nécessité de suivre et coordonner les participants.

Exemple #1 Masque général pour les transactions XA

`<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli) {
/* Bien évidemment, votre gestionnaire d'erreurs est bien meilleur... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Démarre une transaction globale */
$gtrid_id = "12345";
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error));
}
/* Exécute les requêtes comme d'habitude : XA BEGIN sera injecté lors de l'exécution de la requête */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
/* Soit l'INSERT échoue, soit l'injection de XA BEGIN échoue */
if ('XA' == substr($mysqli->sqlstate, 0, 2)) {
printf("Global transaction/XA related failure, [%d] %s\n", $mysqli->errno, $mysqli->error);
} else {
printf("INSERT failed, [%d] %s\n", $mysqli->errno, $mysqli->error);
}
/* Annulation de la transaction globale */
mysqlnd_ms_xa_rollback($mysqli, $xid);
die("Stopping.");
}
/* Continue de mener à bien les requêtes sur les autres serveurs, i.e. les autres serveurs partagés */
/* commit the global transaction */
if (!mysqlnd_ms_xa_commit($mysqli, $xa_id)) {
printf("[%d] %s\n", $mysqli->errno, $mysqli->error);
}
?>`

Contrairement aux transactions locales, qui sont envoyées à un seul serveur, les transactions XA ont un identifiant (xid) associé. L'identifiant de transaction XA est composé d'un identifiant de transaction globale (gtrid), un qualificatif de branche (bqual), ainsi qu'un identifiant de format (formatID). Seul l'identifiant de transaction globale peut et doit être fourni lors de l'appel à n'importe laquelle des fonctions XA du plugin.

Une fois qu'une transaction globale a commencé, le plugin commence à surveiller les serveurs tant que la transaction globale n'est pas terminée. Lorsqu'un serveur est sélectionné pour l'exécution de la requête, le plugin injecte la requête SQL XA BEGIN avant d'exécuter la requête SQL sur le serveur. XA BEGIN rend le serveur participant à la transaction globale. Si la requête SQL injectée échoue, le plugin va rapporter l'erreur dans la réponse à la fonction utilisée pour l'exécution de la requête. Dans l'exemple ci-dessus, $mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')") va indiquer une telle erreur. Vous pouvez vouloir vérifier le code statut des erreurs SQL pour déterminer si la requête actuelle a échoué (ici : INSERT) ou si l'erreur est en relation avec la transaction globale. Il vous appartient de choisir d'ignorer l'erreur pour commencer la transaction globale sur un serveur et continuer l'exécution sans que le serveur ne participe à la transaction globale.

Exemple #2 Les transactions locales et globales sont mutuellement exclusives

`<?php
$mysqli = new mysqli("myapp", "username", "password", "database");
if (!$mysqli) {
/* Bien évidemment, votre gestionnaire d'erreurs est bien mieux... */
die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()));
}
/* Démarre une transaction locale */
if (!$mysqli->begin_transaction()) {
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
}
/* Impossible de commencer la transaction globale maintenant - on doit terminer la transaction locale d'abord */
$gtrid_id = "12345";
if (!mysqlnd_ms_xa_begin($mysqli, $gtrid_id)) {
die(sprintf("[%d/%s] %s\n", $mysqli->errno, $mysqli->sqlstate, $mysqli->error));
}
?>`

L'exemple ci-dessus va afficher :

    Warning: mysqlnd_ms_xa_begin(): (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first in ... on line ...
    [1400/XAE09] (mysqlnd_ms) Some work is done outside global transaction. You must end the active local transaction first

Une transaction globale ne peut être démarrée lorsqu'une transaction locale est active. Le plugin tente de détecter cette situation au plus tôt, i.e. lors de l'appel à la fonction mysqlnd_ms_xa_begin(). Si vous utilisez uniquement des appels API pour contrôler les transactions, le plugin va savoir qu'une transaction locale est ouverte, et va retourner une erreur pour mysqlnd_ms_xa_begin(). Cependant, notez les limitations sur les limites des transactions. Dans le pire des cas, si vous utilisez des requêtes SQL directes pour les transactions locales (BEGIN, COMMIT, ...), il se peut qu'une erreur ne soit relayée pendant que des requêtes SQL soient exécutées sur un serveur.

Pour terminer une transaction globale, appelez la fonction mysqlnd_ms_xa_commit() ou mysqlnd_ms_xa_rollback(). Lorsqu'une transaction globale se termine, tous les participants doivent être informés de cette fin. Toutefois, PECL/mysqlnd_ms va envoyer les requêtes SQL XA appropriées sur une partie ou la totalité des participants. Toute erreur pendant cette phase va engendrer une annulation implicite. L'API relative à XA est intentionnellement simple ici. Une API plus complexe, qui donne plus de contrôle, aurait peu d'avantage par rapport à une implémentation utilisateur qui se charge lui même des requêtes SQL XA bas niveaux.

Les transactions XA utilisent un protocole de validation en deux phases. Le protocole de validation en deux phases est un protocole bloquant. Il y a des cas où aucune progression ne peut avoir lieu, y compris en utilisant des délais maximals d'attente. Les coordinateurs de transaction doivent survivre à leur propre échec, être capable de détecter les blocages et de rompre des liens. PECL/mysqlnd_ms prend le rôle de coordinateur de transaction et peut être configuré pour survivre à son propre échec pour éviter des soucis avec les serveurs MySQL bloqués. Toutefois, le plugin peut et doit être configuré pour utiliser un statut persistent et résistant au crash pour permettre la collection des données incorrectes, et stopper les transactions globales. Une transaction globale peut être stoppée dans un statut ouvert si, soit le plugin échoue (crash), soit une connexion depuis le plugin vers un participant de la transaction globale échoue.

Exemple #3 Stockage du statut du coordinateur de transaction

    {
        "myapp": {
            "xa": {
                "state_store": {
                    "participant_localhost_ip": "192.168.2.12",
                    "mysql": {
                        "host": "192.168.2.13",
                        "user": "root",
                        "password": "",
                        "db": "test",
                        "port": "3312",
                        "socket": null
                    }
                }
            },
            "master": {
                "master_0": {
                    "host": "localhost",
                    "socket": "\/tmp\/mysql.sock"
                }
            },
            "slave": {
                "slave_0": {
                    "host": "192.168.2.14",
                    "port": "3306"
                }
            }
        }
    }

Actuellement, PECL/mysqlnd_ms supporte uniquement les tables de base de données MySQL comme espace de stockage de statut. Les définitions des tables SQL sont fournies dans la section de configuration du plugin. Assurez-vous d'utiliser un moteur transactionel et résistant au crash pour les tables, comme InnoDB. InnoDB est le moteur de table par défaut dans les versions récentes du serveur MySQL. Assurez-vous également que le serveur de base de données lui-même a une haute disponibilité.

Si un stockage de statut a été configuré, le plugin peut effectuer une collection des données incorrectes. Pendant la collection de données incorrectes, il peut être nécessaire de se connecter à un participant à une transaction globale qui a échouée. Aussi, le stockage de staut accueille une liste des participants et, entre autres, leurs noms d'hôte. Si la collection de données incorrectes est exécutée sur un autre hôte que celui qui a été noté comme participant avec le nom d'hôte localhost, alors localhost va résoudre différentes machines. Il y a deux solutions à ce problème. Soit vous ne configurez aucun serveur avec le nom d'hôte localhost mais configurez une adresse IP (et un port), ou, vous utilisez la collection de données incorrectes. Dans l'exemple ci-dessus, localhost est utilisé pour master_0, et va résourdre le mauvais hôte durant la collection des données incorrectes. Cependant, participant_localhost_ip est également défini pour utiliser la collection de données incorrectes où localhost correspond à l'IP 192.168.2.12.