读mysql45讲-过期读

在读写分离的情况下,也就是主库主要负责写数据,读取数据得压力都分布在从库上。由于主从延迟是无法避免得,所以如果在主库中执行完一个事务之后,立即发起一个查询,这个时候在从库上查询得到的数据可能是刚刚事务之前的状态。这种在从库上读取到的一种过期的状态,暂时称为过期读。处理过期读的方案有如下几个:

  • 强制走主库
  • sleep方案
  • 判断主备有无延迟方案
  • 配合semi-sync方案
  • 等主库位点方案
  • 等主库GITD方案

强制走主库

就是将查询请求分为两类,一类是业务上需要实时查询的,另一类是不需要的。业务上需要实时查询的就让走主库查询。但是如果业务都需要走实时查询的话,那读写的压力都在主库上了,就等于放弃了读写分离,放弃了扩展性。

sleep方案

主库更新之后,每次去到从库的查询请求都先sleep一下,具体的方案就是类似于执行一条sleep sleep(1)命令。也就是查询请求之前,先等待一秒。但是这样会有两个问题:

  1. 如果从库执行relay log的时间超过了一秒,那么还是会出现过期读的情况。
  2. 每次查询请求都sleep一秒,如果大部分执行 relay log的时间都不超过1秒,那就等于浪费了时间,影响了用户体验。

判断主备无延迟方案

之前中的showslave status命令中结果里的seconds_behind_master参数的值,可以用来衡量主备延迟时间的长短。每次从库执行查询请求之前,先判断seconds_behind_master这个参数的值是不是为0,如果不等于0,就一直等待到等于0的时候,seconds_behind_master的精度是秒。除了判断这个参数的值之外,还有另外两种方式来判断主从延迟。
第一种就是判断同步位点的方式:

  • Master_Log_File和Read_Master_Log_Pos,表示的是当前读取到的主库中的最新位点。
  • Relay_Master_Log_File和Exec_Master_Log_Pos,表示的是从库中执行的最新位点。

如果两组位点位置是相同的话,表示数据已经同步完成。
第三种方式就是GTID集合对比的方式

  • Auto_Position=1 表示对主备关系开启了GTID协议。
  • Retrieved_Gtid_Set,是备库中收到的所有日志的GTID集合。
  • Executed_Gtid_Set, 是备库中所有已经完成的日志的GTID集合。

如果两个集合相同,那么也表示数据同步完成。这两种方式都要比seconds_behind_master参数值=0来的更准确。
这几种判断主备无延迟的判断标准都是备库将接收到的relay log都已经执行完毕了。但是存在这样一个问题:

  1. 客户端发起一个事务请求
  2. 主库提交事务完成,写入binglog,日志发送给备库,与此同时客户端收到主库的确认结果。
  3. 备库还没有收到binlog日志,客户端已经开始查询,因为这个时候备库收到的所有relay log日志都是已经执行过的,所以认为无延迟。
  4. 但是客户端无法在备库中查询到最新数据,还是出现了过期读的状态。

配置semi-sync

为了解决上面出现的问题,就要引入半同步复制,也就是semi-sync replication.

  • 事务提交的时候,主库把binlog发送给备库
  • 备库收到binlog日志,回给主库一个ack确认标记,表示收到了
  • 主库在得到备库返回的ack标记之后,才想客户端返回确认结果。

但是semi-sync配置前面同步位点的判断,只会对一主一备的情况下是成立的,因为主库在讲binlog日志发送给多个从库的情况下,只要有一个从库返回了ack标志,那么主库就会给客户端返回确认结果。
所以如果查询的请求是在返回ack标志的从库中执行的,那可以确保是可以读取到最新数据的;但是如果查询的请求不是在返回ack标志的从库的,那么就会有过期读的情况存在。

并且判断同步位点还是有另外一个问题的,客户端发起了一个请求A,随后又有很多请求也一起到了主库中,主库执行完事务A和之后所有的事务请求之后,将binlog日志发送给备库。虽然从库中已经将最先进入的事务A提交了,并且数据也已经更新到了最新,但是由于从库中存在还未同步完成的relay log日志,所以认为此时是存在主备延迟的(同步位点判断的标准就是从库中所有收到的relay log日志已经执行完成)。这个时候其实是可以避免的,因为最新进入的事务A已经完成了,那么查询事务A涉及到的数据可以认为是没有延迟的。

等主库位点方案

先介绍一条命令:

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

这是在从库中执行的,file和pos指的是主库中的问题名,timeout是一个可选指定超时参数。
正常情况下这个命令会返回一个正整数,但是也会在特定情况下返回其他数字:

  • 如果执行期间,同步日志的线程发生了异常,会返回null
  • 如果等待时间超过timeout指定的时间,就会返回-1
  • 如果执行的时候发现之前执行过这个位置了,就返回0(这个每太懂,是发现查询过这个位置了,就直接返回0么?还是说防止两次同样的查询?)

那么在主库中执行完一个更新事务请求之后,马上执行show master status得到当前最新的file和position;然后在从库的查询请求中执行
select master_pos_wait(file,positon,timeout)命令,前两个参数就是主库中得到的file和position;如果返回结果是一个正整数,那就说明查询数据的日志已经同步完成过了,就直接查询从库,否则的话就直接查询主库。

等GITD方案

其实和上面的类似,也有一条查询命令:

select wait_for_executed_gtid_set(gtid_set, 1);

是在从库中执行的,第一个参数是指定的gtid,也就是一个事务提交完成之后分配的gtid,返回的逻辑是:

  • 等待,直到这个库中已经执行过的gtid_set集合中包含这个指定的gtid,返回0
  • 超时返回1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值