Transacciones XA/Distribuidas

Nota: Requisitos de versión

Las funciones relacionadas con XA han sido introducidas en la verisón 1.6.0-alpha de PECL mysqlnd_ms.

Nota: Se buscan los primeros adaptadores

Esta característica está actualmente en desarrollo. Podrían existir problemas y/o limitaciones. No la use en entornos de producción, aunque las primeras pruebas indican una calidad razonable.

Por favor, contacte con el equipo de desarrollo si está interesado en esta característica. Estamos buscando comentarios de la vida real para completar esta característica.

Las transacciones XA son un método estandarizado para ejecutar transacciones a través de varios recursos. Estos recursos pueden ser bases de datos u otros sistemas transaccionales. El servidor de MySQL admite sentencias SQL de XA, las cuales permiten a los usuarios llevar a cabo una transacción SQL distribuida que genera varios servidores de bases de datos, o cualquier otro tipo siempre y cuando admita también las sentencias SQL. En este tipo de escenario, es responsabilidad del usuario coordinar los servidores participantes.

PECL/mysqlnd_ms puede actuar como un coordinador de transacciones para una transacción global (distribuida, XA) se lleve a cabo solamente en servidores de MySQL. Como coordinador transacciones, el complemento rastrea todos los servidores involucrados en una transacción global y envía de forma transparente las sentencias SQL apropiadas a los participantes. Las transacción globales son controladas con mysqlnd_ms_xa_begin(), mysqlnd_ms_xa_commit() y mysqlnd_ms_xa_rollback(). Los detalles SQL mayormente están ocultos a la aplicación, ya que es necesario rastrear y coordinar a los participantes.

Ejemplo #1 Patrón general para transacciones XA

<?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()));
}

/* iniciar una transacción global */
$gtrid_id "12345";
if (!
mysqlnd_ms_xa_begin($mysqli$gtrid_id)) {
    die(
sprintf("[%d] %s\n"$mysqli->errno$mysqli->error));
}

/* ejecutar las consultas como siempre: XA BEGIN será inyectada en cuanto se ejecute una consulta */
if (!$mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')")) {
    
/* Tanto si falla INSERT o falla la inyección de XA BEGIN */
    
if ('XA' == substr($mysqli->sqlstate02)) {
        
printf("Fallo relacionado con una transacción/XA global, [%d] %s\n"$mysqli->errno$mysqli->error);
    } else {
        
printf("Fallo de INSERT, [%d] %s\n"$mysqli->errno$mysqli->error);
    }
    
/* revertir la transacción global */
    
mysqlnd_ms_xa_rollback($mysqli$xid);
    die(
"Deteniendo.");
}

/* continuar con la realización de consultas en otros servidores, p.ej. otros fragmentos */

/* consignar la transacción global */
if (!mysqlnd_ms_xa_commit($mysqli$xa_id)) {
    
printf("[%d] %s\n"$mysqli->errno$mysqli->error);
}
?>

A diferencia de las transacciones locales, la cuales se llevan a cabo en un único servidor, las transacciones XA tienen un identificador (xid) asociado a ellas. El identificador de transacción de XA está compuesto por un identificador de transacción global (gtrid), un identificador de ramificación (bqual), y un identificador de formato (formatID). Solamente el identificador de transacción global puede y debe proporcionarse al invocar a cualquier función de XA del complemento.

Una vez que ha sido iniciada una transacción global, el complemento comienza a rastrear servidores hsata que la transacción global finaliza. Cuando un servidor se escoge para la ejecución de una consulta, el complemento inyecta la sentencia SQL XA BEGIN antes de ejecutar la sentencia SQL real en el servidor. XA BEGIN hace que el servidor participe en la transacción global. Si falla la inyección de la sentencia SQL, el complemento informará del problema en réplica a la función de ejecución de consultas que se empleó. En el ejemplo de arriba, $mysqli->query("INSERT INTO orders(order_id, item) VALUES (1, 'christmas tree, 1.8m')") indicaría tal error. Se podría comprobar el código de estado SQL de los errores para determinar la consulta real (aquí: INSERT) que ha fallado o el error relacionado con la transacción global. Depende de usted ignorar el fallo de iniciar la transacción global en un servidor y continuar la ejecución sin tener al servidor participando en la transacción global.

Ejemplo #2 Las transacciones locales y globales son mutuamente exclusivas

