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 la version admissible 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. Toutefois, le jeu de fonctionnalités inclus dans la version finale de MySQL 5.6 est insuffisant pour supporter toutes les idées discutées ici. Veuillez vous référer à la section sur les concepts pour plus de détails.
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.
PECL/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
CREATE TABLE `trx` ( `trx_id` int(11) DEFAULT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=latin1 INSERT INTO `trx`(`trx_id`) VALUES (1);
Dans le fichier de configuration, utilisez on_commit pour insérer le SQL qui mettra à jour le GTID, section global_transaction_id_injection. Assurez vous que le nom de table pour la commande UPDATE est pleinement qualifié. Dans l'exemple, test.trx est utilisé pour faire référence à la table trx dans la base test. Le nom pleinement qualifié est important car la connexion sur laquelle la requête sera éxecutée peut changer et représenter une base différente. Assuez-vous aussi des droits de l'utilisateur qui ouvre la connexion pour l'éxecution de commandes UPDATE.
Activez le rapport d'erreurs concernant l'injection de GTID.
Exemple #2 Plugin config: SQL pour l'injection GTID coté client
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } }, "global_transaction_id_injection":{ "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1", "report_error":true } } }
Exemple #3 Injection GTID transparente
<?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());
?>
L'exemple ci-dessus va afficher :
array(1) { ["id"]=> string(1) "1" }
L'exemple lance 3 requêtes en mode autocommit sur le maitre, causant 3 transactions sur le maitre. Pour châque requête, le plugin injectera l'UPDATE configuré de manière transparente avant d'exécuter la requête utilisateur. Lorsque le script se termine, le GTID a été incrémenté de 3 sur le maitre.
La 4ème requête exécutée dans l'exemple, un SELECT, ne déclenche pas l'incrémentation. Seules les écritures sur le maitre déclenchent l'incrémentation.
Note: SQL pour GTID: Solution efficace demandée!
Le SQL utilisé pour le GTID n'est pas efficace. Il est conçu pour être lisible, pas performant. Considérez une solution plus robuste et reportez-la, nous l'incluerons dans le manuel.
Exemple #4 Plugin config: SQL pour récupérer le GTID
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } }, "global_transaction_id_injection":{ "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1", "fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx", "report_error":true } } }
Exemple #5 Obtenir le GTID après l'injection
<?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));
?>
L'exemple ci-dessus va afficher :
GTID après transaction 7 GTID après transaction 8
Les applications peuvent demander à mysqlnd_ms le GTID correspondant à la dernière écriture. La fonction mysqlnd_ms_get_last_gtid() retourne le GTID obtenu lors de l'éxecution d'une requête SQL depuis l'entrée fetch_last_gtid de la section global_transaction_id_injection du fichier de configuration. La fonction doit être appelée après que le GTIF ait été incrémenté.
Les applications ne doivent pas exécuter le SQL elles-mêmes car elles risquent alors de causer une incrémentation accidentelle du GTID. Aussi, si la fonction est utilisée, il est plus simple de migrer une application.
Le guide de démarrage rapide montre une requête SQL qui retourne un GTID supérieur ou égal à celui crée pour la requête précédente. Il s'agit du GTID de la requête précédente si aucun client ne l'ont incrémenté entre temps jusqu'au SELECT le récupérant, sinon, il est plus élevé.
Exemple #6 Plugin config: Vérifier un certain GTID
{ "myapp": { "master": { "master_0": { "host": "localhost", "socket": "\/tmp\/mysql.sock" } }, "slave": { "slave_0": { "host": "127.0.0.1", "port": "3306" } }, "global_transaction_id_injection":{ "on_commit":"UPDATE test.trx SET trx_id = trx_id + 1", "fetch_last_gtid" : "SELECT MAX(trx_id) FROM test.trx", "check_for_gtid" : "SELECT trx_id FROM test.trx WHERE trx_id >= #GTID", "report_error":true } } }
Exemple #7 Consistence de session et GTID combinés
<?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
Note: Support serveur insuffisant dans MySQL 5.6
Le greffon a été développé sur la base d'une version de pre-production de MySQL 5.6. Or il s'avère que les versions MySQL 5.6 publiées finalement ne fournissent pas des clients avec assez d'informations pour imposer la consistence des sessions basés sur les GTIDs. Veuillez vous référer à la section sur les concepts pour plus de détails.
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 dans certains cas mais pas dans tous! 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 } } }