原文链接:《MySQL Replication 配置与原理》http://www.ytbean.com/posts/mysql-replication/

总体架构

内部架构

image-20220310175129529

连接管理

MySQL服务端采用线程池维护客户端连接

SQL解析

分析查询语句,生成解析树,并将解析结果放入缓存中

优化器

优化包括选择合适的索引,数据的读取方式,分析语句执行的开销以及统计信息,优化器可以和存储引擎直接交互,尽管看起来似乎不必要。

执行

执行查询语句,返回结果

缓存

缓存SQL解析器解析后的结果

存储引擎

  1. 粒度

    • 表级锁(table lock) : 资源消耗最少,但并发度低
    • 行级锁(row lock) : 资源消耗较多,但并发度高
  2. 死锁
    死锁的简单例子:两个transaction同时开始执行,它们分别开始执行第一条update的语句时,便锁住了对方的资源,你拿着我的资源不放,我拿着你的资源不放,最后二人都僵持着,当事人事不关己高高挂起,旁观者疾呼:死锁!

    # transaction 1
    START TRANSACTION;
    UPDATE USER SET NAME='Bob' WHERE ID=1;
    # sleep for some time;
    UPDATE USER SET NAME='Jack' WHERE ID=2;
    COMMIT;
    # transaction 2
    START TRANSACTION;
    UPDATE USER SET NAME='Bob' WHERE ID=2;
    # sleep for some time
    UPDATE USER SET NAME='Jack' WHERE ID=1;
    COMMIT;
  3. 显示锁定和隐式锁定
    隐式锁定就是系统自动加锁而不是人为的添加锁,显示锁定就是人为的添加锁,比如lock tables或者unlock tables。
事务

事务的特点由存储引擎决定,是MySQL与其它数据库的不同。
不支持事务的存储引擎有:

  • MYISAM
  • MEMORY
  • ARCHIEVE

MySQL中的表按是否支持事务,分为:事务型表和非事务型表。
非事务型表没有commit或者rollback的概念。

SET AUTOCOMMIT=OFF;

或者

SET AUTOCOMMIT=0;

能设置当前连接是否是自动提交的。不过对非事务型表没有作用。

  1. ACID属性

    • 原子性:事务是不可分割的最小工作单元,整个事务要么全部提交要么全部回滚失败。
    • 一致性:数据库总是从一个一致性状态转换到另一个一致性的状态。
    • 隔离性: 一个事务所做的更改在最终提交之前其它事务是不可见的。
    • 持久性:事务一旦提交所做的修改就会永久保存在数据库中,即使系统崩溃,数据也不会丢失。
  2. 隔离级别

    • 未提交读(READ UNCOMMITTED):未提交读隔离级别也叫读脏,就是事务可以读取其它事务未提交的数据。
    • 提交读(READ COMMITTED):在其它数据库系统比如SQL Server默认的隔离级别就是提交读,已提交读隔离级别就是在事务未提交之前所做的修改其它事务是不可见的。
    • 可重复读(REPEATABLE READ):保证同一个事务中的多次相同的查询的结果是一致的,比如一个事务一开始查询了一条记录然后过了几秒钟又执行了相同的查询,保证两次查询的结果是相同的,可重复读也是mysql的默认隔离级别。
    • 可串行化(SERIALIZABLE):可串行化就是保证读取的范围内没有新的数据插入,比如事务第一次查询得到某个范围的数据,第二次查询也同样得到了相同范围的数据,中间没有新的数据插入到该范围中。
      查询和设置隔离级别:

       # 查询系统默认隔离级别,当前会话隔离级别
       select @@global.tx_isolation,@@tx_isolation;
       # 设置系统隔离级别:
       SET global transaction isolation level read committed;
       # 设置会话隔离级别:
       SET SESSION transaction isolation LEVEL read committed;

Replication概念

复制意味着一份数据可以有多个副本,一个数据库中的数据,可以复制至另一个数据库。

复制在我们的生活中无处不在,同一份数据,有可能在你个人的电脑上有一份,U盘上有一份,云端的网盘上也可能会有一份。甚至在个人电脑里,同样一份数据也会有多个副本。
这里,我们之所以会将数据复制出多份,目的是显而易见的:备份
当你抱着自己的电脑准备接上投影仪,准备向老板展示你苦战数个通宵后的PPT时,硬盘突然坏了,或者文件误删,莫名其妙的找不到了,没关系,你的U盘还有一份。什么!U盘忘了带了?没关系,你的网盘里还有一个。
复制备份的是两件不同的事情,可以通过复制来实现备份的目的。但是,复制的却不只是能提供备份而已。