<?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()));
}

/* iniciar una transacción local */
if (!$mysqli->begin_transaction()) {
    die(
sprintf("[%d/%s] %s\n"$mysqli->errno$mysqli->sqlstate$mysqli->error));
}

/* anora no se puede iniciar la transición global - primero debe finalizar la local */
$gtrid_id "12345";
if (!
mysqlnd_ms_xa_begin($mysqli$gtrid_id)) {
    die(
sprintf("[%d/%s] %s\n"$mysqli->errno$mysqli->sqlstate$mysqli->error));
}
?>

El resultado del ejemplo sería:


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

Una transacción global no se puede iniciar cuando una transacción local está activa. El complelento intenta detectar esta situación tan pronto como sea posible, que es cuando se llama a mysqlnd_ms_xa_begin(). Si solamente se emplean llamadas a la API para controlar transacciones, el complemento sabrá qué transacción local está abierta, devolviendo un error para mysqlnd_ms_xa_begin(). Sin embargo, observe que las limitaciones para detectar límites de transacciones del complemento. En el peor de los casos, si se emplea SQL directo para transacciones locales (BEGIN, COMMIT, ...), podría suceder que un error de demorara hasta que se ejecute alguna sentencia SQL en el servidor.

Para finalizar una transacción global se ha de invocar a mysqlnd_ms_xa_commit() o a mysqlnd_ms_xa_rollback(). Cuando finaliza una transacción global, todos los participantes deben ser informados de dicha finalización. Por lo tando, PECL/mysqlnd_ms enviará de forma transparente las sentencias SQL relacionadas apropiadas a alguno o todos ellos. Cualquier fallo durante esta fase ocasionará una reversión implícita. La API relacionada con XA se mantiene aquí intencionadamente simple. Una API más compleja que proporcionara más control dejaría al descubierto pocas ventajas, si las hubiera, sobre una implementación de usuario que enviara todas las sentencias SQL de XA de bajo nivel por sí misma.

Las transacciones XA emplean el protocolo de consignación de dos fases. El protocolo de consignación de dos fases es un protocolo de bloqueo. Hay casos en los que no se puede hacer ningún progreso, incluso al emplear tiempos de espera. Los coordinadores de transaccionesbe made, not even when using timeouts. Transaction coordinators deberían sobrevivir a sus propios fallos, ser capaces de detectar bloqueos y deshacer empates. PECL/mysqlnd_ms toma el papel de un coordinador de transacciones y puede ser configurado para sobrevivir a su propia caída para evitar problemas con servidores de MySQL bloqueados. Por tanto, el complemento puede y debería ser configurado para utilizar un estado persistente y seguro ante caídas para permitir la recolección de basura de transacciones globales abortadas y no finalizadas. Una transacción global puede ser abortada en un estado abierto si el complemento falla (se cae) o falla una conexión desde el complemento a un participante de una transacción global.

Ejemplo #3 Almacenamiento del estado del coordinador de transacciones

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

Actualmente, PECL/mysqlnd_ms admite solamente el uso de tablas de bases de datos de MySQL como almacenamiento de estados. Las definiciones de SQL de las tablas se proporcionan en la sección de configuración del complemento. Asegúrese de utilizar un motor de almacenamiento transaccional y seguro ante caídas para las tablas, como InnoDB. InnoDB es el motor de tablas predeterminado en versiones recientes del servidor de MySQL. Asegúrese también de que el servidor de bases de datos sea altamente disponible.

Si se ha configurado un almacén de estado, el complemento puede realizar una recolección de basura. Durante dicha recolección, podría ser necesario conectarse a un participante de una transacción global fallida. Así, el almacén de estado mantiene una lista de participantes y, entre otras cosas, sus nombres de host. Si la recolección de basura se ejecuta en otro host que no sea el que haya escrito una entrada de participación con el el nombre de host localhost, entonces localhost se resolverá a máquinas diferentes. Hay dos soluciones al problema. O no se configura ningún servidor con el nombre de host localhost y se configura una dirección IP (y puerto), o se indica la recolección de basura. En el ejemplo anterior, localhost se usa para master_0, de ahí que podría no resolverse al host correcto durante la recolección de basura. Sin embargo, participant_localhost_ip también está establecido para indicar la recolección de basura que localhost representa para la IP 192.168.2.12.