Skip to content

主库出问题了,从库怎么办

一、背景:一主多从架构与主备切换

  1. 一主多从架构的意义:
    • 常见于读多写少的互联网应用场景,用于解决读性能瓶颈。
    • 架构图: 一个主库A,一个互为主备的备库A',多个只读从库B, C, D均指向主库A。
    • 功能: 主库负责写操作和部分读,从库分担其余读请求。
  2. 主备切换的复杂性增加:
    • 当主库A故障,备库A'提升为新主库。
    • 不仅A与A'之间要完成切换,从库B, C, D也需要重新指向新主库A'。这个过程增加了切换的复杂度。

二、传统基于位点(Log Position)的主备切换

  1. CHANGE MASTER TO 命令回顾:
    • 设置从库时,需要指定新主库的IP、端口、用户、密码。
    • 关键参数:MASTER_LOG_FILEMASTER_LOG_POS,即同步位点
  2. 获取同步位点的挑战:
    • 问题: 从库B原来记录的是旧主库A的位点,切换到新主库A'时,需要找到A'上对应的同步位点。相同的日志内容在A和A'上的位点是不同的。
    • 精确位点难取: 只能取大概位置,通常会稍往前取,以避免丢数据。
    • 一种获取方法示例:
      1. 等待新主库A'同步完所有中转日志(relay log)。
      2. 在A'上执行 SHOW MASTER STATUS,获取最新的File和Position。
      3. 取原主库A故障的时刻T。
      4. 使用 mysqlbinlog <File> --stop-datetime=T --start-datetime=T 解析A'的binlog,找到T时刻的 end_log_pos
  3. 位点不精确导致的问题:
    • 场景: T时刻,主库A已执行insert R,并将binlog传给A'和B后宕机。
    • 状态: B上有R,A'上也有R(日志在T时刻位点之后)。
    • 切换后: B以T时刻位点指向A',会重新同步并执行插入R的binlog。
    • 结果: 从库B报主键冲突错误 (Duplicate entry),同步停止。
  4. 处理位点不精确的方法 (跳过错误):
    • 方法一:主动跳过事务 (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) 模式

  1. GTID 引入的目的: 彻底解决基于位点切换时寻找同步位点的困难。
  2. GTID 定义:
    • 全称: 全局事务ID。
    • 生成: 事务提交时生成,是事务的唯一标识。
    • 格式: GTID = server_uuid:gno
      • server_uuid:实例首次启动时自动生成的全局唯一值 (官方文档称 source_id)。
      • gno:一个整数,初始为1,每次提交事务时分配并加1 (官方文档称 transaction_id,但作者认为 gno 更准确,因事务ID在回滚时也递增,而 gno 只在提交时分配)。
  3. 启用 GTID 模式:
    • 启动参数:gtid_mode=ONenforce_gtid_consistency=ON
  4. GTID 的分配方式 (取决于 SESSION 变量 gtid_next):
    • gtid_next = automatic (默认):
      • MySQL 将 server_uuid:gno 分配给当前事务。
      • 记录 binlog 时,先记录 SET @@SESSION.GTID_NEXT='server_uuid:gno';
      • 将此 GTID 加入本实例的 GTID 集合 (Executed_Gtid_Set)。
    • gtid_next = 'specified_gtid'
      • 如果 specified_gtid 已存在于实例的 GTID 集合中: 后续事务被系统忽略。
      • 如果 specified_gtid 不存在于实例的 GTID 集合中:specified_gtid 分配给后续事务,实例的 gno 不增加。此 specified_gtid 只能用于一个事务,后续事务需重新设置 gtid_next
  5. 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的集合中,会直接跳过此事务。

四、基于GTID的主备切换

  1. 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_FILEMASTER_LOG_POS
  2. GTID 模式下的切换逻辑:
    1. 从库B (GTID集合 set_b) 连接新主库A' (GTID集合 set_a)。
    2. B将 set_b 发给A'。
    3. A'计算差集 (set_a - set_b),即A'上有但B上没有的GTID。
    4. A'检查本地是否包含差集所需的所有binlog事务:
      • 不包含: A'已删除B需要的binlog,返回错误,拒绝同步。
      • 包含: A'从自己binlog中找到第一个不在 set_b 中的事务,发给B。
    5. 之后按顺序发送后续binlog给B。
  3. GTID模式的优势:
    • 简化切换: 从库B, C, D只需执行 CHANGE MASTER TO ... MASTER_AUTO_POSITION=1 指向A'即可。
    • 自动位点查找: 找位点的工作由A'内部自动完成。
    • 日志完整性保证: 系统确保主库发给备库的日志是完整的。如果备库需要的日志已不存在,主库会拒绝。
  4. 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

五、GTID 与在线 DDL 优化

  1. 场景回顾 (第22讲): 为避免影响主库,先在备库加索引,再进行主备切换。
    • 传统方法:备库执行DDL时 SET sql_log_bin=OFF,避免DDL传回主库。
    • 问题:日志与数据不一致。
  2. GTID 模式下的在线DDL切换流程 (以X为主,Y为备,均启用GTID):
    1. 在主库X上执行 STOP SLAVE; (如果X也是Y的从库,例如双M结构)。
    2. 在备库Y上执行DDL语句 (如加索引)。无需关闭binlog
    3. DDL执行完成后,在Y上查出该DDL对应的GTID,记为 server_uuid_of_Y:gno
    4. 在主库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的复制(如果需要)
    5. 效果:
      • 备库Y的DDL操作有binlog记录。
      • 主库X不会重复执行这个DDL (因为该GTID已在其集合中)。
    6. 完成主备切换 (Y成为新主,X成为新备)。
    7. 在新备X上重复类似步骤2-4,为X也加上索引。

六、核心小结与实践建议

  1. 一主多从切换痛点: 从库找新主库的同步位点困难且易错。
  2. GTID是良药: MySQL 5.6 引入的GTID模式极大简化了主备切换,特别是复杂的一主多从场景。
  3. 推荐使用GTID: 如果MySQL版本支持,强烈建议使用GTID模式进行主备切换和管理。
  4. GTID的日志完整性检查: 与基于位点不同,GTID模式下主库会校验日志的完整性,若备库请求的日志已被删除,则会拒绝同步,这是一种更安全的机制。

七、延伸思考:文末问题预告

问题: 在GTID模式下设置主从关系时,从库执行 START SLAVE 后,主库发现需要的binlog已被删除,导致主备创建不成功。这种情况下,可以怎么处理? * 思考方向: * 为什么binlog会被删除?(binlog过期策略) * 如何重建主备关系?(全量备份恢复 + 增量同步) * 是否有其他工具或方法可以辅助?

八、上期问题回顾:单线程压力模式下 binlog-transaction-dependency-tracking 参数选择

  • 答案: 应设置为 WRITESET
  • 原因:
    • COMMIT_ORDER:主库单线程,每个事务 commit_id 不同,从库也会退化为单线程。
    • WRITESET_SESSION:要求同一线程的日志按主库顺序执行,主库单线程导致从库也单线程。
    • WRITESET:基于行数据冲突判断,即使主库单线程,只要事务写入的行不冲突,从库就能并行。