ID de transacción globales

Nota: Requisitos de versión

Se ha introducido una inyección de ID de transacciones global en el lado del cliente en la versión 1.2.0-alpha de mysqlnd_ms. Esta característica no es necesaria para clústeres sincrónicos, tales como el Clúster MySQL. Utilícela con clústeres asíncronos tales como la replicación MySQL clásica.

A partir de la versión candidata MySQL 5.6.5-m8, el servidor MySQL introduce los identificadores de transacciones globales internos. La característica del ID de transacciones global interno está soportada por PECL/mysqlnd_ms 1.3.0-alpha o posterior. Sin embargo, el conjunto final de características encontrado en las versiones de producción de MySQL 5.6 hasta la fecha, no es suficiente para soportar las ideas tratadas abajo en todos los casos. Véase también la sección de conceptos.

PECL/mysqlnd_ms puede usar tanto su propia emulación del ID de transacciones global como la característica del ID de transacciones global interna de MySQL 5.6.5-m8 o posterior. Desde la perspectiva del desarrolador, los enfoques del lado del cliente y del lado del servidor ofrecen las mismas características en cuanto a niveles de servicios proporcionados por PECL/mysqlnd_ms. Las diferencias son tratadas en la sección de conceptos.

Esta guía rápida primero demuestra el uso de la emulación interna del ID de transacciones global en el lado del cliente en PECL/mysqlnd_ms antes de mostrar cómo usar su homólogo en el lado del servidor. Este orden asegura que la idea subyacente se trata primero.

La idea y la emulación en el lado del cliente

En su forma más básica, un ID de Transacciones Global (GTID de sus siglas en inglés) es un contador de una tabla del maestro. El contador se incrementa siempre que se consigne una transacción en el maestro. Los esclavos replican la tabla. El contador sirve para dos propósitos. En caso de un fallo del maestro, ayuda al administrador de la base de datos a identificar al esclavo que fue promovido más recientemente como nuevo maestro. Este esclavo es aquel con el valor de contador más alto. Las aplicaciones pueden usar el ID de transacciones global para buscar los esclavos que ya han replicado una escritura en particular (identificada por un ID de transacciones global).

PECL/mysqlnd_ms puede inyectar SQL para cada transacción consignada para incrementar un contador GTID. El ID creado es accesible por la aplicación para poder identificar una operación de escritura de una aplicación. Esto habilita al complemento para proporcionar el nivel de servicio de consistencia de sesión (lectura de sus escrituras) mediante no solamente la consulta a los maestros, sino también a los esclavos que ya han replicado el cambio. La carga de lectura se le quita al maestro.

La emulación del ID de transacciones global en el lado del cliente tiene algunas limitaciones. Por favor, lea la sección de conceptos detenidamente para comprender completamente los principios y las ideas subyacentes antes de usarla en entornos de producción. No es necesario un conocimiento profundo para continuar con esta guía rápida.

Primero, cree una tabla contador en su servidor maestro e inserte un registro en ella. El complemento no asiste en la creación de la tabla. Los administradores de la base de datos deben asegurarse de que existe. Dependiendo del modo de notificación de errores, el complemento ignorará de forma silenciosa la ausencia de la tabla o abandonará.

Ejemplo #1 Crear una tabla contador en el maestro

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);

En el fichero de configuración del complemento establezca el SQL para actualizar la tabla del ID de transacciones global usando on_commit en la sección global_transaction_id_injection. Asegúrese de que el nombre de la tabla usado para la sentencia UPDATE está completamente cualificado. En el ejemplo, test.trx se usa para referirse a la tabla trx del esquema (base de datos) test. Use la tabla que se creó en el paso anterior. Es importante establecer el nombre de la tabla completamente cualificado, ya que la conexión donde se realiza la inyección puede usar una base de datos predeterminada diferente. Asegúrese de que al usuario que abra la conexión se le permita ejecutar la sentencia UPDATE.

Habilite la notificación de los errores que pudieran ocurrir cuando mysqlnd_ms realice inyecciones de ID de transacciones global.