复制解决了什么问题

在MySQL中,复制可以解决几个问题:

  • 数据分布
  • 读写分离
  • 备份
  • 高可用和故障切换

如果你们公司的数据中心位于全国各地,通过复制,可以实现异地备份,但异地的数据同步会有较大的时间延迟。
同时,如果主数据库的数据都同步复制至从库,那么当需要更新数据时,只需要更新主库即可,新更新的数据将会通过主库同步至从库,在这个基础上,便可以实现读写分离,即DML语句在主库上执行,而查询类SQL语句则在从库上执行,由于主从的同步有时延,因此这里的数据一致性模型并不满足强一致性,是最终一致性模型。
因为有了主从副本,所以当主库不可用(宕机,崩溃等原因),从库可以临危受命,升级为主库,保证数据库服务的高可用性。

MySQL间如何复制

分为3个步骤

  1. 在主库上把数据更改记录到二进制(binary log)日志中。
  2. 从库把主库上的日志复制到自己的中继日志(relay log)中。
  3. 从库读取中继日志中的事件,将其重放在从库数据中。

image-20220310175315702

主库在每次事务准备提交前,按照事务的提交顺序,将更新事件记录到binary log中,并通知存储引擎提交事务。然后,从库会主动启动一个线程与主库建立连接,与此对应,主库会启动一个二进制转储(binlog dump)线程与之合作,将binary log发送给从库,从库接收并产生relay log,然后由从库的一个SQL线程负责将relay log还原为数据。

需要注意的是,从库在relay时,是单线程执行,换言之,串行执行的。

Replication配置

复制的配置步骤

  1. 创建复制账号
  2. 配置主库和从库
  3. 通知从库开始进行复制

    基于二进制日志的主从复制

    配置master

    通过在my.cnf中添加一个配置项log-bin,来起用master的二进制日志记录功能,如果没有配置log-bin,那么master的二进制记录功能并不会起用,log-bin在这里有两个作用

  4. 起用master的日志记录功能
  5. 指定日志文件名的前缀

另外,需要在my.cnf中添加一个server-id配置项,用来指定master的唯一ID,范围可以是1到2^32-1。

[mysqld]
# Replication Configurations(by beanlam)
server-id=1
log-bin=bin-log

配置完成后需要重启mysql server。

配置slave

同样,slave也需要配置一个唯一的server-id,不允许跟master的server-id冲突,如果有多个slave,各个slave与master的server-id都应该是不同的。
slave也可以配置log-bin,配置了以后slave也可以和master一样,记录binary log。slave记录binary log有其用武之地的,比如数据备份和崩溃恢复,若当前的replication环境拓扑结构比较复杂,slave需要作为其它mysql server的master时,那么这个slave也必须启用binary log的功能。

[mysqld]
# Replication Configurations(by beanlam)
server-id=2

创建一个账号用于复制

slave获取master的binary log时,通过用户名和密码与master建立连接,可以创建一个专用于复制的账号,并只赋予这个账号与复制有关的权限。

mysql> CREATE USER 'repl'@'%.mydomain.com' IDENTIFIED BY 'slavepass';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%.mydomain.com';

除了REPLICATION SLAVE权限,还可以给用户授权REPLICATION CLIENT权限,这样用户就可以用来监控和管理复制。

获取master二进制文件的坐标

slave必须事先了解从master的二进制文件的哪个位置开始复制,因此需要先记录master当前二进制日志的坐标,坐标由文件名和偏移量决定。
获得master的二进制日志坐标,需要先保证没有写操作在进行。在master上执行以下语句:

mysql> FLUSH TABLES WITH READ LOCK;

这个语句能为表获得读锁,阻止其它写入操作。需要注意,执行这个语句的会话如果关闭,那么这个锁将会被释放,如果会话没有关闭,那么锁会一直持有。

在另一个会话里,用以下语句来查看master的二进制日志坐标

mysql> show master status;
+----------------+----------+-------------------------+------------------+-------------------+
| File           | Position | Binlog_Do_DB            | Binlog_Ignore_DB | Executed_Gtid_Set |
+----------------+----------+-------------------------+------------------+-------------------+
| bin-log.000005 |     1264 | beanlam_db1,beanlam_db2 |                  |                   |
+----------------+----------+-------------------------+------------------+-------------------+
1 row in set (0.00 sec)

