主从复制原理
MySQL利用binlog实现主从复制,大致流程如下:
- 主节点每一次执行SQL都会写binlog日志
- 从节点IO线程与主节点建立起长连接,不断的从主节点读取binlog
- IO线程将读到的binlog写到replay log中
- SQL线程读取replay log并执行
示意图如下:
配置主从复制
主节点设置
1.主节点开启binlog日志
修改配置文件my.cnf:
[mysqld]
log-bin=mysql-bin
server-id=1
为了方便搭建环境,我使用docker演示:
docker run -d \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
--name=master-mysql \
mysql:5.6 \
--log-bin=mysql-bin \
--server-id=1
在命令末尾使用--
加参数,直接传入要修改的配置,而不用修改配置文件。参数列表可以使用以下命令查看:
docker run -it --rm mysql:5.6 --verbose --help
2.创建一个专门用于复制的账号
进入mysql容器并执行:
docker exec -it master-mysql bash
mysql -uroot -p123456
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' identified by '123456';
迁移已有数据
我们需要将主库已有数据导出并迁移到从库中。
为了演示,先在主库中创建数据库,建表,并插入一条数据:
mysql> create database mydb;
mysql> use mydb;
mysql> create table t(id int, name varchar(32));
mysql> insert into t values(1, 'zhangsan');
mysql> select * from t;
+------+----------+
| id | name |
+------+----------+
| 1 | zhangsan |
+------+----------+
导出数据:
mysqldump -uroot -p123456 --all-databases --master-data=1 > dump.db
将dump.db文件拷贝到从节点
查询主节点当前binlog及位置点:
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000004 | 743 | | | |
+------------------+----------+--------------+------------------+-------------------+
记住binlog文件名mysql-bin.000004
以及position的值743
,从节点复制数据时需要从这个位置点开始复制。
从节点设置
从节点配置my.cnf:
[mysqld]
server-id=2
此处server-id一定要和主节点不一样。
为了方便演示,使用docker启动从节点,并且端口设为3307:
docker run -d \
--name=slave-mysql \
-p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:5.6 \
--server-id=2
导入从主节点搬来的dump.db文件。我的演示环境里,两个mysql部署在同一台机器上,dump.db文件要先从主mysql容器中搬出来,再拷贝到主mysql容器中:
docker cp master-mysql:/dump.db .
docker cp ./dump.db slave-mysql:/
导入数据:
mysql -uroot -p123456
mysql> source /dump.db;
配置master并开始复制:
mysql> change master to master_host='192.168.0.100',master_user='repl',master_password='123456',master_log_file='mysql-bin.000004',master_log_pos=743;
mysql> start slave;
注意:命令中的参数要根据你的实际情况修改。
查询主从复制情况:
mysql> show slave status \G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.100
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000004
Read_Master_Log_Pos: 743
Relay_Log_File: mysqld-relay-bin.000002
Relay_Log_Pos: 283
Relay_Master_Log_File: mysql-bin.000004
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: 743
Relay_Log_Space: 457
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: 1
Master_UUID: 96d15f65-9477-11eb-b5a6-0242ac110002
Master_Info_File: /var/lib/mysql/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:
Auto_Position: 0
从查询结果里可以看到Slave_IO_Running和Slave_SQL_Running的值都是Yes,说明主从复制成功了。可以在主库修改数据,在从库验证。
Binlog格式
binlog有statement,raw两种格式。还有一种mixed,mixed是前两种格式的混合
binlog格式使用binlog-format
配置项设置
statement格式
statement格式的binlog,就是直接记录了执行的SQL语句,这种方式有极大的风险,同一条SQL,在主库执行的结果与在从库执行的结果未必一致。
我们可以使用mysqlbinlog工具查看binlog文件的内容。
基于上面配置的数据库,我们进行个小实验。
首先使用show master logs
查看当前binlog位置:
mysql> show master logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000001 | 69432 |
| mysql-bin.000002 | 1640526 |
| mysql-bin.000003 | 7021288 |
| mysql-bin.000004 | 1584 |
+------------------+-----------+
记住最后一行:mysql-bin.000004
以及1584
利用上面例子中建立的mydb库以及t表,先新增一条记录:
mysql> insert into t values(100, 'abc');
Query OK, 1 row affected (0.01 sec)
再将其删除:
mysql> delete from t where id=100;
Query OK, 1 row affected (0.01 sec)
使用mysqlbinlog工具查看binlog文件,默认目录在:/var/lib/mysql
:
cd /var/lib/mysql
mysqlbinlog mysql-bin.000004 --start-position=1584
参数解析:
- –start-position:从binlog指定的位置开始,设置为上面查出来的值1584
从执行结果可以清楚看到刚刚执行的SQL语句:
除了SQL外,还能看到binlog中有SET TIMESTAMP=1618024381/*!*/;
,这是为了记录执行这条SQL的时间。
有什么用呢?比如SQL中使用到了NOW()
函数,那么即使主从延迟了,从库也会使用binlog中的TIMESTAMP作为当前时间来执行SQL,避免主从数据不一致。
但可能导致主从不一致的情况不仅这一种,比如同一条SQL,在主库和从库执行时,优化器选择了不同的索引,那么就可能导致执行结果不一致。
因此statement格式的binlog是不靠谱的
row格式
row格式记录的是被更新的数据:
- 如果是insert操作,则直接记录插入的数据
- 如果是update操作,记录更新前和更新后的数据
- 如果是delete操作,记录被删除的数据
为了演示,先将binlog临时修改为row:
mysql> set session binlog_format=row;
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like 'binlog_format';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW |
+---------------+-------+
分别写入insert,update,delete三条语句:
mysql> insert into t values(10, 'javajun.net');
Query OK, 1 row affected (0.01 sec)
mysql> update t set name='www.javajun.net' where id=10;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> delete from t where name='www.javajun.net';
Query OK, 1 row affected (0.00 sec)
注意,delete语句时根据name来删除
首先看insert语句执行的binlog,这次需要加上-vv
参数,可以看到很多细节
mysqlbinlog -vv mysql-bin.000004
执行结果:
上图中,Table_map说明了当前要操作的库表,第二个红框中@1和@2分别指定了表的第一个字段和第二个字段是什么值
再看update语句的结果:
可以看到,更新前和更新后的数据都被记录下来了
delete语句的执行结果:
delete语句的binlog记录了要删除的数据,而不是根据你的where条件来删除,也保证了主从的一致性。
mixed格式
row格式能够保证主从数据的一致,但是生成的binlog比较占空间。比如一条批量更新的SQL,如果是statement格式,只要记一条SQL就好了,row格式则需要记录所有被更新的数据。
所以后来有了mixed格式,mysql会自动判断SQL是否可能产生主从不一致的情况,如果会,则用row记录,不会则用statement记录。
推荐使用row或者mixed