MySQL主从复制¶
零. 架构¶
master 192.168.100.101
salve 192.168.100.100
注意: master和slave的密码策略保持一致。可以使用mysql_secure_installation命令进行修改。
二. 主从复制的原理¶
1. 传统主从复制的一些概念¶
在master上开启二进制日志;创建用于主从复制的账户及授权;
在slave/上开启中继日志;设置change source to语句;
change source to语句的含义:从知道去哪连接的master是谁,使用哪个账号去连接,连接的密码又是什么,请求master上的哪个二进制文件,从该二进制文件的哪个位置开始。
在slave上启动主从复制 start replica;
slave会产生两个线程:
IP线程,连接master并向其发送请求,将获取到的二进制文件内容保存到中继日志中
SQL线程:读取中继日志中的内容并转换成SQL语句,然后执行。
二进制日志的记录方式:
基于语句statment
基于行ROW
基于混合mixed
二进制日志记录数据发送变化/更改的操作。如增删改表、建库、删库、删表等。
传统的主从复制是基于二进制实现的。
2. GTID¶
1). 事务
事务是将MySQL的多个相关联操作打包起来的工作单元,其具有以下四大特性
-
原子性 (Atomicity):执行单元内的操作不可分割。要么全部提交成功,要么全部回滚失败。实现靠的是undo log(回滚日志),它记录数据修改前的旧值,一旦要回滚,就用旧值恢复数据。
-
一致性 (Consistency):事务执行前后,数据必须符合所有约束规则(如外键、唯一索引等)。这是数据库的最终目标,原子性、隔离性和持久性共同为它服务。
-
隔离性 (Isolation):多个事务并发执行时,互不干扰。实现靠的是锁和MVCC(多版本并发控制)。
-
持久性 (Durability):一旦事务提交,数据就永久保存在硬盘上,即使宕机也不丢失。实现靠的是redo log(重做日志),它记录数据修改后的新值,用于崩溃恢复。
2). GTID
GTID是MySQL为每一个提交的事务分配的全局唯一标识符。GTID可以使得事物可以通过GTID来简单进行定位。
3. 主从复制的过程¶
a. 当主库与从库连接后,从库利用I/O线程登录主库用于复制的账户账号密码完成身份和权限的验证
b. 身份验证通过后,主库会为这个连接创建对应的Binlog Dump线程,专门负责把Binlog事件推送给从库的I/O线程。
c. 当主库提交事务后,主库的事务和其GTID会被写入到binlog二进制文件中
d. 复制账户利用主库的Binlog Dump线程从Binlog中读取这些事务事件(GTID作为事件头的一部分),推送给从库的I/O线程。I/O线程将其原样写入从库的中继日志(Relay Log) 中。
e. 从库上的SQL线程将中继日志中的事务在从库上执行,从而完成主从复制。
三. 实现主从复制¶
0. 配置时间同步¶
我们使用chrony工具实现时间同步
直接用我写好的脚本来安装,内容如下
#! /bin/bash
#安装时间同步服务chrony
mount /dev/sr0 /media/cdrom &>/dev/null
sleep 2
dnf -y install chrony &>/dev/null
sed -i 's/^pool/#pool/' /etc/chrony.conf
cat << EOF >> /etc/chrony.conf
server ntp.aliyun.com iburst
server ntp1.aliyun.com iburst
server ntp2.aliyun.com iburst
server ntp3.aliyun.com iburst
EOF
systemctl restart chronyd
sleep 5
if chronyc sources -v|grep -q '^\^\*';then
echo "chronyd同步成功"
else
echo "chronyd同步失败"
fi
将该脚本在两台主机上分别执行
1. 配置3作为master¶
a. 修改mysql的配置文件
[root@3 ~]# vi /etc/my.cnf
[client]
socket = /usr/local/mysql/mysql.sock #客户连接使用的socket
[mysqld]
server_id=10 #将这台 MySQL 服务器的 ID 指定为 10
port = 3306 #默认监听的端口为3306
user = mysql #mysql进程的维护用户为mysql
character-set-server = utf8mb4 #默认字符集
default_storage_engine = innodb #默认存储引擎
log_timestamps = SYSTEM #日志时间戳使用系统时间
socket = /usr/local/mysql/mysql.sock #服务端的socket文件存放的位置
basedir = /usr/local/mysql #mysql的安装目录
datadir = /data/mysql/data #mysql数据文件的存放目录
pid-file = /usr/local/mysql/mysql.pid #mysql进程id存放位置
log_error = /data/mysql/log/mysql-error.log #mysql错误日志的存放位置
sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
#STRICT_TRANS_TABLES 严格模式核心 — 对事务表启用严格模式,插入/更新数据时若值无效(如类型错误、超出长度),直接报错而非发出警告
#NO_ZERO_IN_DATE 不允许月份或日为零的日期,例如 '2010-00-01'、'2024-01-00' 会被拦截。现已被废弃,但不会报错
# 不允许 '0000-00-00' 作为合法日期。现已被废弃,但不会报错
#ERROR_FOR_DIVISION_BY_ZERO 除零操作直接报错,而非返回 NULL 并给警告
#NO_ENGINE_SUBSTITUTION 建表时如果指定了被禁用的存储引擎(如 Memory 被禁用),直接报错,而非自动替换为默认引擎(InnoDB)
skip-name-resolve #跳过DNS反向解析
innodb_print_all_deadlocks = 1 #每次发生死锁,都把详情写到错误日志里
log_bin = /data/mysql/log/binlog #开启二进制文件
binlog_format = ROW #以行格式记录二进制的内容
slow_query_log = ON #开启慢查询日志
slow_query_log-file = /data/mysql/log/slow.log
#记录慢查询日志的select语句
long_query_time = 1 #查询超时时间
log_queries_not_using_indexes = ON #查询语句没有使用后索引也会记录到慢查询日志文件中
gtid_mode=ON #开启GTID模式
enforce-gtid-consistency=ON #强制GTID一致性,开启后,违反 GTID 一致性的语句会被直接拒绝执行,确保复制不会出错。
log_slave_updates=ON #记录从库上执行的中继日志事务到从库自己的 binlog。
binlog_expire_logs_seconds=604800 #二进制日志binlog的自动过期时间设置为604800 秒
sync_binlog=1 #每次事务提交都强制将 binlog 刷入磁盘。
binlog_cache_size=4M #在事务期间用于缓存 binlog 写入的内存缓冲区大小。
max_binlog_cache_size=2G #单个事务的binlog缓存上限为2G
innodb_buffer_pool_size=10G #innodb缓冲池大小设置为10GB
innodb_log_file_size=1G #单个 InnoDB redo log(重做日志) 文件的大小,设为 1GB
innodb_log_buffer_size=64M #InnoDB redo log 的缓冲区大小,设为 64MB。
innodb_flush_log_at_trx_commit=1 #每次事务提交时,强制将 redo log 刷入磁盘。
innodb_file_per_table=1 #每个 InnoDB 表使用独立的表空间文件(表名.ibd),而不是全部存到共享的 ibdata1 中。
max_connections=5000 #MySQL 允许的最大并发连接数为 5000。
max_allowed_packet=64M #客户端与服务器之间传输的单个数据包最大为 64MB。
thread_cache_size=64 #MySQL 缓存待复用的线程数量为 64 个。
binlog-do-db=school #只主从复制school这个库
binlog-ignore-db=information_schema #不复制
binlog-ignore-db=mysql #不复制
binlog-ignore-db=performance_schema #不复制
binlog-ignore-db=sys #不复制
b. 重启服务
[root@3 log]# systemctl start mysqld
[root@3 log]# ss -tuan|grep 3306
tcp LISTEN 0 70 *:33060 *:*
tcp LISTEN 0 151 *:3306 *:*
c. 在3上创建用于主从复制的账户,并给予他相应权限
[root@3 ~]# mysql -uroot -p
mysql> CREATE USER 'repl_user'@'192.168.100.100' identified by 'Abcd1234';
Query OK, 0 rows affected (0.04 sec)
mysql> #创建用户'repl_user'@'192.168.100.100',密码为'Abcd1234'
mysql> GRANT REPLICATION CLIENT,REPLICATION SLAVE ON *.* TO 'repl_user'@'192.168.100.100';
Query OK, 0 rows affected (0.01 sec)
mysql> #赋予该用户REPLICATION SLAVE(复制从库权限)和REPLICATION CLIENT(复制客户端权限)
mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)
mysql> #刷新权限
mysql> quit
Bye
d. 备份3上的所有库并将备份文件发送给2
[root@3 ~]# mysqldump -u root -p --single-transaction --routines --events --triggers -B school > backup-school.sql
[root@3 ~]# scp backup-school.sql root@192.168.100.100:/root
e. 查看主库状态
[root@3 ~]# mysql -u root -p
mysql> show binary log status \G
*************************** 1. row ***************************
File: binlog.000006
Position: 325
Binlog_Do_DB: school
Binlog_Ignore_DB: information_schema,mysql,performance_schema,sys
Executed_Gtid_Set: 06072465-7531-11f1-95a0-000c294f01ee:1
1 row in set (0.00 sec)
2. 配置2作为slave¶
a. 修改配置文件开启中继日志
[root@2 ~]# vi /etc/my.cnf
[client]
socket = /usr/local/mysql/mysql.sock #客户连接使用的socket
[mysqld]
server_id=20 #将这台 MySQL 服务器的 ID 指定为20,与master应当设置为不同的ID
port = 3306 #默认监听的端口为3306
user = mysql #mysql进程的维护用户为mysql
character-set-server = utf8mb4 #默认字符集
default_storage_engine = innodb #默认存储引擎
log_timestamps = SYSTEM #日志时间戳使用系统时间
socket = /usr/local/mysql/mysql.sock #服务端的socket文件存放的位置
basedir = /usr/local/mysql #mysql的安装目录
datadir = /data/mysql/data #mysql数据文件的存放目录
pid-file = /usr/local/mysql/mysql.pid #mysql进程id存放位置
log_error = /data/mysql/log/mysql-error.log #mysql错误日志的存放位置
sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
#STRICT_TRANS_TABLES 严格模式核心 — 对事务表启用严格模式,插入/更新数据时若值无效(如类型错误、超出长度),直接报错而非发出警告
#NO_ZERO_IN_DATE 不允许月份或日为零的日期,例如 '2010-00-01'、'2024-01-00' 会被拦截。现已被废弃,但不会报错
# 不允许 '0000-00-00' 作为合法日期。现已被废弃,但不会报错
#ERROR_FOR_DIVISION_BY_ZERO 除零操作直接报错,而非返回 NULL 并给警告
#NO_ENGINE_SUBSTITUTION 建表时如果指定了被禁用的存储引擎(如 Memory 被禁用),直接报错,而非自动替换为默认引擎(InnoDB)
skip-name-resolve #跳过DNS反向解析
log_bin = /data/mysql/log/binlog #开启二进制文件
binlog_format = ROW #以行格式记录二进制的内容
slow_query_log = ON #开启慢查询日志
slow_query_log-file = /data/mysql/log/slow.log
#记录慢查询日志的select语句
long_query_time = 1 #查询超时时间
log_queries_not_using_indexes = ON #查询语句没有使用后索引也会记录到慢查询日志文件中
gtid_mode=ON #开启GTID模式
enforce-gtid-consistency=ON #强制GTID一致性,开启后,违反 GTID 一致性的语句会被直接拒绝执行,确保复制不会出错。
log_slave_updates=ON #记录从库上执行的中继日志事务到从库自己的 binlog。
binlog_expire_logs_seconds=604800 #二进制日志binlog的自动过期时间设置为604800 秒
sync_binlog=1 #每次事务提交都强制将 binlog 刷入磁盘。
binlog_cache_size=4M #在事务期间用于缓存 binlog 写入的内存缓冲区大小。
max_binlog_cache_size=2G #单个事务的binlog缓存上限为2G
innodb_buffer_pool_size=10G #innodb缓冲池大小设置为10GB
innodb_log_file_size=1G #单个 InnoDB redo log(重做日志) 文件的大小,设为 1GB
innodb_log_buffer_size=64M #InnoDB redo log 的缓冲区大小,设为 64MB。
innodb_flush_log_at_trx_commit=1 #每次事务提交时,强制将 redo log 刷入磁盘。
innodb_file_per_table=1 #每个 InnoDB 表使用独立的表空间文件(表名.ibd),而不是全部存到共享的 ibdata1 中。
max_connections=5000 #MySQL 允许的最大并发连接数为 5000。
max_allowed_packet=64M #客户端与服务器之间传输的单个数据包最大为 64MB。
thread_cache_size=64 #MySQL 缓存待复用的线程数量为 64 个。
relay_log=/data/mysql/log/relay-log #指定中继日志(relay log) 的存放路径和文件名前缀。
relay_log_index=/data/mysql/log/relay-log.index
#中继日志索引文件路径
relay_log_recovery=ON #从库崩溃重启后,自动丢弃未执行完的中继日志,重新从主库获取,保证数据一致性。
relay_log_purge=ON #自动清理已执行完的旧中继日志文件,避免磁盘空间被撑满。
slave_parallel_type=LOGICAL_CLOCK #基于逻辑时钟的并行回放模式。允许同一时间点上没有冲突的事务并行回放,大幅提升从库性能
slave_parallel_workers=4 #并行复制的工作线程数 = 4。SQL 线程拆成 1 个协调线程 + 4 个工作线程并发回放事务。
slave_preserve_commit_order=ON #保证并行回放的事务提交顺序与主库一致,防止因提交乱序导致的数据不一致或主从不一致。
slave_checkpoint_period=2 #检查点周期 2 毫秒,控制并行复制协调线程检查工作线程进度的频率。
slave_checkpoint_group=512 #每处理 512 个事务做一次检查点,限制协调线程的批量大小。
slave_net_timeout=30 #设置从库与主库之间网络超时 30 秒,超过这个时间没收到数据就报错重连。
slave_compressed_protocol=1 #主从传输时启用压缩,减少网络带宽占用(CPU 换带宽)。
replicate-do-db=school #只复制 school 数据库的变更,其他库一律忽略。
replicate-ignore-db=information_schema #忽略 information_schema 库的复制。
replicate-ignore-db=mysql #忽略 mysql 库(用户权限表)的复制
replicate-ignore-db=performance_schema #忽略 performance_schema 库。
replicate-ignore-db=sys #忽略 sys 库。
b. 重启mysqld服务,然后将master备份内容导入到slave库中
[root@2 ~]# systemctl restart mysqld
[root@2 ~]# ss -tuanp |grep mysql
tcp LISTEN 0 70 *:33060 *:* users:(("mysqld",pid=12373,fd=18))
tcp LISTEN 0 4096 *:3306 *:* users:(("mysqld",pid=12373,fd=21))
[root@2 ~]# mysql -uroot -p<backup-school.sql
c. 查看school数据库是否导入成功
[root@2 ~]# mysql -uroot -p -e 'show databases;'
Enter password:
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| school |
| sys |
+--------------------+
[root@2 ~]# mysql -uroot -p -e 'show databases;'
Enter password:
d. 在从服务器上设置主从连接的相关信息
mysql> CHANGE REPLICATION SOURCE TO
-> SOURCE_HOST='192.168.100.101',
-> SOURCE_USER='repl_user',
-> SOURCE_PASSWORD='Abcd1234',
-> SOURCE_PORT=3306,
-> SOURCE_LOG_FILE='binlog.000006',
-> SOURCE_LOG_POS=325,
-> #也可以使用自动定位,语法是 MASTER_AUTO_POSITION=1,
-> GET_SOURCE_PUBLIC_KEY=1;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
mysql> START REPLICA;
mysql> #开启主从复制 停止主从复制的命令为stop replica;
Query OK, 0 rows affected (0.05 sec)
e. 查看主从复制状态
mysql> SHOW REPLICA STATUS\G
*************************** 1. row ***************************
Replica_IO_State: Waiting for source to send event
Source_Host: 192.168.100.101
Source_User: repl_user
Source_Port: 3306
Connect_Retry: 60
Source_Log_File: binlog.000006
Read_Source_Log_Pos: 325
Relay_Log_File: relay-log.000002
Relay_Log_Pos: 325
Relay_Source_Log_File: binlog.000006
Replica_IO_Running: Yes
Replica_SQL_Running: Yes
Replicate_Do_DB: school
Replicate_Ignore_DB: information_schema,mysql,performance_schema,sys
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Source_Log_Pos: 325
Relay_Log_Space: 530
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Source_SSL_Allowed: No
Source_SSL_CA_File:
Source_SSL_CA_Path:
Source_SSL_Cert:
Source_SSL_Cipher:
Source_SSL_Key:
Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Source_Server_Id: 10
Source_UUID: 06072465-7531-11f1-95a0-000c294f01ee
Source_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
Source_Retry_Count: 10
Source_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Source_SSL_Crl:
Source_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set: 21c6b6f7-7686-11f1-afec-000c29105f32:1-11
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Source_TLS_Version:
Source_public_key_path:
Get_Source_public_key: 1
Network_Namespace:
1 row in set (0.00 sec)
f. 如果实现锁过master表,则取消锁表
锁表命令为:LOCK TABLES school.students READ;
取消锁表命令为:UNLOCK TABLES;
四. 查看2及3的线程信息¶
1. 对于3¶
mysql> show processlist \G
*************************** 1. row ***************************
Id: 5
User: event_scheduler
Host: localhost
db: NULL
Command: Daemon
Time: 3329
State: Waiting on empty queue
Info: NULL
*************************** 2. row ***************************
Id: 9
User: repl_user
Host: 192.168.100.100:57934
db: NULL
Command: Binlog Dump
Time: 416
State: Source has sent all binlog to replica; waiting for more updates
Info: NULL
*************************** 3. row ***************************
Id: 11
User: root
Host: localhost
db: NULL
Command: Query
Time: 0
State: init
Info: show processlist
3 rows in set, 1 warning (0.00 sec)
2. 2上的¶
mysql> SHOW PROCESSLIST \G
*************************** 1. row ***************************
Id: 5
User: event_scheduler
Host: localhost
db: NULL
Command: Daemon
Time: 1973
State: Waiting on empty queue
Info: NULL
*************************** 2. row ***************************
Id: 13
User: root
Host: localhost
db: NULL
Command: Query
Time: 0
State: init
Info: SHOW PROCESSLIST
*************************** 3. row ***************************
Id: 14
User: system user
Host: connecting host
db: NULL
Command: Connect
Time: 536
State: Waiting for source to send event
Info: NULL
*************************** 4. row ***************************
Id: 15
User: system user
Host:
db: NULL
Command: Query
Time: 536
State: Replica has read all relay log; waiting for more updates
Info: NULL
*************************** 5. row ***************************
Id: 16
User: system user
Host:
db: NULL
Command: Connect
Time: 536
State: Waiting for an event from Coordinator
Info: NULL
*************************** 6. row ***************************
Id: 17
User: system user
Host:
db: NULL
Command: Connect
Time: 536
State: Waiting for an event from Coordinator
Info: NULL
*************************** 7. row ***************************
Id: 18
User: system user
Host:
db: NULL
Command: Connect
Time: 536
State: Waiting for an event from Coordinator
Info: NULL
*************************** 8. row ***************************
Id: 19
User: system user
Host:
db: NULL
Command: Connect
Time: 536
State: Waiting for an event from Coordinator
Info: NULL
8 rows in set, 1 warning (0.00 sec)
五. 验证主从复制¶
1. 在3上建库建表并插入数据¶
mysql> use school;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> INSERT INTO A VALUES('bob','工业大学');
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO B VALUES('bob','嵌入式');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM A;
+-----------+--------------+
| 姓名 | 毕业院校 |
+-----------+--------------+
| 杨过 | 哈工大 |
| 郭靖 | 北大 |
| 黄蓉 | 武大 |
| 风清扬 | 西交大 |
| bob | 工业大学 |
+-----------+--------------+
5 rows in set (0.00 sec)
mysql> SELECT * FROM B;
+--------+-----------+
| 姓名 | 职业 |
+--------+-----------+
| 杨过 | 作家 |
| 杨逍 | 演员 |
| 孙杨 | 运动员 |
| 黄蓉 | 考研 |
| bob | 嵌入式 |
+--------+-----------+
5 rows in set (0.00 sec)
2. 回到2查看验证¶
mysql> USE school;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> SELECT * FROM A INNER JOIN B ON A.`姓名`=B.`姓名`;
+--------+--------------+--------+-----------+
| 姓名 | 毕业院校 | 姓名 | 职业 |
+--------+--------------+--------+-----------+
| 杨过 | 哈工大 | 杨过 | 作家 |
| 黄蓉 | 武大 | 黄蓉 | 考研 |
| bob | 工业大学 | bob | 嵌入式 |
+--------+--------------+--------+-----------+
3 rows in set (0.00 sec)