File和Position即表明了slave应该从何处开始进行复制。

如果master之前没有启用过二进制日志的功能,那么show master status查询结果将为空。这时对于slave来说,File是一个空字符串,Position是4,之所以是4,与二进制日志的文件格式有关。

slave开始复制

根据以上步骤,得到了master二进制文件的坐标后,只需要告诉slave,slave便可以埋头进入复制的状态中。

这里根据master是否有旧数据需要同步,分为两种情况:

  1. 如果master是一个新的数据库服务器,其上没有任何旧的数据需要复制,那么就可以使用change master to命令为slave配置master的信息。

    CHANGE MASTER TO
    MASTER_HOST='master_host_name',
    MASTER_USER='replication_user_name',
    MASTER_PASSWORD='replication_password',
    MASTER_LOG_FILE='recorded_log_file_name',
    MASTER_LOG_POS=recorded_log_position;

    配置完后,可以通过start slave命令正式开始进行复制。

  2. 如果master在启用二进制日志功能之前,已经存在有一部分数据,这一部分数据需要在复制开始之前,先同步至slave。
    可以通过mysqldump工具对当前master的数据库数据做一个快照,生成一个dump文件,在slave开始复制之前,把这个文件的数据导入slave中。
    基本的使用方法:

    mysqldump --all-databases --master-data > dbdump.db

    -all-databases表明为所有数据库作快照,也可以用--databases来制定需要做快照的数据库。
    --ignore-table可以跳过数据库中的所有表
    --master-data会自动地在dump文件里加上change master to语句,启动slave复制,如果不加这个选项,则需要先开启一个新的会话,对所有的表加读。
    当存储引擎是InnoDB时,推荐使用mysqldump。
    另外一种方法是直接拷贝数据文件到slave。

Replication原理

基于语句的复制

基于语句的复制也称为(逻辑复制),slave把master上造成数据更改的SQL语句在自己的库上也执行一次。

  • 优点
    master的binlog只记录SQL语句,使得日志文件体积更小。
  • 缺点

master除了传输SQL语句给slave,还需要传输一些元数据,比如当前时间戳。
还有一些语句无法被正确复制,比如包含用户自定义函数的语句,这些函数可能有不确定的行为。以下函数可能导致非正常的复制:

LOAD_FILE(), UUID(), UUID_SHORT(), USER(), FOUND_ROWS(), SYSDATE(), GET_LOCK(), IS_FREE_LOCK(), IS_USED_LOCK, MASTER_POS_WAIT, RAND(), RELEASE_LOCK(), SLEEP(), VERSION()

此外,INSERT......SELECT语句需要获取更多的行级锁,比起基于行的复制来说。
UPDATE语句可能导致全表扫描(where字句中没有包含索引字段)
如果使用的是InnoDB引擎,带有auto_incrementinsert语句会堵塞其它非冲突的insert语句。
slave上的更新是串行的,因此需要更多的锁。另外,并不是所有的存储引擎都支持基于语句的复制。

基于行的复制

MySQL5.1开始支持基于行的复制

  • 优点
    更少的锁
  • 缺点
    对于某些语句,例如插入或者删除语句,基于行的复制方式会将整行的数据都写进binary log,导致binary log体积很大,也导致需要持有锁的时间变长。
    如果包含用户自定义函数,这些函数输出值非常大的文本,那么采取行的复制,会把这么大的文本也写进日志里。
    在slave端看不到执行了哪些SQL语句
    当使用MyISAM引擎时,insert语句需要获得重量级的锁,这意味着插入操作只能是串行的。

slave也作为master

如果slave配置了log_slave_updates选项,slave也会像master一样记录binary log,从而可以作为一个master存在。

image-20220310175430233

拓扑结构

最常见的

image-20220310175524622

不支持多主库复制

image-20220310175547780

主动-被动模式下的主-主复制

被动服务器时只读的(日志里记录的事件都带有一个server id,发现server id与自己的相同,则忽略这个事件)

image-20220310175643089

拥有备库的主-主结构

image-20220310175702861

环形结构

非常脆弱,其中一个节点失效会导致由这个节点发起的事件在其它节点之间链式死循环,因为只有它自己能过滤掉与自己server id相同的事件。

image-20220310175732798

改进:

image-20220310175747714

主库-分发库-备库

主要用在当多个备库执行复制请求时,导致主库负载过高时,可以引进分发库来减少主库的负载。

image-20220310175819465

树形或金字塔形

故障处理过程更加复杂

image-20220310175855537

Replication 控制

