vlambda博客
学习文章列表

MYSQL主从重要参数原理

最近压测一套MYSQL 8.0的主从架构数据库,一主两从,其中有一台式异地从库.

为此异地从库使用最大性能异步模式,而不是半同步插件方式.

经过压测的时候10个并发 一个小时才跑4万个订单. 3MBPS的网络带宽,实压想才有37KB数据同步,没有把带宽打满.同时从库远落后与主库.那个Seconds_Behind_Master都跑到了1万秒.

主库每20分钟产生1GB的日志,而从库却落后了20个日志量. 需要18个小时才能完成同步,这还是要在主库不再产生新的日志情况下.

而本地从库差距就胜小.

为什么呢?


mysql> show variables like '%relay%';

+---------------------------+--------------------------------------------+

| Variable_name             | Value                                      |

+---------------------------+--------------------------------------------+

| max_relay_log_size        | 0                                          |

| relay_log                 | paylabs-drc-relay-bin                      |

| relay_log_basename        | /var/lib/mysql/paylabs-drc-relay-bin       |

| relay_log_index           | /var/lib/mysql/paylabs-drc-relay-bin.index |

| relay_log_info_file       | relay-log.info                             |

| relay_log_info_repository | TABLE                                      |

| relay_log_purge           | ON                                         |

| relay_log_recovery        | ON                                         |

| relay_log_space_limit     | 0                                          |

| sync_relay_log            | 1                                          |

| sync_relay_log_info       | 1                                          |

+---------------------------+--------------------------------------------+


因为我们设置了sync_relay_log =1 每1个事件就要刷盘 同步到中续日志文件中.

sync_relay_log  单位是event IO线程 当 SYNC_RELAY_LOG 设置成1000时候, 是从主库拉到 从库内存中.保存1000个后才刷入磁盘.

如果是SYNC_RELAY_LOG=1 那么每个事件都要刷入磁盘.那么因为FSYNC操作导致读取BINLOG速度变慢. 为此造成主库BINLOG无法及时同步到从库中.

如果设置成1000 读取BINLOG 不会受到FSYNC刷盘动作影响,影响会比较小. 


从这里得知,这个参数影响我们的IO线程,使得它变忙,无法及时去主库拉取BINLOG. 

IO 线程 :1. 读 binlog;2. 写到 relay log(文件缓存);3. 调用 fsync 刷到磁盘

读取 binlog 和刷盘 到 relay log 是两个动作 不过这两个动作是同步的相关联的.


此时此刻从库挂了,会丢失同步的999个事件吗?

那么要保证数据一致性,就得设置 relay log recovery =1  这岂不是两难啊?


这要看实际情况,如果MYSQL挂了,或者是STOP SLAVE,那么无所谓!

因为 SQL应用线程是从磁盘的中续日志OS CACHE中读入再应用.

也就是说SYNC_RELAY_LOG=1000 是积累1K事件刷盘,这1K事件已经写到了OS缓存区中,而SQL应用线程读取的时候,先读取OS缓存区中.这1K事件就被SQL线程读取到.这也不是SQL线程主动,是OS的行为.

另外不受 innodb_flush_method=O_DIRECT 影响.因为中续日志文件是普通的系统文件,不由INNODB掌控的数据文件.


如果sync_relay_log=1000 OS系统挂了怎么办?

这下就要涉及到很多参数了

sync_relay_log_info

sync_master_info

relay_log_info_repository

master_info_repository

relay_log_recovery


sync_master_info 表示IO线程写入中续日志最后MASTER位置信息.

sync_relay_log_info 表示SQL线程最后完成回放中续日志位置信息.

前面SYNC 表示同步.以前是同步到文件中,现在一般设置同步到表中

relay_log_info_repository=TABLE

master_info_repository=TABLE

由INNODB 完成持久化工作.


这样一来,系统挂了,我们MYSQL的IO线程和SQL线程就知道上一次工作结束的位置.

mysql>  select * from mysql.slave_master_info\G;

