现象
这个场景很常见:MariaDB 复制原本使用 file/position 模式,为了测试或增强复制连接而启用 GTID,然后又切回普通模式。几秒钟后,IO thread 无法启动:
SHOW SLAVE STATUS\G
Slave_IO_Running: No
Slave_SQL_Running: Yes
Using_Gtid: No
Last_IO_Errno: 1236
Last_IO_Error: Could not find first log file name in binary log index file
最容易想到的解释是:“切到 GTID 清理了 relay logs 或 binlogs”。在这次分析的事件中,事实并不是这样。
GTID 按钮实际做了什么
在 PmaControl 中,MariaDB 复制上的 GTID > Activate 操作不会执行 purge,也不会执行 RESET SLAVE。
它只执行:
STOP SLAVE;
CHANGE MASTER TO MASTER_USE_GTID = slave_pos;
START SLAVE;
回滚操作执行:
STOP SLAVE;
CHANGE MASTER TO MASTER_USE_GTID = no;
START SLAVE;
这些命令不会删除源端的 binlogs。但是,CHANGE MASTER 会重建本地复制状态,并重置副本上的 relay logs。这个区别很重要:源端没有丢失任何东西,但副本会丢弃它已经在本地下载的内容。
真实规模测试
为了验证这个行为,我在两台 MariaDB 11.8 实验服务器上复现了该场景:
source -> replica
初始为 file/position
relay_log_purge=ON
测试步骤:
- 启动一条干净的 file/position 复制;
- 只停止副本的 SQL thread;
- 在源端生成 20,000 行;
- 确认 IO thread 已经把事件下载到 relay logs;
- 执行与
GTID > Activate按钮相同的序列。
在 CHANGE MASTER 之前,副本上存在尚未应用的 relay logs:
Slave_IO_Running: Yes
Slave_SQL_Running: No
Read_Master_Log_Pos: 635180
Exec_Master_Log_Pos: 3464
Relay_Log_Space: 632576
relay-bin.000002: 632273 bytes
执行 STOP SLAVE 后,没有丢失:
Slave_IO_Running: No
Slave_SQL_Running: No
Relay_Log_Space: 632576
relay-bin.000002: 632273 bytes
紧接着执行:
CHANGE MASTER TO MASTER_USE_GTID=slave_pos;
relay logs 被重置:
Using_Gtid: Slave_Pos
Read_Master_Log_Pos: 3464
Exec_Master_Log_Pos: 3464
Relay_Log_Space: 256
relay-bin.000001: 256 bytes
反向测试得到相同结果。在 GTID 模式下,如果存在尚未应用的 relay logs,通过:
CHANGE MASTER TO MASTER_USE_GTID=no;
回到 file/position,也会重置本地 relay logs。
测试结论:单独的 STOP SLAVE 会保留 relay logs。CHANGE MASTER 即使只修改 MASTER_USE_GTID,也会重置它们。
实际发生了什么
副本回到 file/position 模式时,保留下来的物理坐标太旧:
mariadb-bin.014786:35578691
relay source 的 binary log index 中已经没有这个文件。它当前第一个可用文件已经更新:
first available: mariadb-bin.015028
last available: mariadb-bin.015130
retained files: 103
retained size: about 10 GiB
而源端当时仍配置为:
binlog_expire_logs_seconds = 864000
expire_logs_days = 10
binlog_space_limit = 0
max_binlog_size = 104857600
也就是说,当前配置显示“10 天”。但副本请求的坐标已经早于实际可用窗口。
为什么 10 天并不保证这个文件存在
10 天保留期意味着服务器可以清理早于该限制的 binlogs。它并不保证副本在切换模式后还能回到任意旧的物理坐标。
有几种情况会让问题变得危险:
- 点击之前复制已经有延迟;
- 副本在 GTID 期间保留了旧的 file/position 坐标;
- 之前的 purge 已经删除了请求的文件;
- retention 值过去可能被修改过;
- maintenance 或 rebuild 可能重建了一个比预期更短的窗口;
- upstream master 和 relay source 可能使用不同的策略。
关键点很简单:如果文件不在 SHOW BINARY LOGS 中,MariaDB 就无法提供它,即使当前配置显示 10 天。
为什么 GTID 掩盖了问题
在 MariaDB GTID 模式下,副本不再请求 MASTER_LOG_FILE / MASTER_LOG_POS 这一对物理坐标。它通过 gtid_slave_pos 请求逻辑事务集合。
当切回 file/position 时,旧的物理坐标再次变得重要。如果这些坐标指向一个已经被清理的文件,IO thread 会立即以 1236 失败。
GTID 没有清理源端 binlogs。但是,通过 CHANGE MASTER 进行切换确实删除了副本已经下载到本地的 relay logs。如果这些 relay logs 中的事务已经无法从源端 binlogs 再次获取,那么恢复就必须依赖重新同步。
为什么简单的 CHANGE MASTER 不够
一个常见尝试是把副本直接指向源端当前的位置:
STOP SLAVE;
CHANGE MASTER TO
MASTER_LOG_FILE='mariadb-bin.015130',
MASTER_LOG_POS=97211185,
MASTER_USE_GTID=no;
START SLAVE;
IO thread 可能会重新启动。但 SQL thread 随后可能因为约束失败而停止,例如:
Last_SQL_Errno: 1452
Cannot add or update a child row:
foreign key constraint fails
这是符合预期的:直接跳到较新的位置会跳过一段事务。副本数据已经无法匹配它接下来收到的事件序列。
这不是修复,而是跳日志。
正确修复方式
可靠的修复方式是从一致的源重新同步副本:
- 停止复制;
- 从 source 或 relay source 获取一致性备份;
- 恢复副本;
- 获取备份对应的精确位置;
- 以 GTID 或 file/position 配置复制;
- 启动复制;
- 验证两个线程都正常。
期望结果:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0 or decreasing
Last_IO_Error:
Last_SQL_Error:
这里不应使用 sql_slave_skip_counter。跳过外键错误只会掩盖数据分歧,并留下不一致的数据。
点击前应增加的检查
在启用或停用 GTID 之前,PmaControl 应该先回答三个问题:
SHOW SLAVE STATUS\G
SHOW BINARY LOGS;
SHOW VARIABLES WHERE Variable_name IN (
'expire_logs_days',
'binlog_expire_logs_seconds',
'binlog_space_limit',
'max_binlog_size'
);
然后确认:
- 副本的
Relay_Master_Log_File是否仍存在于源端? - 副本是否已经有延迟或错误?
- binlog 窗口是否覆盖请求的位置?
如果答案是否定的,按钮就不应该只是发送 CHANGE MASTER。它应该显示警告并建议 rebuild。
结论
切换到 GTID 并没有清理源端 binlogs。但是,用于进入 GTID 并返回 file/position 的 CHANGE MASTER 重置了副本的本地 relay logs。
在健康环境中,副本会从源端重新下载这些事件。在这次分析的事件中,请求的坐标已经超出了源端 binlog 窗口。本地 relay logs 被丢弃后,就不再有任何来源可以重放缺失的事务范围。
运维上的教训很明确:在 GTID 和 file/position 之间切换之前,必须验证源端 binlog 窗口。否则,一个本应可回滚的测试会变成一次强制重新同步。
评论 (0)
暂无评论。
发表评论