控制Master

SHOW相关

show binary logs;/*相当于*/show master logs;
show binlog events;
show master status;
show slave hosts;

image-20220310180013888

image-20220310180025822

image-20220310180044097

binary logs清除

语法:

PURGE { BINARY | MASTER } LOGS { TO 'log_name' | BEFORE datetime_expr }

log_name或者datetime_expr之前的日志文件将会被删除,此处binary和master是同义词。
datetime_expr日期格式必须为'YYYY-MM-DD hh:mm:ss'。
例子:

PURGE BINARY LOGS TO 'mysql-bin.010';
PURGE BINARY LOGS BEFORE '2008-04-02 22:46:26';

当slave正在从master上复制时,上述的语句的执行也是安全的,如果要删除的文件正在被slave读取,那么这个文件将不会被删除。
如果删除了slave还未复制的日志文件,那么slave无法同步这部分被删除的数据。

当要清楚binary logs时,以下步骤是最佳实践:

  • 在每个slave上用show slave status查看正在同步哪个binary log。
  • 在master上执行show binary logs查看日志文件列表
  • 在列表中,找出所有slave正在同步的binary log中那个最靠前的,准备删除这个log之前的logs。
  • 为即将被删除的Logs作备份(非必须,但建议这么做)
  • 最后用purge命令清除日志。

purge命令是根据.index文件里所列的日志文件来进行删除的(.index的前缀就是binlog的名字,这个文件是由mysqld维护的),如果一个日志文件在操作系统中不存在(例如被人为地通过rm命令删除),而.index文件里又记录了这个日志文件,那么purge命令会报错。

重置Master

语法:

reset master;

这个命令会删除.index文件里所列举的所有binary logs,清空.index文件并产生一个新的空的binary log文件。

这个命令还会重置gtid_purged gtid_executed这两个系统变量为空字符串(会话范围内的变量值不会被重置),并且从MySQL5.7.5开始,这个命令还会清空mysql.gtid_executed表。

reset命令和purge命令的区别
当有slave正在进行数据同步时,不应该使用(也不支持)reset master命令,可能带来未知的后果,不过purge命令则可以在slave运行时安全地使用。

当在测试环境中,需要经常初始化设置master和slave时,使用reset master的好处多多,可以按以下步骤初始master-slave:

  • 分别启动master和slave,并启动replication
  • 在master上执行一些语句
  • 检查master的更新是否同步到slave
  • 如果slave正确同步了master的数据,在slave上执行stop slave命令,然后执行reset slave,并确认数据是否删除了数据。
  • 在master上执行reset master命令

通过以上步骤,在测试的时候,就可以清空master的binary log,并且重新开始replication的测试。

暂停replication

命令:

set sql_log_bin=0|1;

0, 不同步至slave
1, 同步至slave

可以在session内进行设置,也可以在全局范围内设置(设置后新建的session会有影响,但已经存在的session不会受影响)

控制Slave

SHOW相关

show slave status;
show relaylog events;

指向Master

语法:

CHANGE MASTER TO option [, option] ... [ channel_option ]
option:
MASTER_BIND = 'interface_name'
| MASTER_HOST = 'host_name'
| MASTER_USER = 'user_name'
| MASTER_PASSWORD = 'password'
| MASTER_PORT = port_num
| MASTER_CONNECT_RETRY = interval
| MASTER_RETRY_COUNT = count
| MASTER_DELAY = interval
| MASTER_HEARTBEAT_PERIOD = interval
| MASTER_LOG_FILE = 'master_log_name'
| MASTER_LOG_POS = master_log_pos
| MASTER_AUTO_POSITION = {0|1}
| RELAY_LOG_FILE = 'relay_log_name'
| RELAY_LOG_POS = relay_log_pos
| MASTER_SSL = {0|1}
| MASTER_SSL_CA = 'ca_file_name'
| MASTER_SSL_CAPATH = 'ca_directory_name'
| MASTER_SSL_CERT = 'cert_file_name'
| MASTER_SSL_CRL = 'crl_file_name'
| MASTER_SSL_CRLPATH = 'crl_directory_name'
| MASTER_SSL_KEY = 'key_file_name'
| MASTER_SSL_CIPHER = 'cipher_list'
| MASTER_SSL_VERIFY_SERVER_CERT = {0|1}
| MASTER_TLS_VERSION = 'protocol_list'
| IGNORE_SERVER_IDS = (server_id_list)

channel_option:
    FOR CHANNEL channel

