正常的一主多从的状态如下:主库负责主要写请求和部分读,从分担大部分的读请求。但是因为主从可能出现延迟,如果客户端执行完一个事务之后马上发起查询,就可能会出现 “过期读” 的现象,也就是读到一个不是最新的更新数据。

mysql 主从 主挂了切到从上边 mysql主从主挂了还能读吗_延迟时间

我们有如下方案来解决这个问题:

  • 强制走主库方案;
  • sleep 方案;
  • 判断主备无延迟方案;
  • 配合 semi-sync 方案;
  • 等主库位点方案;
  • 等 GTID 方案。
28.1强制走主库

强制走主库的方案其实就是将请求做分类,大致将请求分为两类:

  • 对于需要最新数据的请求就走主库
  • 对于可以使用旧数据应用的走从库
28.2sleep方案

主库更新完后,从库查询前先sleep一下。也就是在每条从从库过的请求,我们都给它sleep(n)n秒,用来掩盖同步延迟的时间。

28.3判断主备无延迟方案

1.对比同步延迟时间

通过show slave status结果下的seconds_behind_master参数的值来得到主备延迟时间的长短。

每次请求前,我们需要去请求主备延迟时间,只有等于0的情况下,才可以去执行请求。

2.对比日志位点

  • 通过参数Master_Log_File 和 Read_Master_Log_Pos,表示的是读到的主库的最新位点;
  • 通过参数Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是备库执行的最新位点。

要这两组数据完全相同时,才可以执行请求。

3.对比GTID集合

通过show slave status指令来 Retrieved_Gtid_Set 和 Executed_Gtid_Set 参数,这两个参数分别表示着 备库收到的所有日志的 GTID 集合, 备库所有已经执行完成的 GTID 集合。

通过对比集合是否相同,看日志是否已经同步完成。

这里有个问题,一个事务的 binlog 在主备库间的流程:

  1. 主库执行完成,写入 binlog,并反馈给客户端;
  2. binlog 被从主库发送给备库,备库收到;
  3. 在备库执行 binlog 完成。

这样子就可以出现,客户端已经收到提交确认,但是备库却没有收到binlog的情况。解决问题如下:

28.4 配合 semi-sync

semi-sync 做了这样的设计:

  1. 事务提交的时候,主库把 binlog 发给从库;
  2. 从库收到 binlog 以后,发回给主库一个 ack,表示收到了;
  3. 主库收到这个 ack 以后,才能给客户端返回“事务完成”的确认。

当时semi-sync有如下问题,①如果备库过渡等待,也导致事务也过渡等待;②在一主多从情况下,客户端在收到任意一个ack后,便确认事务已经完成,但这还是会出现过期读的现象,读取的从库不是提交ack的那个库。

下面介绍两种方案解决这个问题:

28.5 等主库位点方案

看,新命令如下:

select master_pos_wait(file, pos[, timeout]);

这条命令的逻辑如下:

  • 它是在从库执行的;
  • 参数 file 和 pos 指的是主库上的文件名和位置;
  • timeout 可选,设置为正整数 N 表示这个函数最多等待 N 秒。

这个命令正常返回的结果是一个正整数 M,表示从命令开始执行,到应用完 file 和 pos 表示的 binlog 位置,执行了多少事务。

当然,除了正常返回一个正整数 M 外,这条命令还会返回一些其他结果,包括:

  • 如果执行期间,备库同步线程发生异常,则返回 NULL;
  • 如果等待超过 N 秒,就返回 -1;
  • 如果刚开始执行的时候,就发现已经执行过这个位置了,则返回 0。

使用方式:

  1. trx1 事务更新完成后,马上执行 show master status 得到当前主库执行到的 File 和 Position;
  2. 选定一个从库执行查询语句;
  3. 在从库上执行 select master_pos_wait(File, Position, 1);
  4. 如果返回值是 >=0 的正整数,则在这个从库执行查询语句;否则,到主库执行查询语句。
28.6 GTID 方案

如果你的数据库开启了 GTID 模式,对应的也有等待 GTID 的方案。MySQL 中同样提供了一个类似的命令:

select wait_for_executed_gtid_set(gtid_set, 1);

流程图如下:

  1. trx1 事务更新完成后,从返回包直接获取这个事务的 GTID,记为 gtid1;
  2. 选定一个从库执行查询语句;
  3. 在从库上执行 select wait_for_executed_gtid_set(gtid1, 1);
  4. 如果返回值是 0,则在这个从库执行查询语句;
  5. 否则,到主库执行查询语句。

将参数 session_track_gtids 设置为 OWN_GTID ,就可以让事务的返回包带有GTID。