MySQL复制
一、复制概述
复制是指将主库的 DDL 和 DML 操作通过 binlog 日志传到从库上,然后在从库上对日志重新执行,使得从库和主库数据保持同步
1.1 复制原理
a) 主库事务提交时会把数据变更作为事件 event 记录在 binlog 日志中;MySQL主库上的 sync_binlog 参数控制 binlog 日志刷新到磁盘的频率
b) 创建 I/O 线程连接主库,I/O 线程向主库请求数据
c) 主库创建 binlog dump 线程读取 binlog 中的 event 。发送给 I/O 线程
d) I/O 线程获取到 event 数据后更新到从库的 relay log 中
e) 从库上的 SQL线程读取中继日志 relay log中的 event,更新到数据库
二、三种binlog格式对复制的影响
2.1 SBR
基于语句级别的复制。binlog_format=statement,记录 SQL 语句到 binlog。
优点:
- Binlog文件较小
- 可用于审计
缺点:
- 执行insert select需要持有更多的行锁
- update要扫表
- auto_increment会阻塞其他insert语句
- 对一些系统函数复制会导致主从不一致
- Load_file()
- Uuid()
- User()
- Found_rows()
- Sysdate(),master执行时间是 00:00:00,由于有延时,到slave的执行时间是 00:00:01
- ……
2.2 RBR
基于行级别的复制。binlog_format=row,记录每一行的变化到 binlog
优点:
- 在某些情况下复制速度更快(SQL复杂,表有主键)
- 系统的特殊函数也可以复制
- 更少的锁
缺点
- Binary log比较大
- 单语句更新表的行数过多,会形成大量binlog。造成同步延迟(解决办法:使用SBR)
- 无法从binlog看见用户执行的SQL(mysql5.6增加了一个新的event:binlog_row_query_log_events,记录用户的query)
- 对于非事务引擎,持有的锁比SBR多。使用RBR在SLAVE上没有并行插入
- 生成BLOB的值花费的时间比SBR长
行复制流程:
a) master更新数据,写入binlog。
b) Slave在master上注册io_thread接受日志变更
c) Io_thread接收变更之后,会写入到本地的realy_log
d) 通过sql_thread进行解析,执行
e) 判断是否有主键
f) 如果有主键,就进行主键匹配、写入
g) 如果没有主键,就使用二级索引进行匹配
h) 如果没有二级索引,就只能进行全表扫描
2.3 MIXED
混合使用row和statement格式。对于DDL记录使用statement格式,对于table的行操作记录使用row格式
binlog_format=mixed
- 如果使用innodb表,事物级别使用READ COMMIT和READ UNCOMMIT只能使用row格式
- 但在使用ROW格式中DDL语句还是会记录成statement格式
- Mixed模式,以下几种情况会将binlog_format由SBR改成RBR
- 当DML语句更新一个NDB表时
- 当函数中包含UUID()时
- 2个及以上包含AUTO_INCREMENT字段的表被更新时
- 行任何INSERT DELAYED语句时
- 用UDF时
- 试图中必须使用RBR。例如创建视图使用了UUID()函数
三、GTID
- GTID全称是global transaction identifieds(全局的事务标志)
- 一个事物对应一个唯一ID
- GTID组成:server_uuit + transaction_id
- server_uuit 是全局唯一,默认保存在 $DATADIR/auto.cnf,如果有重复,删除了重启MySQL
- transaction_id 是自增的
- 搭建GTID需要配置以下参数
- gtid_mode=on
- enforce-gtid-consistency=on
- log-bin=mysql-bin
3.1 GTID的意义
传统复制模式下
当 master 宕机后,一个 slave 被提升为 new master,如果需要继续维持复制关系,就需要把另外两个 slave 的 change master 指向 new master。那么问题就来了,原来 slave 是指向 master 的 filename + pos 的位置,现在要指向 new master 上新的 filename + pos 位置,这两个位置是比较难对应起来的,此时两个 slave 要 change master 比较麻烦
GTID模式下
和上面的场景一样,两个 slave 需要重新指向 new master,由于使用了 GTID,目前 slave-A 获取到的日志对应的GTID=G_A,slave-B 获取到的日志对应GTID=G_B。此时 new_master上存在G_A和G_B(通过选举出来的,获取的日志应该是最多的),那两个slave就可以直接使用 G_A和G_B这两个GTID,通过指向 new master 接着继续复制
3.2 GTID 与 filename-pos 的对应
在binlog中,多了一个 GTID 的 event,通过扫描 binlog 中的GTID值,就可以知道 GTID 与 filename-pos的关系,如下所示
+-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+ | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | +-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+ | mybinlog.000057 | 4 | Format_desc | 873306 | 120 | Server ver: 5.6.37-log, Binlog ver: 4 | | mybinlog.000057 | 120 | Previous_gtids | 873306 | 151 | | | mybinlog.000057 | 151 | Gtid | 873306 | 199 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:1' | | mybinlog.000057 | 199 | Query | 873306 | 274 | BEGIN | | mybinlog.000057 | 274 | Table_map | 873306 | 335 | table_id: 70 (hb.heartbeat) | | mybinlog.000057 | 335 | Update_rows | 873306 | 485 | table_id: 70 flags: STMT_END_F | | mybinlog.000057 | 485 | Xid | 873306 | 516 | COMMIT /* xid=10 */ | | mybinlog.000057 | 516 | Gtid | 873306 | 564 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:2' | | mybinlog.000057 | 564 | Query | 873306 | 639 | BEGIN | | mybinlog.000057 | 639 | Table_map | 873306 | 700 | table_id: 70 (hb.heartbeat) | | mybinlog.000057 | 700 | Update_rows | 873306 | 850 | table_id: 70 flags: STMT_END_F | | mybinlog.000057 | 850 | Xid | 873306 | 881 | COMMIT /* xid=16 */ | | mybinlog.000057 | 881 | Gtid | 873306 | 929 | SET @@SESSION.GTID_NEXT= '987c1082-8001-11e8-a03b-52540089b89e:3' | +-----------------+--------+----------------+-----------+-------------+---------------------------------------------------------------------+
但是如果 binlog 很大,扫描的成本会很大,所以在 binlog 的开头部分有一个 Previous_gtids 的 event。如果我要的 GTID 比 Previous_gtids 的大,就扫描当前文件,反之扫描之前的文件,以此类推。因为 binlog 在 rotate 的时候,是知道当前最大的 GTID 的,可以将该值写入到下一个新的 binlog 的开头,即 Previous_gtids
3.3 GTID 复制出错处理
3.3.1 slave 上插入一行数据
insert into a select 2;
3.3.2 master 上插入同样数据
insert into a select 2;
3.3.3 查看复制状态
root@localhost:mysql.sock 10:17: [vcyber]>show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.84.87 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mybinlog.000058 Read_Master_Log_Pos: 52587080 Relay_Log_File: mysql-relay-bin.000006 Relay_Log_Pos: 319820 Relay_Master_Log_File: mybinlog.000058 Slave_IO_Running: Yes Slave_SQL_Running: No Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 1062 Last_Error: Could not execute Write_rows event on table vcyber.a; Duplicate entry '2' for key 'id_2', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mybinlog.000058, end_log_pos 52584859 Skip_Counter: 0 Exec_Master_Log_Pos: 52584646 Relay_Log_Space: 52587694 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 1062 Last_SQL_Error: Could not execute Write_rows event on table vcyber.a; Duplicate entry '2' for key 'id_2', Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mybinlog.000058, end_log_pos 52584859 Replicate_Ignore_Server_Ids: Master_Server_Id: 873306 Master_UUID: 987c1082-8001-11e8-a03b-52540089b89e Master_Info_File: /data/mysql/mysql_3306/data/master.info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: 180723 10:17:19 Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: 987c1082-8001-11e8-a03b-52540089b89e:1-146477 Executed_Gtid_Set: 81e0915b-5187-11e8-b129-5254004a7fb5:1, 987c1082-8001-11e8-a03b-52540089b89e:1-146470 Auto_Position: 1
3.3.4 处理复制步骤
a) 将 slave 的 gtid_next 指向执行失败 gtid
Executed_Gtid_Set: 987c1082-8001-11e8-a03b-52540089b89e:1-148588 是已经执行过的 GTID,gtid_next 的值应该是148589
b) 执行一个空的事务,即 begin;commit;
c) 将 gtid 还原为 automatic
-- 设置 gtid_next 为失败的 gtid set gtid_next='987c1082-8001-11e8-a03b-52540089b89e:148589'; -- 执行一个空的事务 begin;commit; -- 还原为 automatic set gtid_next='automatic'
3.3.5 启动 sql_thread,查看复制状态
start slave sql_thread;
show slave status\G
四、并行复制
MySQL5.7复制的前提是,所有处于 prepare 阶段的事务都是可以并行提交的
5.7的 binlog 比 以前版本多了以下两个内容
- last_committed:一个 last_commited 代表 一个组
- sequence_number:一个 sequence_number 代表一个事务
如图所示,last_committed有3个值,分别是 0 1 4,这代表着 3 个组。
laster_commit = 4 中有3个 sequence,5 6 7,代表着 3 个事务,这 3 个事务可以并行复制。每一组中的 last_commited 值,都是上一个组中事务的 sequence_number 最大值。只要切换 binlog 文件,技术从0开始
4.1 ordered_commit
当事务提交,会经过三个阶段 FLUSH、SYNC 及 COMMIT。这三个步骤分别有各自的队列。
4.1.1 FLUSH 阶段
- 首先进入 FLUSH 队列,如果某个事物加入时,队列是空的,则担任这个事务的队长,来代表其他事务执行提交操作。其他事务继续加入时,发现已经有了队长,那么就跟随队伍。
- 当队长将这个队列中的事物取出期间,其他事务进入FLUSH队列需要等待。
- 为每个事物分配一个 sequence_number。为组分配 last_committed。last_committed=sequence_number-1
- 将Redo log中prepare阶段的数据刷盘
- 将binlog数据写入文件,当然此时只是写入文件系统的缓冲,并不能保证数据库崩溃时binlog不丢失
- Flush阶段队列的作用是提供了Redo log的组提交
- 如果在这一步完成后数据库崩溃,由于协调者binlog中不保证有该组事务的记录,所以MySQL可能会在重启后回滚该组事务
- 等到队长取出队列中所有事务。其他事务就可以加入这个队列。
4.1.2 SYNC阶段
这里为了增加一组事务中的事务数量,提高刷盘收益,MySQL使用两个参数控制获取队列事务组的时机:
- binlog_group_commit_sync_delay=N:在等待N μs后,开始事务刷盘(图中Sync binlog)
- binlog_group_commit_sync_no_delay_count=N:如果队列中的事务数达到N个,就忽视binlog_group_commit_sync_delay的设置,直接开始刷盘(图中Sync binlog)
Sync阶段队列的作用是支持binlog的组提交
如果在这一步完成后数据库崩溃,由于协调者binlog中已经有了事务记录,MySQL会在重启后通过Flush 阶段中Redo log刷盘的数据继续进行事务的提交
4.1.3 commit阶段
- 首先获取队列中的事务组
- 依次将Redo log中已经prepare的事务在引擎层提交(图中InnoDB Commit)
- Commit阶段不用刷盘,如上所述,Flush阶段中的Redo log刷盘已经足够保证数据库崩溃时的数据安全了
- Commit阶段队列的作用是承接Sync阶段的事务,完成最后的引擎提交,使得Sync可以尽早的处理下一组事务,最大化组提交的效率
4.2 多线程复制分发原理
- 从库 SQL thread 拿到一个新事务,取出 last_committed 及 sequence_number 值
- 判断当前 last_committed 是否大于当前已经执行的 sequence_number 的最小值
- 如果大于,则说明上一个组的事务还没有完成。此时等待最小值变大,知道 last_committed与最小值相等,才可以继续
- 如果小于或等于,则说明当前事务与正在执行的组是同一个组,不需要等待
- SQL thread 通过统计,找到一个空闲的 worker 线程,如果没有空闲则等待;
- 将当前事务打包,交给 worker去 APPLY这个事务
4.3 并行复制相关参数
- slave-parallel-type:并行模式
- LOGICAL_CLOCK:逻辑时钟
- DATABASE:基于库级别
- slave-parallel-workers:并行复制的线程
- slave_preserve_commit_order:对于多线程slaves,来保障事务在slave上执行的顺序与relay log中的顺序严格一致,只有当“slave_parallel_workers”开启时有效;此时“log_bin”、“log_slave_updates”必须开启,而且“slave_parallel_type”值必须为“LOGICAL_CLOCK”(默认值为DATABASE)。即当多线程开启时,且根据relay log中事务的逻辑顺序执行statements,是否需要严格保持顺序,默认值为0表示并发执行忽略顺序。
- binlog_group_commit_sync_no_delay_count:等待一组里面有多少事物我才提交
- binlog_max_flush_queue_time:等待多少时间后才进行组提交
五、复制相关参数
- log-bin:是否打开binlog日志,不可动态修改
- server-id:数据库唯一的标识。以后可能会被server-uuid取代(gtid)
- log-bin-index:指定mysql-logbin.index的位置
- binlog_format:binlog日志格式:statement、row、mixed三种。可动态修改
- binlog_cache_size:binlog写入的buffer,可以动态修改。通常配置成1M
- max_binlog_size:指单个binlog文件的大小。默认是1G
- sync_binlog:多少个SQL后,刷新binlog到disk。默认是0系统自动刷新,0是性能最好的。如果是交易型的,建议是1,1是每个SQL刷新到磁盘中。1是性能最差的。
- expire_logs_days:指N天后自动删除binlog。默认没有开启,就会遇到binlog把磁盘占满
- log_bin_trust_function_creators:存储过程调用到的。假如存储过程在测试环境创建的,导出之后就带着用户名。到线上的时候就调用不了,这时候打开这个参数就好了。
- log_warnings:异常的断开连接Abort connection,拒绝连接请求。默认是0。等于1是不会记录。
- binlog_error_action:默认是不开启,可以开启binlog_error_action=ABORT_SERVER。假如磁盘满了,binlog写不进去,就会在应用层直接报错。这个参数是5.6之后才有
- binlog_row_image :5.6之后的参数。在用row的时候,日志很大。binlog_row_image=[full|minimal|noblob]
- full: update tb set c2=5 where id=1;会记录变更之前的记录,还有之后的记录。默认值
- minimal:假如update tb set c2=xxx where id=xxx;假如一个表有10个字段,然后值update c2字段,只记录c2跟主键。
- noblob:如果没有涉及到blob字段的update,就不记录blob
- binlog_rows_query_log_events=1|0:默认是关闭的,row格式下只记录具体变更的内容。开启这个参数后,会把用户执行的SQL写入到binlog
- slave-skip-errors=1062,1053,1146 #跳过指定error no类型的错误
-- GTID相关的参数
- gtid_mode=on:打开GTID
- log-slave-updates : 从库上也会记录binlog
- enforce-gtid-consistency:控制GTID,哪些语句可以记录到LOG中。
- gtid_next:跳过下一个事物
- gtid_purged:进行同步的时间,忽略之前的数据。gtid_purged=uuid:1000;忽略1000以前个事物,需要reset master才能用。
- gtid_executed:GTID到底执行过多少事物
-- SLAVE相关的参数
- server-id:数据库的唯一标识
- relay-log:slave存放的日志路径
- relay-log-index
- read-only :只读,但是对super用户没 有作用
- log-slow-slave-statements: sql_thread 执行sql超过long_query_time 会记到慢日志中
- log_slave_updates
- max_relay_log_size:设置relay日志的大小
- relay-log-info-file:假如从库挂掉了,同步接不上了。从relay.info上读取信息。从这些信息向master重新请求。
- relay_log_info_repository:table --SQL thread crash safe – SQL线程安全
- relay-log-recover:1 --I/O thread crash safe – IO线程安全
- master_info_repository = TABLE
- read_only = 1
- binlog_gtid_recovery_simpliefied :默认是不开启。假如从库挂了,重新启动slave,会从新主库拉一下relay log。
- relay_log_purge
- relay_log_recovery
- replicate-same-server-id :默认是不开启。复制自己相同的server-id。建议不开启
- skip-slave-start:从库启动之后,不自动开启同步。
- slave_transaction_retries:执行SQL语句的时间,把区间给锁了,sql_theard就要等这些锁的释放。就会有一个重试的过程,当超过这个重试的次数,就会报错。这时候start slave sql_thread就可以修复
- slave_parallel_workers:5.6的参数。并行复制,默认是关闭;如果有2个库,就可以定义slave_parallel_workers=2
- relay_log_space_limit:sql_thread=NO, IO_THREAD=YES,会发现本地的relay log越来越多,磁盘使用就会越来越多。限定relay占用磁盘的大小。
- sync_master_info:master info存到数据库的表里面。默认情况下是1000秒,如果在5.6.17之前,配置成1,就会遇到内存溢出的错误。
- sync_relay_log_info :relay info 存到数据库的表里面。
- slave_net_timeout:从库跟主库多少时间断开,才认为是连不上主库。默认是3600秒。推荐配置成10秒
- slave_skip_errors:slave上的参数,忽略错误。不建议忽略错误
- sql_slave_skip_counter:忽略多少个复制事件,遇到个别错误(主键冲突、记录不存在等)时,可以忽略这些事件,继续复制进程。一般一次只忽略一个事件,除非很肯定,否则不要设置大于1
六、复制过滤规则
-- master端:
- binlog-do-db = "abc":只记录 abc 库的binlog
- binlog-ignore-db = "abc":不记录 abc 库的binlog
-- slave端
- replicate-do-db = "abc":只想记录某个DB的binlog
- replication-ignore-db="mysql":忽略某个库
- replicate-do-table = wubx.tb;:只记录某个表
- replicate-ignore-table=wubx.quota:忽略某个表
- replicate-rewrite-db='a_db->b_db':把A库复制到B库
- replicate-wild-do-table="wubx.ds_%":只记录ds开头的表
七、复制维护管理
7.1 查看从库状态
root@localhost:mysql.sock 17:35: [(none)]>show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.84.88 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mybinlog.000002 Read_Master_Log_Pos: 2967 Relay_Log_File: mysql-relay-bin.000008 Relay_Log_Pos: 359 Relay_Master_Log_File: mybinlog.000002 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 2967 Relay_Log_Space: 532 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 883306 Master_UUID: 81e0915b-5187-11e8-b129-5254004a7fb5 Master_Info_File: /data/mysql/mysql_3306/data/master.info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for the slave I/O thread to update it Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: 6e6cd1a5-5187-11e8-b129-52540089b89e:1-11 Auto_Position: 1
io_thread: master_log_file , read_master_log_pos
sql_thread: relay_master_log_file, exec_master_log_pos
比较一下是不是同步完成?
master_log_file = relay_master_log_file
read_master_log_pos = exec_master_log_pos
7.2 主从一致性数据校验与修复
pt-table-checksunm:利用语句级别复制,主从库上对表分段进行校验,如果值不一样,说明不同步。
pt-table-sync:利用语句级别复制,所有的修复都是在主库上修复。
八、Replication crash safe
8.1 SQL thread crash safe
8.1.1 relay_log_info_repository 为 FILE 情况下
如果将 relay_log_info_repository 设置为 FILE,MySQL会把回放记录在 relay-info.log 文件中,其中包含 SQL 线程回放到的 relay_log 和 relay_log_pos,以及对应的 master 的 master_log 和 master_log_pos
- SQL 线程回放 event
- 将回放到的 binlog 文件名和位置写到 relay-info.log 文件
- 参数 sync_relay_log_info = 10000 (fsync) 代表每回放 10000个 event,写一次 realy-info.log,如果设置为 1,则表示每回放一个event,就写一次relay-info.log,那么性能会很差。而且设置为 1 后,即使性能上可以接受,可还是会丢失最后一次的操作。
当一个Slave节点在复制数据时,可能发生如下情况, 数据2和数据3写入成功(且已经落盘),但是relay-info.log 中的记录还是数据1的位置(因为sync_relay_log_info 的关系,此时还没有fsync),此时Slave宕机,然后重启,便会产生如下的状况:
- Slave的库中存在数据2和数据3
- Slave读取 relay-info.lo g中的 Relay_log_name 和 Relay_log_pos ,此时记录的是回放到数据1的位置
- Slave 从数据1开始回放,继续插入数据2和数据3
- 但是,此时的数据库中存在数据2和数据3 ,于是发生了1062 的错误(重复记录)
8.1.2 relay_log_info_repository 为 TABLE 情况下
在 MySQL5.6 之后,将 relay_log_info_repository 设置为 TABLE,relay-info 写到 mysql.slave_relay_log_info 这张表中
设置为 TABLE 的原理为,将 event 的回放 和 relay-info 的更新放在同一个事物里面,变成原子操作,从而保证一致性
BEGIN;
apply log event;
apply log event;
UPDATE mysql.slave_relay_log_info
SET Master_log_pos = Exec_Master_Log_Pos,
Master_log_name = Relay_Master_Log_File,
Relay_log_name = Relay_Log_File,
Relay_log_pos = Relay_Log_Pos;
COMMIT;
8.2 I/O thread crash safe
IO thread 也是接受一个个的 event,将接受到的 event,通过设置参数 master_info_repository 将 master-info(IO线程接受到的位置,master_log_name 和 master_log_pos) 信息写入到文件或者数据库表中,然后将接受到的 event 写入 relay log file。
参数 sync_master_info = 10000 表示每接收 10000 个 event,写一次 master-info
假设存在下面这个情况,event2和event3已经写入到了relay-log,但是master-info还没有同步到master-info.log。此时如果服务宕机后,MySQL重启,I/O线程会读取master-info.log的内容,读取到的位置为event1的位置,然后I/O线程会继续将event2和event3拉取过来,然后继续写入到relay-log 中。event2 和 event3 被重复写入到了relay-log文件中,当SQL线程回放时,就会产生1062 的错误(重复记录),看到的现象还是SQL线程报错,IO线程正常,但其实是因为 IO线程重复写入了。
8.2.1 master_info_repository
IO thread 同样也提供了 master_info_repository参数,TABLE 记录数据库表,FILE 记录文件。但是可以解决 crash 问题吗?
答案是不可以,为什么?
因为 IO thread 写的是 relay-log 文件吧,即使 master-info 设置为 TABLE 也无法解决宕机问题。
但是将 master-info-repository 设置为 TABLE,可以提高性能
设置为TABLE 后, master-info 将信息保存到mysql.slave_master_info 中。
那么 IO thread crash应该怎么解决?
relay-log-recover
8.2.2 relay-log-recover
relay-log-recover = 1,该参数表示 IO thread crash 重启之后,把当前接收的所有 relay-log 都删除掉,然后以 SQL thread 应用到的位置,重新去 master 请求日志
注意:以上参数都是 5.6 之后才有的
九、复制延迟
9.1 Seconds_Behind_Master
Seconds_Behind_Master 字面意思为Slave落后Master的秒数。计算的方式为: Slave的SQL线程执行时刻的时间减去 event产生时刻的时间(event_header 中的前 4个字节就是timestamp )。字面意思:当 sql_thread 执行 event 的时候才会计算。那么当 io_thread 延迟或者 stop。那么这时候,sql_thread 已经把接收到的 relay_log应用完,那么Seconds_Behind_Master 为0,但是实际上,主从有延时。所以 Seconds_Behind_Master 只能计算 sql_thread的延迟,无法计算 io_thread。那么有什么办法解决这个问题?答案是:pt-heartbeat
9.2 pt-heartbeat
9.2.1 pt-heartbeat 的原理
在 Master 节点上创建一个带有时间戳字段的表, pt-heartbeat --update 每隔一段时间写入时间戳,通过主从复制,将表中的内容(带有时间戳的记录)复制到Slave节点,通过运行pt-heartbeat --monitor 的服务器的本地时间戳减去复制到Slave节点的记录的时间戳,就可知道Slave落后Master的时间
9.2.2 使用pt-heartbeat
# 主库:
pt-heartbeat --update -u'root' -p'abc#1234' -S '/data/mysql/mysql_3306/tmp/mysql.sock' -D hb --create-table --daemonize
-D:指定数据库
--create-table:创建存储时间戳的表
--daemonize:后台运行
# 从库:
pt-heartbeat --monitor -u'root' -p'abc#1234' -S '/data/mysql/mysql_3306/tmp/mysql.sock' --master-server-id 873306 -D hb
0.00s [ 0.00s, 0.00s, 0.00s ]
0.00s [ 0.00s, 0.00s, 0.00s ]
0.00s [ 0.00s, 0.00s, 0.00s ]
# monitor 从slave的 hb.heartbeat中找到master同步过来的记录,然后和本地系统时间做差值计算,得到落后时间
9.2.3 主从延迟的几个可能原因
- 主从配置不一致,包括硬件,参数等
- 主库 TPS 太高,从库单线程回放
- 主库运行了大事务
- row 模式下没有主键最为致命
- 主库运行了 DDL 语句造成延时
参考书籍:《MySQL运维内存》
参考链接:https://mp.weixin.qq.com/s/_LK8bdHPw9bZ9W1b3i5UZA