server_id_list:
    [server_id [, server_id] ... ]

CHANGE MASTER TO用来重新设置slave,使其指向新的master,或者仅仅是改变一些上面提到的option。这个命令在MySQL5.7.4及其以上版本增加了许多特性,例如channel的概念等等。待补充。

replication过滤

CHANGE REPLICATION FILTER 语法

CHANGE REPLICATION FILTER filter[, filter][, ...]
filter:
REPLICATE_DO_DB = (db_list)
| REPLICATE_IGNORE_DB = (db_list)
| REPLICATE_DO_TABLE = (tbl_list)
| REPLICATE_IGNORE_TABLE = (tbl_list)
| REPLICATE_WILD_DO_TABLE = (wild_tbl_list)
| REPLICATE_WILD_IGNORE_TABLE = (wild_tbl_list)
| REPLICATE_REWRITE_DB = (db_pair_list)
db_list:
db_name[, db_name][, ...]
tbl_list:
db_name.table_name[, db_table_name][, ...]
wild_tbl_list:
'db_pattern.table_pattern'[, 'db_pattern.table_pattern'][, ...]
db_pair_list:
(db_pair)[, (db_pair)][, ...]
db_pair:
from_db, to_db

从MySQL5.7.3开始,CHANGE REPLICATION FILTER命令用来为slave设置一个或多个复制过滤规则,比如说`--
replicate-do-db或者 --replicate-wild-ignore-table,这些选项不像服务器选项,重置后还需要重启mysql才生效,这些选项可以动态修改,只需要先停止slave的SQL线程,设置后,再重启SQL线程(start|stop slave sql_thread`)。

各个选项的作用,待补充。

MASTER_POS_WAIT()

确切来说,这个一个函数,而非SQL语句 。

SELECT MASTER_POS_WAIT('master_log_file', master_log_pos [, timeout][, channel])

这里的file和pos对应主库show master status得到的值,代表执行位置。 函数逻辑是等待当前从库达到这个位置后返回, 返回期间执行的事务个数。
参数timeout可选,若缺省则无限等待,timeout<=0时与缺省的逻辑相同。若为正数,则等待这么多秒,超时函数返回-1.
其他返回值:若当前slave为启动或在等待期间被终止,返回NULL; 若指定的值已经在之前达到,返回0。

重置slave

语法

RESET SLAVE [ALL] [channel_option]
channel_option:
FOR CHANNEL channel

这个命令清除了slave保存的关于master和relay log的信息,删除所有的relay log。使用这个命令,slave的replication线程必须先停下来。

这个命令还有影响到channel,待补充。

reset slave不会改变slave与master的连接信息,比如master的ip地址,端口,用户名和密码等,reset slave all将会重置连接的信息,因此这意味着需要重启slave的mysqld进程。

跳过执行

SET GLOBAL sql_slave_skip_counter = N

start slave

语法:

START SLAVE [thread_types] [until_option] [connection_options] [channel_option]

thread_types:
[thread_type [, thread_type] ... ]
    thread_type:
    IO_THREAD | SQL_THREAD
until_option:
    UNTIL { {SQL_BEFORE_GTIDS | SQL_AFTER_GTIDS} = gtid_set
    | MASTER_LOG_FILE = 'log_name', MASTER_LOG_POS = log_pos
    | RELAY_LOG_FILE = 'log_name', RELAY_LOG_POS = log_pos
    | SQL_AFTER_MTS_GAPS }
    
connection_options:
[USER='user_name'] [PASSWORD='user_pass'] [DEFAULT_AUTH='plugin_name'] [PLUGIN_DIR='plugin_dir']

channel_option:
    FOR CHANNEL channel
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
    uuid:interval[:interval]...
uuid:
    hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
    [0-9,A-F]
interval:
    n[-n]
    (n >= 1)

不指定thread_type时,IO线程和SQL线程都会被启动,IO线程从master读取日志,SQL线程读取relay log并执行。

stop slave

语法

STOP SLAVE [thread_types]

thread_types:
    [thread_type [, thread_type] ... ]
        
thread_type: IO_THREAD | SQL_THREAD

channel_option:
    FOR CHANNEL channel

当停止slave服务器时,应先执行stop slave停止slave功能。

执行这个命令时,还需要考虑是基于行的replication还是基于语句的replication,存储引擎,以及事务型表和非事务型表。

控制Group复制

start group_replication;
stop group_replication;

联系我


ytbean
3.1k 声望714 粉丝

十年学会编程


引用和评论

0 条评论