*************************** 1. row ***************************

              Number_of_lines: 31

              Master_log_name: master_mysql-bin.000209

               Master_log_pos: 38740




  1. mysql> select * from mysql.slave_relay_log_info\G


  2. *************************** 1. row ***************************

  3. Number_of_lines: 7

  4. Relay_log_name: ./mysql-relay.000015

  5. Relay_log_pos: 621

  6. Master_log_name: mysql-bin.000001

  7. Master_log_pos: 2407

  8. Sql_delay: 0

  9. Number_of_workers: 16

  10. Id: 1

  11. Channel_name:


IO线程 从mysql.slave_master_info信息为起点去主库拉新的BINLOG;

SQL线程从mysql.slave_relay_log_info里的信息为起点从新恢复.


这就是完美解决了系统GAVE OVER的问题.真的吗?

这就是说sync_relay_log=1000 系统挂了丢失了999个事件,

SQL线程到底又没有把999个事件恢复了没? 这就要看mysql.slave_relay_log_info 实际上工作信息. 现在丢失了999个事件,表明当前存在的RELAY_LOG是不可用的,或者是损坏的.

那么我们就要启用relay_log_recovery=ON 参数. 它表示损坏了没有关系,把所有的中续日志给卡杀掉,重新建个新的中续日志文件.叫IO线程往这个新的中续日志文件写.


事情到此基本上就完美了,为了保证数据一致性.然并卵的事情是

sync_relay_log_info=1000

sync_master_info=1000

这两个参数默认值是1K 为了性能不设置1,是避免频繁更新INNODB. 哎! 这回带来啥问题啊?

很明显的是, 我们原本很清楚知道SQL工作结束位置, 而现在设置成1K后,就无法准确,精确了. 

sync_relay_log=1000  && sync_relay_log_info=1000

SQL线程我很卖力气,实际上这1K事件我都恢复了.然系统挂了,丢失了999个事件,INFO 也丢失了888个事件.如果继续以mysql.slave_relay_log_info为标准,以前的工作岂不是白干了吗? 说真的白干不可怕,因为你要重新干过,再来一遍,就会导致数据重复,或者不一致性发生. 哦!MY GOD.


master_auto_position

master_auto_position的作用是根据从库的 Executed_Gtid_Set 自动寻找主库上对应 binlog 位置,这是在 GTID 出现后的一个功能。

  • 当第一次或者 reset slave 后,执行 start slave,io thread 将从库的 Executed_Gtid_Set 发往主库,获取到对应的 File、Position,之后更新到从库的 slave_relay_log_info、slave_master_info 表中
  • 当 slave_relay_log_info、slave_master_info 表中存在位置信息后,此后无论是重启复制还是重启 mysqld,都是直接从这两个地方获取 File、Position,并从这里开始读取 binlog 和回放 relay log
注意:执行 "reset slave" 会删除从库上的 relay log,并且重置 slave_relay_log_info 表,即重置复制位置。


如果 master_auto_position=0,下次启动复制时会从新开始获取并回放主库的 binlog,造成错误。


这需要我人工完成这操作.

reset slave;

2 CHANGE MASTER TO ...,MASTER_PORT=3306,MASTER_AUTO_POSITION=1,..;

3 START SLAVE;


其实我们不需要执行这波人工操作,因为relay_log_info_repository=TABLE,默认值 sync_relay_log_info=1000 此时同步事件会失效,变成每回放一个事务都会在这个事务里同时更新 mysql.slave_relay_log_info 表,保证持久性,以最终保证复制的数据一致。当然 InooDB 的持久性需要 innodb_flush_log_at_trx_commit=1 来保证。


这样一来 relay_log_info_repository=TABLE & relay_log_recovery=ON

就能保证数据一致性,知道自己SQL恢复到那个位置,已经恢复对应的主库日志位置.

sync_relay_log_info=1000mysql.slave_master_info 就没有什么意义了.


为了提高性能 参数设置

sync_master_info=1000

sync_relay_log = 1000


为了保证数据一致性

relay_log_info_repository=TABLE

master_info_repository=TABLE

relay_log_recovery=ON


从库INNODB 这块参数:

sync_binlog=1

innodb_flush_method = O_DIRECT

innodb_flush_neighbors=1

innodb_flush_log_at_trx_commit=1

log_slave_updates = OFF