Ejemplo #2 Configuración del complemento: SQL para la inyección del GTID en el lado del cliente

{
    "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
        }
    }
}

Ejemplo #3 Inyección transparente del ID de transacciones global

<?php
$mysqli 
= new mysqli("myapp""nombre_usuario""contraseña""base_datos");
if (!
$mysqli) {
    
/* Por supuesto, su manejo de errores es mejor... */
    
die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
}

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("INSERT INTO test(id) VALUES (1)")) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

/* modo autoconsigna, lectura en el esclavo, sin incremento */
if (!($res $mysqli->query("SELECT id FROM test"))) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

var_dump($res->fetch_assoc());
?>

El resultado del ejemplo sería:

array(1) {
  ["id"]=>
  string(1) "1"
}

El ejemplo ejecuta tres sentencias en el modo autoconsigna en el maestro, causando tres transacciones en el maestro. Para cada sentencia, el complemento inyectará el UPDATE configurado de forma transparente antes de ejecutar las sentencias SQL del usuario. Cuando finaliza el script, el contador de IDs de transacciones globales en el maestro ha sido incrementado en tres.

La cuarta sentencia SQL ejecutada en el ejemplo, un SELECT, no desencadena un incremento. Solamente las transacciones (escrituras) ejecutadas en un maestro incrementarán el contador GTID.

Nota: SQL para el ID de transacciones global: ¡se busca una solución eficiente!

El SQL usado para la emulación del ID de transacciones global es ineficiente. Está optimizado para la trasnparencia, no para el rendimiento. No lo use para entornos de producción. Por favor, ayude a encontrar una solución eficiente para su inclusión en el manual. Apreciamos su aportación.

Ejemplo #4 Configuración del complemento: SQL para obtener el 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
        }
    }
}

Ejemplo #5 Obtener el GTID después de la ejecución

<?php
$mysqli 
= new mysqli("myapp""nombre_usuario""contraseña""base_datos");
if (!
$mysqli) {
    
/* Por supuesto, su manejo de errores es mejor... */
    
die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
}

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("DROP TABLE IF EXISTS test")) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

printf("GTID después de la transacción %s\n"mysqlnd_ms_get_last_gtid($mysqli));

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
if (!$mysqli->query("CREATE TABLE test(id INT)")) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

printf("GTID después de la transacción %s\n"mysqlnd_ms_get_last_gtid($mysqli));
?>

El resultado del ejemplo sería:

GTID después de la transacción 7
GTID después de la transacción 8

Las aplicaciones pueden preguntar a PECL mysqlnd_ms por un ID de transacciones global que pertenezca a la última operación de escritura realizada por la aplicación. La función mysqlnd_ms_get_last_gtid() devuelve el GTID obtenido cuando se ejecuta la sentencia SQL desde la entrada fetch_last_gtid de la sección global_transaction_id_injection del fichero de configuración del complemento. La función puede ser invocada después de que el GTID haya sido incrementado.

Se aconseja que las aplicaciones no ejecuten la sentencia SQL por sí mismas ya que aumenta el riesgo de que accidentalmente se ocasione un incremento implícito del GTID. También, si se usa la función, es más fácil migrar una aplicación desde una sentencia SQL para obtener un ID de transacciones a otra, por ejemplo, si ningún servidor MySQL incluye el soporte interno para IDs de transacciones globales.

Esta guía rápida muestra una sentencia SQL que devolverá un GTID igual o mayor que el creado por la sentencia anterior. Éste es exactamente el GTID creado por la sentencia anterior si ningún cliente ha incrementado el GTID en el tiempo transcurrido entre la ejecución de la sentencia y el SELECT para obtener el GTID. De otro modo, será mayor.

Ejemplo #6 Configuración del complemento: comprobar un GTID en particular

{
    "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
        }
    }
}

Ejemplo #7 Nivel de servicio de consistencia de sesión y GTID combinados

