本文对 MySQL 复制功能和用法做了整理和介绍。
复制(Replication)解决的问题
- 数据分布
- 负载均衡和读写分离
- 备份
- 高可用性和故障切换
- 大表 DDL 避免锁表
- MySQL 升级测试
复制的工作原理
- 主库将数据更改记录到 binlog 中
- 备库将主库上的日志复制到自己的 relaylog 中
- 备库读取 relaylog 中的事件,将其在备库上重放
- 实现方式是:备库会启动一个 I/O 线程,I/O 线程会跟主库建立一个客户端连接,然后在主库上启动一个 binlog dump 线程,该线程不断把新的事件传送给备库
- 使用复制,需要在主库上增加一个配库使用的账号,该账号需要有
REPLICATION SLAVE,REPLICATION CLIENT
权限
复制的使用
- 配置 master
# 1) my.cnf 配置文件
[mysqld]
log-bin=mysql-bin
server-id=1
# 2) 获取主库 binlog 位点
mysql> SHOW MASTER STATUS;
File: mysql-bin.000001
Position:98
- 配置 slave 并开启复制
# 1) my.cnf 配置文件
[mysqld]
log_bin = mysql-bin
server_id = 2
relay_log = mysql-relay-bin
log_slave_updates = 1
read_only = 1
# 2) 设置主库和位点
mysql> CHANGE MASTER TO MASTER_HOST='192.168.0.1',
-> MASTER_PORT=3306
-> MASTER_USER='repl',
-> MASTER_PASSWORD='p4ssword',
-> MASTER_LOG_FILE='mysql-bin.000001',
-> MASTER_LOG_POS=98;
# 3) 检查位点是否设置正确
mysql> SHOW SLAVE STATUS\G
# 4) 开启复制
mysql> START SLAVE ;
- SHOW SLAVE STATUS 中各个位点信息的含义
Master_Log_File # 目前拉取的主库文件
Read_Master_Log_Pos # 目前拉取的主库文件位置
Relay_Log_File # 当前执行的本库 relay log 文件
Relay_Log_Pos # 当前执行的 relay log 文件位点
Relay_Master_Log_File # 当前执行到的对应的主库文件的位置
- 暂时关闭 binlog:
set sql_log_bin=0;
- 安全删除 binlog
PURGE MASTER LOGS TO 'mysql-bin.007379'; # 按文件名删除
PURGE MASTER LOGS BEFORE '2003-04-02 22:46:26'; # 按时间删除
复制的类型
- 基于语句的复制
- 复制快,节约 binlog 空间
- 复杂场景下(使用函数或存储过程等)存在各种异常和 bug
- 基于行的复制
- 简单灵活适应性强
- 数据有冗余,出错方便修复
- DDL 还是语句的复制
- 混合性复制
- 适应性最强
- DDL 也是基于行的复制,binlog 会超级大
- 可以理解为,基于行复制的扩展
- 基于 GTID 的复制
- 简单灵活,不用记录位点,会根据全局事务ID自动寻点
- 因为是基于事务的复制(也是基于行的),所以不支持非事务的存储引擎
- 可以理解为,基于行赋值的升级
复制异常
- 写冲突,insert 的主键/唯一键已存在,处理的方式往往是:
- 记录下错误发生的表和对应主键
- 使用
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
跳过这次冲突 - 检查主备库中对应主键的记录是否一致
- 写异常,update 前镜像不一致,导致数据更新失败:
- 使用基于行的复制时,update 操作在备库上会匹配前镜像,也就是带有 where 条件(见下节 binlog 文件结构)
- 当前镜像不匹配时,update 中最新的记录不会更新到备库
- 这种情况往往发生在位点错误或 binlog 丢失的情况下
binlog
- 把 binlog 解析成可读文件
mysqlbinlog binlog.000001 --database=name --start-datetime=name \
--stop-datetime=name --start-position 245770410 \
--stop-position 245793673 -f -v \
--base64-output=decode-rows > tmpfile
- binlog 解析后的文件结构(update 操作)
BEGIN
/*!*/;
# at 92105505
# at 92105582
#160525 19:44:50 server id 101717547 end_log_pos 92105582 Table_map: `im_user_data_0005`.`im_user_cfg_tbl` mapped to number 1450
#160525 19:44:50 server id 101717547 end_log_pos 92106296 Update_rows: table id 1450 flags: STMT_END_F
BINLOG '
MpBFVxMrFhAGTQAAAG5rfQUAAKoFAAAAAAEAEWltX3VzZXJfZGF0YV8wMDA1AA9pbV91c2VyX2Nm
Z190YmwACAMP/AMBAQwMA0AAA8Q=
(二进制信息略)
'/*!*/;
### UPDATE im_user_data_0005.im_user_cfg_tbl
### WHERE
### @1=222 /* INT meta=0 nullable=0 is_null=0 */
### @2='xxxxx' /* VARSTRING(64) meta=64 nullable=0 is_null=0 */
### @3='xxxxx' /* MEDIUMBLOB/MEDIUMTEXT meta=3 nullable=1 is_null=0 */
### @4=222 /* INT meta=0 nullable=0 is_null=0 */
### @5=1 /* TINYINT meta=0 nullable=0 is_null=0 */
### @6=1 /* TINYINT meta=0 nullable=0 is_null=0 */
### @7=2012-07-12 11:22:13 /* DATETIME meta=0 nullable=1 is_null=0 */
### @8=2016-05-24 20:23:03 /* DATETIME meta=0 nullable=1 is_null=0 */
### SET
### @1=1111 /* INT meta=0 nullable=0 is_null=0 */
### @2='xxxxx' /* VARSTRING(64) meta=64 nullable=0 is_null=0 */
### @3='xxxxx' /* MEDIUMBLOB/MEDIUMTEXT meta=3 nullable=1 is_null=0 */
### @4=1111 /* INT meta=0 nullable=0 is_null=0 */
### @5=1 /* TINYINT meta=0 nullable=0 is_null=0 */
### @6=1 /* TINYINT meta=0 nullable=0 is_null=0 */
### @7=2012-07-12 11:22:13 /* DATETIME meta=0 nullable=1 is_null=0 */
### @8=2016-05-25 19:44:50 /* DATETIME meta=0 nullable=1 is_null=0 */
# at 92106296
#160525 19:44:50 server id 101717547 end_log_pos 92106323 Xid = 3286612943
COMMIT/*!*/;
常见的拓扑结构
每一个 MySQL 实例都可以即做主库由做备库,一个主库可以有多个备库,一个备库只能有一个主库。
理论上 MySQL 可以支持各种复杂(网状)的复制拓扑结构,但是不同的业务场景会限制复制拓扑的设计。
- 主主结构
- 两个互为主库,但是应用只写其中一个,另一个可以作为热备份,或者作为只读库分担读流量
- 使用灵活,运维方便(因为切换方便)
- 一主多备
- 在读流量极大的情况下,需要这种一个主库(提供数据更新服务)多个备库(提供数据查询服务)的结构
- 这种结构运维的难点是主库 Crash 后,需要重新选择一个数据最完整的备库作为新的主库
主备一致性保证
- semi-sync (半同步)
- 半同步介于异步和全同步(sync_binlog 打开)之间
- 主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可
- 半同步保证了主备的最终一致性,同时降低了主库事务 commit 的等待时间
参考资料
《高性能MySQL(第3版)》