主库出问题了,从库怎么办
一、背景:一主多从架构与主备切换
- 一主多从架构的意义:
- 常见于读多写少的互联网应用场景,用于解决读性能瓶颈。
- 架构图: 一个主库A,一个互为主备的备库A',多个只读从库B, C, D均指向主库A。
- 功能: 主库负责写操作和部分读,从库分担其余读请求。
- 主备切换的复杂性增加:
- 当主库A故障,备库A'提升为新主库。
- 不仅A与A'之间要完成切换,从库B, C, D也需要重新指向新主库A'。这个过程增加了切换的复杂度。
二、传统基于位点(Log Position)的主备切换
CHANGE MASTER TO
命令回顾:- 设置从库时,需要指定新主库的IP、端口、用户、密码。
- 关键参数:
MASTER_LOG_FILE
和MASTER_LOG_POS
,即同步位点。
- 获取同步位点的挑战:
- 问题: 从库B原来记录的是旧主库A的位点,切换到新主库A'时,需要找到A'上对应的同步位点。相同的日志内容在A和A'上的位点是不同的。
- 精确位点难取: 只能取大概位置,通常会稍往前取,以避免丢数据。
- 一种获取方法示例:
- 等待新主库A'同步完所有中转日志(relay log)。
- 在A'上执行
SHOW MASTER STATUS
,获取最新的File和Position。 - 取原主库A故障的时刻T。
- 使用
mysqlbinlog <File> --stop-datetime=T --start-datetime=T
解析A'的binlog,找到T时刻的end_log_pos
。
- 位点不精确导致的问题:
- 场景: T时刻,主库A已执行insert R,并将binlog传给A'和B后宕机。
- 状态: B上有R,A'上也有R(日志在T时刻位点之后)。
- 切换后: B以T时刻位点指向A',会重新同步并执行插入R的binlog。
- 结果: 从库B报主键冲突错误 (Duplicate entry),同步停止。
- 处理位点不精确的方法 (跳过错误):
- 方法一:主动跳过事务 (
sql_slave_skip_counter
)SET GLOBAL sql_slave_skip_counter=1;
START SLAVE;
- 需要持续观察,遇到错误就执行一次,直到同步稳定。操作繁琐易错。
- 方法二:设置跳过指定错误 (
slave_skip_errors
)- 常见错误:1062 (唯一键冲突), 1032 (删除时找不到行)。
SET GLOBAL slave_skip_errors = "1032,1062";
- 注意: 这是临时措施,明确知道切换时跳过这些错误无损。同步稳定后需将该参数设置为空,避免未来真正的数据不一致被忽略。
- 方法一:主动跳过事务 (
三、GTID (Global Transaction Identifier) 模式
- GTID 引入的目的: 彻底解决基于位点切换时寻找同步位点的困难。
- GTID 定义:
- 全称: 全局事务ID。
- 生成: 事务提交时生成,是事务的唯一标识。
- 格式:
GTID = server_uuid:gno
server_uuid
:实例首次启动时自动生成的全局唯一值 (官方文档称source_id
)。gno
:一个整数,初始为1,每次提交事务时分配并加1 (官方文档称transaction_id
,但作者认为gno
更准确,因事务ID在回滚时也递增,而gno
只在提交时分配)。
- 启用 GTID 模式:
- 启动参数:
gtid_mode=ON
和enforce_gtid_consistency=ON
。
- 启动参数:
- GTID 的分配方式 (取决于
SESSION
变量gtid_next
):gtid_next = automatic
(默认):- MySQL 将
server_uuid:gno
分配给当前事务。 - 记录 binlog 时,先记录
SET @@SESSION.GTID_NEXT='server_uuid:gno';
。 - 将此 GTID 加入本实例的 GTID 集合 (
Executed_Gtid_Set
)。
- MySQL 将
gtid_next = 'specified_gtid'
:- 如果
specified_gtid
已存在于实例的 GTID 集合中: 后续事务被系统忽略。 - 如果
specified_gtid
不存在于实例的 GTID 集合中: 将specified_gtid
分配给后续事务,实例的gno
不增加。此specified_gtid
只能用于一个事务,后续事务需重新设置gtid_next
。
- 如果
- GTID 的应用示例 (处理从库主键冲突):
- 场景: 实例X是实例Y的从库。Y上执行
INSERT INTO t VALUES(1,1)
,GTID为Y_uuid:10
。X同步时因主键冲突停止。 - 处理: 在实例X上执行:
sql SET @@SESSION.GTID_NEXT='Y_uuid:10'; BEGIN; COMMIT; -- 提交一个空事务,将 Y_uuid:10 加入X的GTID集合 SET @@SESSION.GTID_NEXT=automatic; -- 恢复默认GTID分配 START SLAVE;
- 效果: X再次尝试同步
Y_uuid:10
事务时,因该GTID已在X的集合中,会直接跳过此事务。
- 场景: 实例X是实例Y的从库。Y上执行
四、基于GTID的主备切换
CHANGE MASTER TO
命令 (GTID模式):sql CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password MASTER_AUTO_POSITION=1; -- 关键参数,表示使用GTID协议
- 不再需要
MASTER_LOG_FILE
和MASTER_LOG_POS
。
- 不再需要
- GTID 模式下的切换逻辑:
- 从库B (GTID集合
set_b
) 连接新主库A' (GTID集合set_a
)。 - B将
set_b
发给A'。 - A'计算差集 (
set_a - set_b
),即A'上有但B上没有的GTID。 - A'检查本地是否包含差集所需的所有binlog事务:
- 不包含: A'已删除B需要的binlog,返回错误,拒绝同步。
- 包含: A'从自己binlog中找到第一个不在
set_b
中的事务,发给B。
- 之后按顺序发送后续binlog给B。
- 从库B (GTID集合
- GTID模式的优势:
- 简化切换: 从库B, C, D只需执行
CHANGE MASTER TO ... MASTER_AUTO_POSITION=1
指向A'即可。 - 自动位点查找: 找位点的工作由A'内部自动完成。
- 日志完整性保证: 系统确保主库发给备库的日志是完整的。如果备库需要的日志已不存在,主库会拒绝。
- 简化切换: 从库B, C, D只需执行
- GTID 集合的演变:
- 若从库B原GTID集合为
server_uuid_of_A:1-N
。 - 切换到新主A'后,A'自己生成的事务GTID为
server_uuid_of_A':1-M
。 - B的GTID集合将变为
server_uuid_of_A:1-N, server_uuid_of_A':1-M
。
- 若从库B原GTID集合为
五、GTID 与在线 DDL 优化
- 场景回顾 (第22讲): 为避免影响主库,先在备库加索引,再进行主备切换。
- 传统方法:备库执行DDL时
SET sql_log_bin=OFF
,避免DDL传回主库。 - 问题:日志与数据不一致。
- 传统方法:备库执行DDL时
- GTID 模式下的在线DDL切换流程 (以X为主,Y为备,均启用GTID):
- 在主库X上执行
STOP SLAVE;
(如果X也是Y的从库,例如双M结构)。 - 在备库Y上执行DDL语句 (如加索引)。无需关闭binlog。
- DDL执行完成后,在Y上查出该DDL对应的GTID,记为
server_uuid_of_Y:gno
。 - 在主库X上执行:
sql SET @@SESSION.GTID_NEXT='server_uuid_of_Y:gno'; BEGIN; COMMIT; -- 将此GTID加入X的GTID集合 SET @@SESSION.GTID_NEXT=automatic; START SLAVE; -- 重新启动X到Y的复制(如果需要)
- 效果:
- 备库Y的DDL操作有binlog记录。
- 主库X不会重复执行这个DDL (因为该GTID已在其集合中)。
- 完成主备切换 (Y成为新主,X成为新备)。
- 在新备X上重复类似步骤2-4,为X也加上索引。
- 在主库X上执行
六、核心小结与实践建议
- 一主多从切换痛点: 从库找新主库的同步位点困难且易错。
- GTID是良药: MySQL 5.6 引入的GTID模式极大简化了主备切换,特别是复杂的一主多从场景。
- 推荐使用GTID: 如果MySQL版本支持,强烈建议使用GTID模式进行主备切换和管理。
- GTID的日志完整性检查: 与基于位点不同,GTID模式下主库会校验日志的完整性,若备库请求的日志已被删除,则会拒绝同步,这是一种更安全的机制。
七、延伸思考:文末问题预告
问题: 在GTID模式下设置主从关系时,从库执行 START SLAVE
后,主库发现需要的binlog已被删除,导致主备创建不成功。这种情况下,可以怎么处理?
* 思考方向:
* 为什么binlog会被删除?(binlog过期策略)
* 如何重建主备关系?(全量备份恢复 + 增量同步)
* 是否有其他工具或方法可以辅助?
八、上期问题回顾:单线程压力模式下 binlog-transaction-dependency-tracking
参数选择
- 答案: 应设置为
WRITESET
。 - 原因:
COMMIT_ORDER
:主库单线程,每个事务commit_id
不同,从库也会退化为单线程。WRITESET_SESSION
:要求同一线程的日志按主库顺序执行,主库单线程导致从库也单线程。WRITESET
:基于行数据冲突判断,即使主库单线程,只要事务写入的行不冲突,从库就能并行。