<?php
$mysqli 
= new mysqli("myapp""nombre_usuario""contraseña""base_datos");
if (!
$mysqli) {
    
/* Por supuesto, su manejo de errores es mejor... */
    
die(sprintf("[%d] %s\n"mysqli_connect_errno(), mysqli_connect_error()));
}

/* modo autoconsigna, transacción en el maestro, el GTID debe ser incrementado */
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 es un identificador para la última escritura */
$gtid mysqlnd_ms_get_last_gtid($mysqli);

/* Consistencia de sesión (lectura de sus escrituras): intentar leer de los esclavos, no sólo del maestro */
if (false == mysqlnd_ms_set_qos($mysqliMYSQLND_MS_QOS_CONSISTENCY_SESSIONMYSQLND_MS_QOS_OPTION_GTID$gtid)) {
    die(
sprintf("[006] [%d] %s\n"$mysqli->errno$mysqli->error));
}

/* Ejecutar en el maestro o en el esclavo que ha replicado el INSERT */
if (!($res $mysqli->query("SELECT id FROM test"))) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

var_dump($res->fetch_assoc());
?>

Un GTID devuelto por mysqlnd_ms_get_last_gtid() se puede usar como una opción para el nivel de servicio de consistencia de sesión. La consistencia de sesión proporciona la lectura de sus escrituras. La consistencia de sesión puede ser solicitada llamando a mysqlnd_ms_set_qos(). En el ejemplo, el complemento ejecutará la sentencia SELECT en el maestro o en un esclavo que ya ha repliacdo el INSERT anterior.

PECL mysqlnd_ms comprobará de forma transparente cada esclavo configurado, si éste ha replicado el INSERT, mediante la comprobación de la tabla GTID de esclavos. La comprobación se realiza ejecutando la sentencia SQL establecida con la opción check_for_gtid de la sección global_transaction_id_injection del fichero de configuración del complemento. Por favor, observe que este es un procedimiento lento y caro. Las aplicaciones deberían intentar usarlo poco y únicamente si la carga de lectura en el maestro es alta.

Uso de la característica del ID de transacciones global en el lado del servidor

Nota: Soporte del servidor insuficiente en MySQL 5.6

El complemento ha sido derarrollado usando una versión de preproducción de MySQL 5.6. Resulta de que todas las versiones de producción de MySQL 5.6 no proporcionan clientes con suficiente información para forzar la consistencia de sesión basándose en GTID. Por favor, lea la sección de conceptos para más detalles.

Desde MySQL 5.6.5-m8, el sistema de Replicación MySQL introduce los IDs de transacciones globales. Los identificadores de transacciones son automáticamente generados y mantenidos por el servidor. Los usuarios no necesitan ocuparse de ellos. No hay necesidad de configurar ninguna tabla de antemano, ni de configurar on_commit. Ya no es necesaria la emulación en el lado del cliente.

Los clientes puede seguir usando los identificadores de transacciones globales para conseguir la consistencia de sesión al leer desde esclavos de la Replicación MySQL en algunos casos, ¡pero no en todos!. El algoritmo funciona como está descrito arriba. Se deben configurar diferentes sentencias SQL para fetch_last_gtid y check_for_gtid. Estas sentencias se proporcionan más abajo. Por favor, observe que MySQL 5.6.5-m8 es una versión en desarrollo. Los detalles de la implementación del servidor pueden cambiar en el futuro y requerir la adopción de las sentencias SQL mostradas.

Al utilizar la siguiente configuración, cualquier funcionalidad descrita arriba puede usarse junto con la característica del ID de transacciones global en el lado del servidor. mysqlnd_ms_get_last_gtid() y mysqlnd_ms_set_qos() siguen funcionando como se describió arriba. La única diferencia es que el servidor no utiliza un número de secuencia simple, sino un string que contiene un identificador del servidor y un número de secuencia. Por lo tanto, los usuarios no podrán obtener fáciltmente un orden desde los GTIDs devueltos por mysqlnd_ms_get_last_gtid().

Ejemplo #8 Configuración de complemento: usar la característica del GTID interna de 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
        }
    }
}