MySQL主从复制(一):主备一致

2024-05-25 05:04

本文主要是介绍MySQL主从复制(一):主备一致,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MySQL主备的基本原理


如图所示就是基本的主备切换流程:

在状态1中, 客户端的读写都直接访问节点A, 而节点B是A的备库, 只是将A的更新都同步过来, 到本地执行。 这样可以保持节点B和A的数据是相同的。

当需要切换的时候, 就切成状态2。 这时候客户端读写访问的都是节点B, 而节点A是B的备库。

在状态1中, 虽然节点B没有被直接访问, 但是我依然建议你把节点B(也就是备库) 设置成只读(readonly) 模式。 这样做, 有以下几个考虑:

1)有时候一些运营类的查询语句会被放到备库上去查, 设置为只读可以防止误操作。

2)防止切换逻辑有bug, 比如切换过程中出现双写, 造成主备不一致。

3)可以用readonly状态, 来判断节点的角色。

问:既然把备库设置成只读了,还怎么跟主库保持同步更新呢?

因为readonly设置对超级(super)权限用户是无效的, 而用于同步更新的线程, 就拥有超级权限。

接下来, 我们再看看节点A到B这条线的内部流程是什么样的。下图中画出的就是一个update语句在节点A执行, 然后同步到节点B的完整流程图。

从上图中可以看到,主库接收到客户端的更新请求后, 执行内部事务的更新逻辑, 同时写binlog。

备库B跟主库A之间维持了一个长连接。 主库A内部有一个线程, 专门用于服务备库B的这个长连接。 一个事务日志同步的完整过程是这样的:

1)在备库B上通过change master命令, 设置主库A的IP、 端口、 用户名、 密码, 以及要从哪个位置开始请求binlog, 这个位置包含文件名和日志偏移量。

2)在备库B上执行start slave命令, 这时候备库会启动两个线程, 就是图中的io_thread和sql_thread。 其中io_thread负责与主库建立连接。

3)主库A校验完用户名、 密码后, 开始按照备库B传过来的位置, 从本地读取binlog, 发给B。

4)备库B拿到binlog后, 写到本地文件, 称为中转日志(relaylog)。

5)sql_thread读取中转日志, 解析出日志里的命令, 并执行。

注:后来由于多线程复制方案的引入, sql_thread演化成为了多个线程, 跟我们今天要介绍的原理没有直接关系, 暂且不展开。

binlog的三种格式对比


binlog包含3种日志格式:

1)statement(不推荐):仅记录事务执行的DML语句,而不记录数据变更;不能保证主从复制的安全,因而不推荐。

2)row(推荐):记录事务变更的数据表以及表中每一行记录的数据;能够保证主从复制的安全,但日志量大。

3)mixed(现在用的不多了):上述两种方案的折中,mixed格式的日志能够自动检测,对于安全的日志,使用statement格式;反之,使用row格式;

规范:由于row格式的binlog日志记录详实,能够用于数据恢复,比如执行闪回操作,因而线上环境一般推荐使用row格式。

下面举例说明三种格式间的区别:

