MySQL主从复制就是将一个MySQL实例(Master)中的数据实时复制到另一个MySQL实例(slave)中,而且这个复制是一个异步复制的过程。
实现整个复制操作主要由三个进程完成的,其中两个进程在Slave(sql_thread和IO_thread),另外一个进程在 Master(IO进程)上。
要实施复制,首先必须打开Master端的binary log(bin-log)功能,否则无法实现。因为整个复制过程实际上就是Slave从Master端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。
复制的基本过程如下:
1)、Slave上面的IO_thread连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
2)、Master接收到来自Slave的IO_thread的请求后,通过负责复制的IO进程根据请求信息读取制定日志指定位置之后的日志信息,返回给Slave 的IO_thread。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log file的以及bin-log pos;
3)、Slave的IO_thread接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的告诉Master“我需要从某个bin-log的哪 个位置开始往后的日志内容,请发给我”;
4)、Slave的Sql_thread检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在本数据库中执行。
MySQL主从复制搭建主要步骤有:Master端配置部署、Slave端配置部署、建立主从同步
a、 配置参数:
server-id=101 # 这个要保证一个主从复制环境中,不要有相同的server-id log-bin=/data/mysql6001/binlog/mysql-bin.log log-bin-index=/data/mysql6001/binlog/mysql-bin.index expire_logs_days=30
b、 创建用户,并赋予权限:
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' IDENTIFIED BY PASSWORD '******';
a、 配置参数:
[mysqld] server-id=102 relay-log=/data/mysql6001/relaylog/mysql-relay-bin.log relay-log-index=/data/mysql6001/relaylog/mysql-relay-bin.index relay_log_purge=on
建立主从同步可以从主库上导出数据,也可以从已有的从库上导出数据,然后再导入到新的从库中,change master to建立同步。
在主库上导出数据:
mysqldump -u*** -p*** -S /data/mysql6001/mysql.sock --default-character-set=utf8 -q --single-transaction --master-data -A > /tmp/all_database.sql
(或者)在从库上导出数据:
mysqldump -u*** -p*** -S /data/mysql6001/mysql.sock --default-character-set=utf8 -q --single-transaction --dump-slave -A > /tmp/all_database.sql
NOTES:
--master-data和--dump-slave导出的备份中,会包含master_log_file和master_log_pos信息。
mysql -u*** -p*** --default-character-set=utf8 < all_database.sql
NOTES:
此处导入脚本,就已经在从库中执行了以下操作:
change_master_to
master_log_file=' mysql-bin.000xxx',
master_log_pos=xxxxxx;
以下为建立主从同步最基本的6个项:
change master to
master_host='xxx.xxx.xxx.xxx', # 主库IP
master_port=6001, # 主库mysqld的端口
master_user='repl', # 主库中创建的有REPLICATION SLAVE 权限的用户
master_password='xxxxxxxx', # 该用户的密码
master_log_file=' mysql-bin.000xxx', # 已在导入时指定了
master_log_pos=xxxxxx; #已在导入时指定了
指定与主库同步的基本信息后,就可以启动slave进程了:(IO_thread和sql_thread)
start slave;
主库查看binlog情况:
show master status\G
*************************** 1. row ***************************
File: mysql-bin.000303
Position: 18711563
Binlog_Do_DB:
Binlog_Ignore_DB:
在从库上主要是使用以下命令查看从库与主库的同步状态:
show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.43.128 #主库IP
Master_User: repl #主库复制的用户
Master_Port: 6001 #主库mysqld端口
Connect_Retry: 60
Master_Log_File: mysql-bin.000303 #io_thread读取主库master_log_file
Read_Master_Log_Pos: 18711563 # io_thread读取主库master_log_pos
Relay_Log_File: mysql-relay-bin.000900
Relay_Log_Pos: 18711709
Relay_Master_Log_File: mysql-bin.000303 #sql_thread执行主库的master_log_file
Slave_IO_Running: Yes #关键的,io_thread是否running
Slave_SQL_Running: Yes #关键的,sql_thread是否running
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: 18711563 #sql_thread执行主库的master_log_pos
Relay_Log_Space: 18711908
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: 101
1 row in set (0.00 sec)
IO_thread异常,状态往往是Slave_IO_Running: Connecting 或NO。
IO_thread是向Master发送请求读取master binlog,如果处于Connecting状态,说明无法正确地与Master进行连接,可能的原因有:
a、网络不通(是否打开防火墙)
b、复制用户的密码不对
c、指定的master_port端口不对
d、master上的mysql-bin.xxxxxx被误删
e、主库磁盘空间满了
通过show slave status\G可以看到相关错误信息,例如:
show slave status\G
Last_IO_Errno: 2003
Last_IO_Error: error connecting to master '[email protected]:3306' - retry-time: 60
retries: 86400
或者通过错误日志看到相关信息,如:
140828 15:47:20 [ERROR] Slave I/O: error connecting to master '[email protected]:3306' -
retry-time: 60 retries: 86400, Error_code: 2003
140828 15:47:21 [Note] Event Scheduler: Loaded 0 events
140828 15:47:21 [Note] /home/mysql/mysql/bin/mysqld: ready for connections.
sql_thread发生异常,状态就会变为Slave_SQL_Running: NO。
sql_thread发生异常的情况非常多,发生异常后,需要通过以下方法排查和解决:
a、对比主库和从库的二进制日志的情况:
主库:
show master status\G
File: mysql-bin.000303
Position: 18711563
从库:
show slave status\G
Master_Log_File: mysql-bin.000303 --IO_thread
Read_Master_Log_Pos: 18711563 --IO_thread
Relay_Master_Log_File: mysql-bin.000303 --sql_thread
Exec_Master_Log_Pos: 18711163 --sql_thread
b、通过show slave status\G查看错误信息:
show slave status\G
Last_SQL_Errno: 1062
Last_SQL_Error: Error 'Duplicate entry '1' for key 'PRIMARY'' on query. Default database:
'test'. Query: 'insert into test values(1,2,3,4,5,6)'
c、 通过错误日志查看错误信息:
140828 16:27:51 [ERROR] Slave SQL: Error 'Duplicate entry '1' for key 'PRIMARY'' on query.
Default database: 'test'. Query: 'insert into test values(1,2,3,4,5,6)',
Error_code: 1062
140828 16:27:51 [Warning] Slave: Duplicate entry '1' for key 'PRIMARY' Error_code: 1062
140828 16:27:51 [ERROR] Error running query, slave SQL thread aborted. Fix the problem,
and restart the slave SQL thread with "SLAVE START". We stopped at log
'mysql-bin.000303' position 18711163
根据这些报错信息,往往就能够定位到发生异常的原因。如果我们了解产生异常的具体事件,而且能够掌控,可以通过设置sql_slave_skip_counter参数来跳过当前错误。
set global sql_slave_skip_counter=1;
或者使用slave_skip_errors参数(read only variable),指定跳过某种类型的错误:
参数文件中设置:
slave_skip_errors=1062 #跳过1062错误
遇到错误时,不要一通百度后,然后根据看起来很类似的操作直接来进行操作。因为网上大部分解决sql_thread异常的方法是:
a、直接set global sql_slave_skip_counter=n; (n设置很大的值,即:跳过所有错误),
b、设置slave_skip_errors=all; 跳过所有类型的错误
c、直接查看主库的binlog,然后在从库上直接执行change master to。
这些方法都会导致主从数据不一致。
如果发现从库与主库差异太大,无法通过手动操作或数据修改重新建立同步。可以参考上述"MySQL主从复制搭建" 重新搭建从库。
主从复制延迟,可能的原因有:
a、主从同步延迟与系统时间的关系,查看主从两台机器间系统时间差
b、主从同步延迟与压力、网络、机器性能的关系,查看从库的io,cpu,mem及网络压力
c、主从同步延迟与lock锁的关系(myisam表读时会堵塞写),尽量避免使用myisam表。一个实例里面尽量减少数据库的数量。
d、主从复制发生异常而中断,过很久之后才发现复制异常。可通过查看master与slave的status估算相差的日志。如果相差太大,则可以考虑重做从库。
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.33
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000010
Read_Master_Log_Pos: 93731745
Relay_Log_File: mysql-relay-bin.000008
Relay_Log_Pos: 5979038
Relay_Master_Log_File: mysql-bin.000010
Slave_IO_Running: Yes
Slave_SQL_Running: No
Replicate_Do_DB: maopaodb
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 1146
Last_Error: Error 'Table 'maopaodb.test' doesn't exist' on query. Default database: 'maopaodb'. Query: 'insert into test values (1,'a')'
Skip_Counter: 0
Exec_Master_Log_Pos: 93709288
Relay_Log_Space: 6001650
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: 1146
Last_SQL_Error: Error 'Table 'maopaodb.test' doesn't exist' on query. Default database: 'maopaodb'. Query: 'insert into test values (1,'a')'
1 row in set (0.00 sec)
对于这种问题复制同步方面的问题,主要都是要在从库这边手动做一些动作,要么补充一些操作,要么跳过一些操作。我们看看如何跳过这个操作。
首先我们要从上边的报错信息关注到以下三点:
Relay_Master_Log_File: mysql-bin.000010
Skip_Counter: 0
Exec_Master_Log_Pos: 93709288
就是我们要明白是主库的哪个日志文件,和日志文件中报错的具体位置,而Skip_Counter参数就是我们要利用的东西。这里我们看到是mysql-bin.000010这个日志文件的93709288位置报错,那么就到主库看看这个位置到底是在执行什么东西:
mysql> SHOW BINLOG EVENTS in 'mysql-bin.000010' from 93709288;
+------------------+----------+------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+------------------+----------+------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------+
| mysql-bin.000010 | 93709288 | Query | 1 | 93709360 | BEGIN |
| mysql-bin.000010 | 93709360 | Query | 1 | 93709458 | use `maopaodb`; insert into test values (1,'a') |
| mysql-bin.000010 | 93709458 | Xid | 1 | 93709485 | COMMIT /* xid=28862936 */ |
| mysql-bin.000010 | 93709485 | Query | 1 | 93709557 | BEGIN |
| mysql-bin.000010 | 93709557 | Query | 1 | 93709723 | use `mogilefs`; UPDATE device SET mb_total = '9564', mb_used = '386', mb_asof = UNIX_TIMESTAMP() WHERE devid = '13' |
| mysql-bin.000010 | 93709723 | Xid | 1 | 93709750 | COMMIT /* xid=28863050 */
......
可见正是导致出错的这个insert语句,MySQL的binary log每个SQL执行实际上是由一些event组成的,我们这里要设置的就是要跳过的event的个数。本例中就是BEGIN、具体SQL语句和COMMIT这3个event,因此我们可以在从库上这样跳过这个语句:
mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 3;
Query OK, 0 rows affected (0.00 sec)
然后开启同步,一切正常:
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)
mysql> show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.33
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000010
Read_Master_Log_Pos: 93733865
Relay_Log_File: mysql-relay-bin.000008
Relay_Log_Pos: 6003615
Relay_Master_Log_File: mysql-bin.000010
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB: maopaodb
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: 93733865
Relay_Log_Space: 6003770
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:
1 row in set (0.00 sec)