Un usuario de la comunidad se encontró con que al usar la herramienta mysqlbinlog para leer el binlog y restaurar datos, estos no se recuperaban correctamente. A continuación, se analiza el problema y su solución.
El escenario es el siguiente: una tabla tenía varios registros que fueron eliminados accidentalmente. La tabla original contenía:
mysql> SELECT * FROM t1;
+----+
| c1 |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
+----+
Al revisar los eventos del binlog, se observan varias inserciones y, al final, una operación TRUNCATE TABLE que eliminó todos los datos. El objetivo era restaurar la tabla al estado anterior al TRUNCATE.
Los eventos del binlog se ven así (resumido):
mysql> SHOW BINLOG EVENTS IN 'binlog.000003';
+---------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+---------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
| binlog.000003 | 4 | Format_desc | 3306 | 125 | Server ver: 8.0.25-15, Binlog ver: 4 |
| ... | ... | ... | ... | ... | ... |
| binlog.000003 | 1596 | Query | 3306 | 1693 | use \`yejr\`; truncate table t1 /* xid=87 */ |
+---------------+------+----------------+-----------+-------------+--------------------------------------------------------------------+
El comando que se ejecutó para la restauración fue:
$ mysqlbinlog --start-position=4 --stop-position=1512 binlog.000003 | mysql -f yejr
Se especificó una posición de inicio y fin para evitar el evento de TRUNCATE. Sin embargo, después de ejecutar el comando, la tabla seguía vacía. Esto indica que la restauración no funcionó.
Al investigar más a fondo, se observó que el servidor MySQL tenía habilitado el modo GTID (Global Transaction Identifier). En este modo, cada transacción tiene un identificador único global. Una característica clave de GTID es que si un GTID ya está presente en el conjunto de GTIDs ejecutados (Executed_Gtid_Set), el servidor no volverá a ejecutar esa transacción. En esencia, el servidor ignora las transacciones con GTIDs que ya han sido procesados.
Para entenderlo mejor, se realizaron pruebas:
-- Ver el estado actual de GTID
mysql> SHOW MASTER STATUS\G
*************************** 1. row ***************************
File: binlog.000004
Position: 762
...
Executed_Gtid_Set: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa1:1-15:20
-- Intento de insertar con un GTID que ya está en el conjunto (13 está dentro del rango 1-15)
mysql> SET SESSION gtid_next='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa1:13';
mysql> INSERT INTO t1 SELECT 6;
Query OK, 0 rows affected (0.00 sec) -- No se insertó ninguna fila
-- Intento de insertar con un GTID que NO está en el conjunto (17 está fuera del rango)
mysql> SET SESSION gtid_next='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaa1:17';
mysql> INSERT INTO t1 SELECT 6;
Query OK, 1 row affected (0.01 sec) -- Se insertó correctamente
Esto confirma que, en modo GTID, si el GTID de una transacción a restaurar ya existe en el Executed_Gtid_Set, el servidor no la procesa. Al usar mysqlbinlog sin opciones especiales, los GTIDs originales se encluyen en la salida, por lo que el servidor detecta que ya fueron ejecutados y los omite.
La solución es utilizar la opción --skip-gtids al ejecutar mysqlbinlog. Esta opción elimina la información de GTID de la salida, haciendo que el servidor trate las transacciones como nuevas, sin importar su historial de GTID.
El comando correcto para la restauración sería:
$ mysqlbinlog --skip-gtids --start-position=4 --stop-position=1512 binlog.000003 | mysql -f yejr
Al usar esta opción, las transacciones del binlog se ejecutan nuevamente y los datos se restauran correctamente.
En resumen, al recuperar datos desde un binlog en un servidor con GTID activado, es crucial recordar usar la opción --skip-gtids para evitar que el servidor ignore las transacciones basándose en su identificador global. Esto permite una restauración exitosa de los datos perdidos.