ID de transaction globale
Note: Version requise
L'injection d'identifiant de transaction globale côté client a été ajoutée avec mysqlnd_ms version 1.2.0-alpha. Ce n'est pas requis pour les clusters synchrones, comme MySQL Cluster. Utilisez le avec des clusters asynchrones, comme la réplication MySQL.
Depuis MySQL 5.6.5-m8, le serveur MySQL fournit des identifiants de transaction globale. Cette fonctionnalité est supportée par PECL/mysqlnd_ms 1.3.0-alpha ou supérieure.
PECL/mysqlnd_ms peut soit utiliser sa propre émulation des identifiants de transaction globale, soit la fonctionnalité interne du serveur MySQL 5.6.5-m8 ou supérieure. D'un point de vue développeur, ces différentes approches offrent les mêmes fonctionnalités au niveau des niveaux de service fournis par PECL/mysqlnd_ms. Leurs différences sont abordées dans la section sur les concepts.
La section sur le démarrage rapide montre l'utilisation de l'émulation de l'identifiant de transaction globale côté client interne à PECL/mysqlnd_ms avant de montrer son homologue côté serveur. Cet ordre assure que l'idée originelle soit abordée en premier lieu.
Idée et émulation côté client
Dans sa forme de base, un identifiant de transaction globale (GTID) est un compteur dans une table sur le maitre. Le compteur est incrémenté chaque fois qu'une transaction est commitée sur le maitre. Les esclaves répliquent la table. Le compteur a deux rôles. Dans le cas d'un échec du maitre, il aide l'administrateur à identifier l'esclave le plus récent pour le promouvoir nouveau maitre. L'esclave le plus récent est celui qui possède la plus grande valeur de l'identifiant. L'application peut utiliser le GTID pour chercher les esclaves ayant répliqué une certaine écriture identifiée par le GTID.
mysqlnd_ms peut injecter du SQL pour toute transaction comitée afin d'incrémenter le GTID. Le GTID est accessible par l'application pour identifier une opération d'écriture. Ceci permet au plugin d'assurer le niveau de service consistence de session en requêtant les esclaves qui ont répliqué la donnée. La charge de lecture est donc supprimée du maitre.
l'émulation GTID coté client a quelques limites, lisez attentivement la section sur les concepts pour bien comprendre les principes et les idées avant de l'utiliser en production.
D'abord, créez une table compteur sur votre maitre et insérez-y un enregistrement. Le plugin ne créer pas la table. Les administrateurs doivent s'assurer qu'elle existe. En fonction du mode de rapport d'erreur, le plugin ignorera éventuellement silencieusement l'absence de table, ou s'arrêtera brusquement.
Exemple #1 Creation de la table de compteur sur le maitre
<?php $mysqli = new mysqli("myapp", "username", "password", "database"); if (!$mysqli) /* Utilisez ici votre propre gestion des erreurs... */ die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error())); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("DROP TABLE IF EXISTS test")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("CREATE TABLE test(id INT)")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); /* auto commit mode, lecture sur esclave, pas d'incrémentation */ if (!($res = $mysqli->query("SELECT id FROM test"))) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); var_dump($res->fetch_assoc()); ?>
<?php $mysqli = new mysqli("myapp", "username", "password", "database"); if (!$mysqli) /* Utilisez ici votre propre gestion des erreurs... */ die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error())); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("DROP TABLE IF EXISTS test")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); printf("GTID after transaction %s\n", mysqlnd_ms_get_last_gtid($mysqli)); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("CREATE TABLE test(id INT)")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); printf("GTID après transaction %s\n", mysqlnd_ms_get_last_gtid($mysqli)); ?>
<?php $mysqli = new mysqli("myapp", "username", "password", "database"); if (!$mysqli) /* Utilisez ici votre propre gestion des erreurs... */ die(sprintf("[%d] %s\n", mysqli_connect_errno(), mysqli_connect_error())); /* auto commit mode, transaction sur le maitre, GTID doit être incrémenté */ if (!$mysqli->query("DROP TABLE IF EXISTS test") || !$mysqli->query("CREATE TABLE test(id INT)") || !$mysqli->query("INSERT INTO test(id) VALUES (1)")) die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); /* GTID comme identifiant pour la dernière écriture */ $gtid = mysqlnd_ms_get_last_gtid($mysqli); /* Consistence de session ("lit tes écritures"): essaye de lire depuis les esclaves, pas seulement le maitre */ if (false == mysqlnd_ms_set_qos($mysqli, MYSQLND_MS_QOS_CONSISTENCY_SESSION, MYSQLND_MS_QOS_OPTION_GTID, $gtid)) { die(sprintf("[006] [%d] %s\n", $mysqli->errno, $mysqli->error)); } /* Exécute sur le maitre ou un des esclaves ayant la donnée */ if (!($res = $mysqli->query("SELECT id FROM test"))) { die(sprintf("[%d] %s\n", $mysqli->errno, $mysqli->error)); } var_dump($res->fetch_assoc()); ?>
Un GTID retourné par mysqlnd_ms_get_last_gtid() peut être utilisé comme option dans le niveau de service consistence de session, indiqué au moyen de mysqlnd_ms_set_qos(). Dans l'exemple, le plugin exécute la requête SELECT soit sur le maitre, soit sur l'esclave qui possède la donnée répliquée.
PECL mysqlnd_ms va vérifier de manière transparente chaque esclave configuré pour savoir s'il possède la donnée répliquée de l'INSERT au moyen de la table des GTID. La vérification utilise la requête SQL indiquée par l'option check_for_gtid de la section global_transaction_id_injection du fichier de configuration. Notez qu'il s'agit s'une opération lente et couteuse, les applications doivent l'utiliser à bon escient si la charge en lecture sur le maitre devient trop importante.
Utilisation de la fonctionalité d'identifiant de transaction globale côté serveur
Depuis MySQL 5.6.5-m8, le système de réplication MySQL utilise des identifiants globaux de transaction côté serveur. Ces identifiants de transaction sont automatiquement générés et maintenus par le serveur. Les utilisateurs n'ont pas à s'inquiéter de les maintenir. Il n'y a besoin d'avoir des tables de disponible à l'avance, ou de configuration sur le on_commit. L'émulation côté client n'est plus nécessaire non plus.
Les clients peuvent continuer d'utiliser l'identifiant de transaction globale dans un but de consistence des sessions lors des opérations de lecture sur les esclaves de réplication MySQL. L'algorithme fonctionne tel que décrit ci-après. Des requêtes SQL différentes doivent être configurées pour fetch_last_gtid et check_for_gtid. Ces requêtes sont fournies ci-dessous. Notez que MySQL 5.6.5-m8 est une version de développement. Les détails sur l'implémentation serveur peuvent changer dans le futur et nécessite une adoption des requêtes SQL utilisées.
En utilisant la configuration suivante, n'importe quelle fonctionalité décrite ci-dessous peut être utilisée en plus de la fonctionalité des identifiants de transaction globale côté serveur. mysqlnd_ms_get_last_gtid() et mysqlnd_ms_set_qos() continuent de fonctionner tel que décrit. La seule différente est que le serveur n'utilise plus qu'une simple séquence de numéro mais une chaîne contenant un identifiant de serveur et une séquence de numéro. Aussi, les utilisateurs ne peuvent plus déduire un ordre depuis les GTIDs retournés par la fonction mysqlnd_ms_get_last_gtid().
Exemple #8 Configuration du plugin : utilisation de la fonctionalité GTID interne à MySQL 5.6.5-m8
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } }, "global_transaction_id_injection":{ "fetch_last_gtid" : "SELECT @@GLOBAL.GTID_DONE AS trx_id FROM DUAL", "check_for_gtid" : "SELECT GTID_SUBSET('#GTID', @@GLOBAL.GTID_DONE) AS trx_id FROM DUAL", "report_error":true } } }