CREATE TABLE `t` (`id` int(11) NOT NULL,`a` int(11) DEFAULT NULL,`t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `a` (`a`),KEY `t_modified`(`t_modified`)
) ENGINE=InnoDB;insert into t values(1,1,'2018-11-13');
insert into t values(2,2,'2018-11-12');
insert into t values(3,3,'2018-11-11');
insert into t values(4,4,'2018-11-10');
insert into t values(5,5,'2018-11-09');-- 删除1条记录(mysql客户端开启-c模式)
delete from t /*comment*/ where a>=4 and t_modified<='2018-11-10' limit 1;-- 查看binlog内容
show binlog events in 'master.000001’;

注:如果用MySQL客户端来做这个实验的话, 要记得加-c参数(即在连接MySQL命令后面加上 -c), 否则客户端会自动去掉注释。

statement格式的binlog

当binlog_format=statement时, binlog里面记录的就是SQL语句的原文。 你可以用如下命令查看binlog中的内容:

show binlog events in 'master.000001’;

查看结果:

现在来看一下上图中的输出结果:

1)第一行SET@@SESSION.GTID_NEXT='ANONYMOUS’你可以先忽略, 后面文章我们会在介绍主备切换的时候再提到。

2)第二行是一个BEGIN, 跟第四行的commit对应, 表示中间是一个事务。

3)第三行就是真实执行的语句了。 可以看到, 在真实执行的delete命令之前, 还有一个“use ‘test’”命令。 这条命令不是我们主动执行的, 而是MySQL根据当前要操作的表所在的数据库,自行添加的。 这样做可以保证日志传到备库去执行的时候, 不论当前的工作线程在哪个库里, 都能够正确地更新到test库的表t。use 'test’命令之后的delete 语句, 就是我们输入的SQL原文了。 可以看到, binlog“忠实”地记录了SQL命令, 甚至连注释也一并记录了。

4)最后一行是一个COMMIT。

问1:xid是什么?有什么作用?

答:xid用于检查一个事物的binlog是否是完整的。row格式的binlog,如果最后有一个XID event,则说明该binlog是完整的。

row格式的binlog

为了说明statement 和 row格式的区别, 我们来看一下这条delete命令的执行效果图:

可以看到, 运行这条delete命令产生了一个warning, 原因是当前binlog设置的是statement格式, 并且语句中有limit, 所以这个命令可能是unsafe的。

为什么这么说呢? 这是因为delete 带limit, 很可能会出现主备数据不一致的情况。 比如上面这个例子:

1)如果delete语句使用的是索引a, 那么会根据索引a找到第一个满足条件的行, 也就是说删除的是a=4这一行。

2)但如果使用的是索引t_modified, 那么删除的就是 t_modified='2018-11-09’也就是a=5这一行。

由于statement格式下, 记录到binlog里的是语句原文, 因此可能会出现这样一种情况: 在主库执行这条SQL语句的时候, 用的是索引a; 而在备库执行这条SQL语句的时候, 却使用了索引t_modified。 因此, MySQL认为这样写是有风险的。

问2:如果把binlog的格式改为binlog_format=‘row’, 是不是就没有这个问题了呢?

先来看看这时候binog中的内容:

可以看到, 与statement格式的binlog相比, 前后的BEGIN和COMMIT是一样的。 但是, row格式的binlog里没有了SQL语句的原文, 而是替换成了两个event: Table_map和Delete_rows。

1)Table_map event, 用于说明接下来要操作的表是test库的表t。

2)Delete_rows event, 用于定义删除的行为。

但是,通过上图是看不到详细信息的, 还需要借助mysqlbinlog工具, 用下面这个命令解析和查看binlog中的内容。 因为上图中的信息显示, 这个事务的binlog是从8900这个位置开始的, 所以可以用start-position参数来指定从这个位置的日志开始解析。

mysqlbinlog -w data/master.000001 --start-position=8900;

解析结果:

从这个图中, 我们可以看到以下几个信息:

1)server id 1, 表示这个事务是在server_id=1的这个库上执行的。

2)每个event都有CRC32的值, 这是因为我把参数binlog_checksum设置成了CRC32。

3)Table_map event跟在图5中看到的相同, 显示了接下来要打开的表, map到数字226。 现在我们这条SQL语句只操作了一张表, 如果要操作多张表呢? 每个表都有一个对应的Table_map event、 都会map到一个单独的数字, 用于区分对不同表的操作。

4)我们在mysqlbinlog的命令中, 使用了-w参数是为了把内容都解析出来, 所以从结果里面可以看到各个字段的值(比如, @1=4、 @2=4这些值,即记录了删除的是哪一行) 。

5)binlog_row_image的默认配置是FULL, 因此Delete_event里面, 包含了删掉的行的所有字段的值。 如果把binlog_row_image设置为MINIMAL, 则只会记录必要的信息, 在这个例子里,就是只会记录id=4这个信息。

6)最后的Xid event, 用于表示事务被正确地提交了。

你可以看到, 当binlog_format使用row格式的时候, binlog里面记录了真实删除行的主键id, 这样binlog传到备库去的时候, 就肯定会删除id=4的行, 不会有主备删除不同行的问题。

mixed格式的binlog

问1:为什么会有mixed格式的binlog呢?

1)因为有些statement格式的binlog可能会导致主备不一致, 所以要使用row格式。

2)但row格式的缺点是, 很占空间。 比如你用一个delete语句删掉10万行数据, 用statement的话就是一个SQL语句被记录到binlog中, 占用几十个字节的空间。 但如果用row格式的binlog,就要把这10万条记录都写到binlog中。 这样做, 不仅会占用更大的空间, 同时写binlog也要耗费IO资源, 影响执行速度。

3)所以, MySQL就取了个折中方案, 也就是有了mixed格式的binlog。 mixed格式的意思是, MySQL自己会判断这条SQL语句是否可能引起主备不一致, 如果有可能, 就用row格式,否则就用statement格式。

注:mixed格式既可以利用binlog格式的优点,同时又避免了数据不一致的风险。

问2:既然mixed格式那么好,为什么现在越来越多的场景要求把MySQL的binlog格式设置为row?

这么做的理由有很多,如:恢复数据。

下面分别从delete、 insert和update这三种SQL语句的角度, 来看看数据恢复的问题。

1)通过上图(row格式的binlog解析结果)可知,当执行delete语句时,row格式的binlog会把被删掉的整行信息保存起来。所以,如果你在执行完一条delete语句后,发现删错数据了,可以直接把binlog中记录的delete语句转成insert,把被错删的数据插入回去就可以恢复了。

2)row格式下,insert语句的binlog里也会记录所有的字段信息,这些信息可以用来精确定位刚刚被插入的那一行。所以,如果执行错了insert语句,可以直接把insert语句转成delete语句,删掉被误插入的一行数据就可以了。

3)如果执行的是update语句的话, binlog里面会记录修改前整行的数据和修改后的整行数据。 所以, 如果你误执行了update语句的话, 只需要把这个event前后的两行信息对调一下, 再去数据库里面执行, 就能恢复这个更新操作了。

虽然mixed格式的binlog现在已经用的不多了,但这里我还是要再借用一下mixed格式来说明一个问题, 来看一下这条SQL语句:

insert into t values(10, 10, now());

问3:如果我们把binlog格式设置为mixed, 你觉得MySQL会把它记录为row格式还是statement格式呢?

语句执行结果:

可以看到, MySQL用的居然是statement格式。 你一定会奇怪, 如果这个binlog过了1分钟才传给备库的话, 那主备的数据不就不一致了吗?(因为inersrt语句中其中一个值为now(),也就是说由于主从复制延迟的存在,该值会发生变化)

mysqlbinlog工具查看结果如下:

从图中的结果可以看到, 原来binlog在记录event的时候, 多记了一条命令: SET TIMESTAMP=1546103491。 它用 SETTIMESTAMP命令约定了接下来的now()函数的返回时间。

因此, 不论这个binlog是1分钟之后被备库执行, 还是3天后用来恢复这个库的备份, 这个insert语句插入的行, 值都是固定的。 也就是说, 通过这条SETTIMESTAMP命令, MySQL就确保了主备数据的一致性。

注:重放binlog数据的时候,不能把里面的statement语句直接拷贝出来执行。因为有些语句的执行结果是依赖于上下文命令的, 直接执行的结果很可能是错误的。所以, 用binlog来恢复数据的标准做法是, 用 mysqlbinlog工具解析出来, 然后把解析结果整个发给MySQL执行。 类似下面的命令:

mysqlbinlog master.000001  --start-position=2738 --stop-position=2973 |mysql -h127.0.0.1 -P13000 -u$user -p$pwd;

这个命令的意思是, 将 master.000001 文件里面从第2738字节到第2973字节中间这段内容解析出来, 放到MySQL去执行。

循环复制问题


通过上面对binlog基本内容的理解,可以了解到binlog的特性确保了在备库执行相同的binlog, 可以得到与主库相同的状态。

因此, 我们可以认为正常情况下主备的数据是一致的。 也就是说, 上图中A、 B两个节点的内容是一致的。 第一张图中我画的是M-S结构, 但实际生产上使用比较多的是双M结构, 如下所示。

双M结构和M-S结构, 其实区别只是多了一条线, 即: 节点A和B之间总是互为主备关系。 这样在切换的时候就不用再修改主备关系。

但是, 双M结构还有一个问题需要解决:

业务逻辑在节点A上更新了一条语句, 然后再把生成的binlog 发给节点B, 节点B执行完这条更新语句后也会生成binlog。 (我建议你把参数log_slave_updates设置为on, 表示备库执行relaylog后生成binlog) 。

那么, 如果节点A同时是节点B的备库, 相当于又把节点B新生成的binlog拿过来执行了一次, 然后节点A和B间, 会不断地循环执行这个更新语句, 也就是循环复制了。

问:如何解决双M结构中的循环复制问题?

答:从上述row格式的binlog解析结果一图中可知,MySQL在binlog中记录了这个命令第一次执行时所在实例的server id。 因此, 我们可以用下面的逻辑, 来解决两个节点间的循环复制的问题:

1)规定两个库的server id必须不同, 如果相同, 则它们之间不能设定为主备关系。

2)一个备库接到binlog并在重放的过程中, 生成与原binlog的server id相同的新的binlog。

3)每个库在收到从自己的主库发过来的日志后, 先判断server id, 如果跟自己的相同, 表示这个日志是自己生成的, 就直接丢弃这个日志。

按照这个逻辑, 如果我们设置了双M结构, 日志的执行流就会变成这样:

1)节点A更新的事务, binlog里面记的都是A的server id。

2)传到节点B执行一次以后, 节点B生成的binlog 的server id也是A的server id。

3)再传回给节点A, A判断到这个server id与自己的相同, 就不会再处理这个日志。 所以, 死循环在这里就断掉了。

小结:思考题


思考1:说到循环复制问题的时候, 我们说MySQL通过判断server id的方式, 断掉死循环。 但是, 这个机制其实并不完备, 在某些场景下, 还是有可能出现死循环。你能构造出一个这样的场景吗? 又应该怎么解决呢?

1)一种场景是, 在一个主库更新事务后, 用命令set global server_id=x修改了server_id。 等日志再传回来的时候, 发现server_id跟自己的server_id不同, 就只能执行了。

2)另一种场景是, 有三个节点的时候, 如图所示, trx1是在节点 B执行的, 因此binlog上的server_id就是B, binlog传给节点 A, 然后A和A’搭建了双M结构, 就会出现循环复制。

思考2:主库 A从本地读取 binlog, 发给从库 B,这里的本地是指文件系统中的page cache还是disk?

答:对于线程 A来说,就是读“文件”。如果这个文件现在还在 page cache中, 那就最好了, 直接读走;如果不在page cache里, 就只好去磁盘读。

注:这个行为是文件系统控制的,MySQL只是执行“读文件”这个操作。

这篇关于MySQL主从复制(一):主备一致的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1000555

相关文章

mysql索引四(组合索引)

单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引;组合索引,即一个索引包含多个列。 因为有事,下面内容全部转自:https://www.cnblogs.com/farmer-cabbage/p/5793589.html 为了形象地对比单列索引和组合索引,为表添加多个字段:    CREATE TABLE mytable( ID INT NOT NULL, use

mysql索引三(全文索引)

前面分别介绍了mysql索引一(普通索引)、mysql索引二(唯一索引)。 本文学习mysql全文索引。 全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。 在MySql中,创建全文索引相对比较简单。例如:我们有一个文章表(article),其中有主键ID(

mysql索引二(唯一索引)

前文中介绍了MySQL中普通索引用法,和没有索引的区别。mysql索引一(普通索引) 下面学习一下唯一索引。 创建唯一索引的目的不是为了提高访问速度,而只是为了避免数据出现重复。唯一索引可以有多个但索引列的值必须唯一,索引列的值允许有空值。如果能确定某个数据列将只包含彼此各不相同的值,在为这个数据列创建索引的时候就应该使用关键字UNIQUE,把它定义为一个唯一索引。 添加数据库唯一索引的几种

mysql索引一(普通索引)

mysql的索引分为两大类,聚簇索引、非聚簇索引。聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引则不同。聚簇索引能够提高多行检索的速度、非聚簇索引则对单行检索的速度很快。         在这两大类的索引类型下,还可以降索引分为4个小类型:         1,普通索引:最基本的索引,没有任何限制,是我们经常使用到的索引。         2,唯一索引:与普通索引

【服务器运维】MySQL数据存储至数据盘

查看磁盘及分区 [root@MySQL tmp]# fdisk -lDisk /dev/sda: 21.5 GB, 21474836480 bytes255 heads, 63 sectors/track, 2610 cylindersUnits = cylinders of 16065 * 512 = 8225280 bytesSector size (logical/physical)

SQL Server中,查询数据库中有多少个表,以及数据库其余类型数据统计查询

sqlserver查询数据库中有多少个表 sql server 数表:select count(1) from sysobjects where xtype='U'数视图:select count(1) from sysobjects where xtype='V'数存储过程select count(1) from sysobjects where xtype='P' SE

SQL Server中,always on服务器的相关操作

在SQL Server中,建立了always on服务,可用于数据库的同步备份,当数据库出现问题后,always on服务会自动切换主从服务器。 例如192.168.1.10为主服务器,12为从服务器,当主服务器出现问题后,always on自动将主服务器切换为12,保证数据库正常访问。 对于always on服务器有如下操作: 1、切换主从服务器:假如需要手动切换主从服务器时(如果两个服务

SQL Server中,isnull()函数以及null的用法

SQL Serve中的isnull()函数:          isnull(value1,value2)         1、value1与value2的数据类型必须一致。         2、如果value1的值不为null,结果返回value1。         3、如果value1为null,结果返回vaule2的值。vaule2是你设定的值。        如

SQL Server中,添加数据库到AlwaysOn高可用性组条件

1、将数据添加到AlwaysOn高可用性组,需要满足以下条件: 2、更多具体AlwaysOn设置,参考:https://msdn.microsoft.com/zh-cn/library/windows/apps/ff878487(v=sql.120).aspx 注:上述资源来自MSDN。

SQL Server中,用Restore DataBase把数据库还原到指定的路径

restore database 数据库名 from disk='备份文件路径' with move '数据库文件名' to '数据库文件放置路径', move '日志文件名' to '日志文件存放置路径' Go 如: restore database EaseWe from disk='H:\EaseWe.bak' with move 'Ease