一、MySQL底层执行原理详解

MySQL的内部组件结构

Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_mysql

大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。

1、Server层

主要包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数 (如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

2、Store层

存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。也就是说如果我们在create table时不指定 表的存储引擎类型,默认会给你设置存储引擎为InnoDB。

MySQL各个组件详解

1、连接器

我们知道由于MySQL是开源的,他有非常多种类的客户端:navicat,mysql front,jdbc,SQLyog等非常丰富的客户端,这些客户端要向mysql发起通信都必须先跟Server端建立通信连接,而建立连接的工作就是有连接器完成的。

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管 理连接。


# mysql ‐h host[数据库地址] ‐u root[用户] ‐p root[密码] ‐P 3306


连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份, 这个时候用的就是你输入的用户名和密码;

一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。用户的权限表在系统表空间的mysql的user表中。

连接完成后,如果没有后续的动作,这个连接就处于空闲状态,可以使用show processlist 命令查看空闲状态:其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。

客户端如果长时间不发送command到Server端,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值 是 8 小时。 查看wait_timeout。超过8个小时,长连接自动关闭。

如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次 查询就断开连接,下次查询再重新建立一个。

2、查询缓存

1、连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。

2、MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找 到 key,那么这个 value 就会被直接返回给客户端

3、如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查 询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高

4、大多数情况查询缓存就是个鸡肋,为什么呢?

因为查询缓存往往弊大于利。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。 因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率 会非常低

一般建议大家在静态表里使用查询缓存,什么叫静态表呢?就是一般我们极少更新的表。比如,一个系统配置表、字典表,那这张表上的查询才适合使用查询缓存。好在 MySQL 也提供了这种“按需使用”的方式。你可以将my.cnf参数 query_cache_type 设置成 DEMAND。

值得一提的是:Mysql7默认关闭查询缓存;Mysql8.0已经移除了查询缓存功能。

3、分析器

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。

MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符 串“ID”识别成“列 ID”。

做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句 是否满足 MySQL 语法。

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 from 写成了 “rom”


mysql> select * fro test where id=1; 2 ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds t o your MySQL server version for the right syntax to use near 'fro test where id=1' at line 1


词法分析器分成6个主要步骤完成对sql语句的分析

1、词法分析

2、语法分析

3、语义分析

4、构造执行树

5、生成执行计划

6、计划的执行

4、优化器

经过了分析器,MySQL 就知道客户端需要做什么操作了。在开始执行之前,还要先经过优化器的处理。 优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。

5、执行器

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。


mysql> select * from test where id=1;


如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。 比如我们这个例子中的表 test 中,ID 字段没有索引,那么执行器的执行流程是这样的

调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;

调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。

执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。


二、Redolog/Undolog/Binlog详解与区别

1. redoLog

1.1 为什么需要redo log

我们都知道,事务的四大特性里面有一个是持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。

事务在运行过程中,都是在内存的Buffer Pool修改页面,事务提交后,这些被修改后的脏页并不会立刻刷盘(立刻刷盘开销太大,一方面是一个页面可能就修改了一点点,将整个页面刷盘不值当,另一方面是一个事务会涉及不同的页面,如果将这些页面都刷盘会产生很多的随机IO)。

但如果不采取其他措施,那么在事务提交后MySQL发生故障,导致内存中数据丢失,那么这个已提交事务作出的更改也会丢失,那么mysql是如何保证内存和磁盘的一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:
1)、 因为Innodb是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了!
2)、 一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差!

所以这里就需要引入redo日志,对任意页面进行修改的操作都会生成redo日志,在事务提交时,只要保证生成的redo日志成功落盘即可,这样,即使MySQL发生故障导致内存中的数据丢失,也可以根据已落盘的redo日志恢复数据

1.2 redo log基本概念

redo log是InnoDB存储引擎层的日志,又称重做日志文件,用于记录事务操作的变化,记录的是数据修改之后的值,不管事务是否提交都会记录下来。一个事务生成的redo日志是按顺序写入磁盘的,是顺序IO,在实例和介质失败(media failure)时,redo log文件就能派上用场,如数据库掉电,InnoDB存储引擎会使用redo log恢复到掉电前的时刻,以此来保证数据的完整性。

redo log包括两部分:一个是内存中的日志缓冲(redo log buffer),另一个是磁盘上的日志文件(redo log file)。mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术就是MySQL里经常说到的WAL(Write-Ahead Logging) 技术。

1.3 redo log记录形式

redo log日志的大小是固定的,即记录满了以后就从头循环写。
redolog记录方式:

简单的redo日志——记录哪个表空间中的哪个页面从哪个位置开始的多少个节点要修改成什么

复杂的redo日志——记录了对哪个表空间的哪个页面进行修改,存储了对该页面进行修改操作的一些必备要素,重启时,MySQL会根据redo日志的类型,将redo日志中的必备要素作为参数,调用日志类型对应的函数,恢复数据

在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer写入redo log file实际上是先写入OS Buffer,然后再通过系统调用fsync()将其刷到redo log file中,过程如下:

mysql支持三种将redo log buffer写入redo log file的时机,可以通过innodb_flush_log_at_trx_commit参数配置,各参数值含义如下:

redo log实际上记录数据页的变更,而这种变更记录是没必要全部保存,因此redo log实现上采用了大小固定,循环写入的方式,当写到结尾时,会回到开头循环写日志。

总结:确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
还想继续深入的同学可以看看这篇文章
无比详细的redolog讲解

2. binlog

2.1 binlog基本概念

binlog是属于MySQL Server层面的,又称为归档日志,属于逻辑日志,是以二进制的形式记录的,用于记录数据库执行的写入性操作(不包括查询)信息,依靠binlog是没有crash-safe能力的

啥是逻辑日志啥是物理日志:
逻辑日志:可以简单理解为记录的就是sql语句
物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更

另外,binlog是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,当文件大小达到给定值之后,会生成新的文件来保存日志。

2.2 binlog使用场景

在实际应用中,binlog的主要使用场景有两个,分别是主从复制和数据恢复。
1)、主从复制:在Master端开启binlog,然后将binlog发送到各个Slave端,Slave端重放binlog从而达到主从数据一致。
2)、 数据恢复:通过使用mysqlbinlog工具来恢复数据。

2.3 binlog日志格式

binlog日志有三种格式,分别为STATMENT、ROW和MIXED。

在 MySQL 5.7.7之前,默认的格式是STATEMENT,MySQL 5.7.7之后,默认值是ROW。日志格式通过binlog-format指定。

STATMENT 基于SQL语句的复制(statement-based replication, SBR),每一条会修改数据的sql语句会记录到binlog中。 优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 从而提高了性能; 缺点:在某些情况下会导致主从数据不一致,比如执行sysdate()、slepp()等。
ROW 基于行的复制(row-based replication, RBR),不记录每条sql语句的上下文信息,仅需记录哪条数据被修改了。 优点:不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题; 缺点:会产生大量的日志,尤其是alter table的时候会让日志暴涨
MIXED 基于STATMENT和ROW两种模式的混合复制(mixed-based replication, MBR),一般的复制使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog

3. undo log

数据库事务四大特性中有一个是原子性,具体来说就是 原子性是指对数据库的一系列操作,要么全部成功,要么全部失败,不可能出现部分成功的情况。

实际上,原子性底层就是通过undo log实现的。

undo log主要记录了数据的逻辑变化,比如一条INSERT语句,对应一条DELETE的undo log,对于每个UPDATE语句,对应一条相反的UPDATE的undo log,这样在发生错误时,就能回滚到事务之前的数据状态。

undo log保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

4. redolog和binlog区别

redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
redo log是InnoDB存储引擎层的日志,binlog是MySQL Server层记录的日志, 两者都是记录了某些操作的日志(不是所有)自然有些重复(但两者记录的格式不同)。


三、通过Binlog恢复数据

1. 开启Bin-log归档

删库是不需要跑路的,因为我们的SQL执行时,会将sql语句的执行逻辑记录在我们的bin-log当中,什么是bin-log呢? binlog是Server层实现的二进制日志,他会记录我们的cud操作。Binlog有以下几个特点:

1、Binlog在MySQL的Server层实现(引擎共用)
2、Binlog为逻辑日志,记录的是一条语句的原始逻辑
3、Binlog不限大小,追加写入,不会覆盖以前的日志

如果,我们误删了数据库,可以使用binlog进行归档!要使用binlog归档,首先我们得记录binlog,因此需要先开启MySQL的 binlog功能.

1.1 开启binlog日志,配置my.cnf

# 配置开启binlog
log_bin=/usr/local/mysql/data/binlog/mysql‐bin

# binlog格式,有3种statement,row,mixed
binlog_format=ROW

# 注意5.7以及更高版本需要配置本项(自定义,保证唯一性):
server_id=1

# 表示每1次执行写入就与硬盘同步,会影响性能,为0时表示,事务提交时mysql不做刷盘操作,由系统决定
sync_binlog=1

1.2 binlog命令


-- 查看bin‐log是否开启 show variables like '%log_bin%';


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_服务器_02


-- 查看所有binlog日志列表 show master logs;


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_mysql_03


-- 查看master状态,即最后(最新)一个binlog日志的编号名称,及其最后一个操作事件pos结束点(Position)值。 show master status;


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_MySQL_04


-- 会多一个最新的bin‐log日志 flush logs;


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_数据库_05


-- 清空所有的bin‐log日志 mysql> reset master;


1.3 查看binlog内容

mysqlbinlog常见的选项有一下几个:

--start-datetime:从二进制日志中读取指定等于时间戳或者晚于本地计算机的时间

--stop-datetime:从二进制日志中读取指定小于时间戳或者等于本地计算机的时间 取值和上述一样

--start-position:从二进制日志中读取指定position 事件位置作为开始。

--stop-position:从二进制日志中读取指定position 事件位置作为事件截至

-- 不带参数查看
mysqlbinlog /var/lib/mysql/mysql-bin.000009

-- 带"--no-defaults"查看
mysqlbinlog --no-defaults /var/lib/mysql/mysql-bin.000009

-- 根据开始时间、结束时间查看
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --start-datetime="2018-09-12 18:45:00"
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --stop-datetime="2018-09-12:18:47:00"
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --start-datetime="2018-09-12 18:45:00" --stop-datetime="2018-09-12:18:47:00"

-- 根据开始位置、结束位置查看(包前不包后原则) -r 指输出到文件
-- 若指定了开始位置,不指定结束位置,则会截取开始处到结尾的binlog日志:
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --start-position=365 -r pos.sql
-- 若指定了结束位置,不指定开始位置,则截取最开始到最后面的全部binlog日志:
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --stop-position=465 -r pos.sql
-- 所谓的位置点,就是mysqlbinlog解析文件里的不同行行首的“#at 数字”标识的数据。
mysqlbinlog /var/lib/mysql/mysql-bin.000009 --start-position=365 --stop-position=465 -r pos.sql

-- 加参数显示的binlog日志的SQL执行记录
mysqlbinlog /var/lib/mysql/mysql-bin.000001 -d 数据库名 -t 表名 --base64-output=decode-rows --skip-gtids -vv | grep -B 1 -i '关键字'

Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_查询缓存_06

2. 恢复备份数据

1、先仔细查看最后一个binlog日志,并记录下关键的pos点,到底是哪个pos点的操作导致了数据库的破坏(通常在最后几步);

- 查看最后一份binlog
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000005 |  4053335 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

2、先备份一下最后一个binlog日志文件;


[root@ mysql]# cp -v mysql-bin.000005 /opt/backup/ ‘mysql-bin.000005’ -> ‘/opt/backup/mysql-bin.000005’


3、执行一次刷新日志索引操作,重新开始新的binlog日志记录文件;


-- 刷新日志 mysql> flush logs; -- 查看是否生成新的日志文件 show master status;


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_mysql_07

 4、读取binlog日志,分析问题;
方法一:使用mysqlbinlog读取binlog日志:


cd /var/lib/mysql/ mysqlbinlog mysql-bin.000005


方法二:登录服务器,并查看(推荐此种方法)


mysql> show binlog events in 'mysql-bin.000005'; 或者 mysql> show binlog events in 'mysql-bin.000005'\G;


MariaDB [(none)]> show binlog events in 'mysql-bin.000005'\G;
*************************** 7. row ***************************
   Log_name: mysql-bin.000005
        Pos: 459
 Event_type: Query
  Server_id: 1
End_log_pos: 614
       Info: use `test`; insert into member(`name`,`age`,`classid`) values('demo0',00,'cls3'),('demo1',11,'cls4')
*************************** 8. row ***************************
.......
.......

*************************** 10. row ***************************
   Log_name: mysql-bin.000005
        Pos: 687
 Event_type: Query
  Server_id: 1
End_log_pos: 772
       Info: drop database test
*************************** 11. row ***************************
   Log_name: mysql-bin.000005
        Pos: 772
 Event_type: Rotate
  Server_id: 1
End_log_pos: 819
       Info: mysql-bin.000006;pos=4
11 rows in set (0.000 sec)

通过分析,造成数据库破坏的pos点区间是介于687-772 之间(这是按照日志区间的pos节点算的),只要恢复到687前就可。

5、使用之前全备份的数据,恢复到当时备份时;


# 进入备份目录 cd /opt/backup/ # 将指定pos前的数据转成sql文件(包前不包后原则) mysqlbinlog /opt/backup/mysql-bin.000005 --stop-position=687 -r test_2022-07-16.sql


6、将sql执行到mysql;


mysql -uroot -p123456 -v < test_2022-07-16.sql


但是这仅仅只是恢复当时备份时的数据,备份之后插入的数据没有恢复过来;需要重复操作把新生成的日志文件操作重新备份过来。

3. 从binlog日志恢复数据

1. 恢复数据语法


mysqlbinlog mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名


常用参数选项解释:

--start-position=687 起始pos点
--stop-position=772 结束pos点
--start-datetime="2016-9-25 22:01:08" 起始时间点
--stop-datetime="2019-9-25 22:09:46" 结束时间点
--database=zyyshop 指定只恢复zyyshop数据库(一台主机上往往有多个数据库,只限本地log日志)

不常用选项:

-u --user=name 连接到远程主机的用户名
-p --password[=name] 连接到远程主机的密码
-h --host=name 从远程主机上获取binlog日志
--read-from-remote-server 从某个MySQL服务器上读取binlog日志

小结:实际是将读出的binlog日志内容,通过管道符传递给mysql命令。这些命令、文件尽量写成绝对路径;

2. 恢复数据几种方式

a) 完全恢复(需要手动vim编辑mysql-bin.000006,将那条drop语句剔除掉)


cd /opt/backup/ cp /var/lib/mysql/mysql-bin.000006 /opt/backup mysqlbinlog /opt/backup/mysql-bin.000006 > /opt/backup/000006.sql vim /opt/backup/000006.sql #删除里面的drop语句 mysql -uroot -p -v < /opt/backup/000006.sql


温馨提示:
在恢复全备数据之前必须将该binlog文件移出,否则恢复过程中,会继续写入语句到binlog,最终导致增量恢复数据部分变得比较混乱!

b) 指定pos结束点恢复(部分恢复):
–stop-position=687 pos结束节点(按照事务区间算,是687)


mysqlbinlog /var/lib/mysql/mysql-bin.000005 --stop-position=687 --database=test | mysql -uroot -p123456 -v test


b)指定pos结束点恢复(部分恢复):
–stop-position=687 pos结束节点(按照事务区间算,是687)


/usr/bin/mysqlbinlog --stop-position=687 --database=test /var/lib/mysql/mysql-bin.000005 | /usr/bin/mysql -uroot -p -v test



四、主从复制与读写分离详解

在实际的生产环境中,如果对MySQL数据库的读和写都在一台数据库服务中操作,无论在安全性、高可用性,还是高并发性等各个方面都是完全不能满足实际需求的,一般来说都是通过主从复制(Master-Slave)的方式来同步数据,再通过读写分离来提升数据库的并发负载能力这样的方案进行部署与实施

一、MYSQL主从复制原理

1.1、MySQL主从复制的类型

基于语句的复制(STATEMENT):在主服务器上执行的 SQL 语句,在从服务器上执行同样的语句。MySQL 默认采用基于语句的复制,效率比较高。
基于行的复制(ROW):把改变的内容复制过去,而不是把命令在从服务器上执行一遍。
混合类型的复制(MIXED):默认采用基于语句的复制,一旦发现基于语句无法精确复制时,就会采用基于行的复制。

1.2、MySQL主从复制的工作过程

Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_查询缓存_08

主从复制核心部分就是两个日志三个线程(高版本的mysql以及异步复制、半同步复制、全同步复制)
二个日志:二进制和中继日志
三个线程:master的dump和slave的I/O、SQL
主要原理:master将数据保存在二进制日志中,I/O向dump发出同步请求,dump把数据发送给I/O线程,I/O写入本地的中继日志SQL线程读取本地的中继日志数据,同步到自己数据库中,完全同步

1、Master节点将数据的改变记录成二进制日志(bin log),当Master上的数据发生改变时,则将其改变写入二进制日志中。
2、Slave节点会在一定时间间隔内对Master的二进制日志进行探测其是否发生改变,如果发生改变,则开始一个I/O线程请求 Master的二进制事件。
3、同时Master节点为每个I/O线程启动一个dump线程,用于向其发送二进制日志,并保存至Slave节点本地的中继日志(Relay log)中,Slave节点将启动SQL线程从中继日志中读取二进制日志,在本地重放,即解析成 sql 语句逐一执行,使得其数据和 Master节点的保持一致,最后I/O线程和SQL线程将进入睡眠状态,等待下一次被唤醒。
注:

  • 中继日志通常会位于 OS 缓存中,所以中继日志的开销很小。
  • 复制过程有一个很重要的限制,即复制在 Slave上是串行化的,也就是说 Master上的并行更新操作不能在 Slave上并行操作。

1.3、Mysql主从复制的四种同步方式

1、异步复制(Async Replication)(默认同步方式)

主库将更新写入Binlog日志文件后,不需要等待数据更新是否已经复制到从库中,就可以继续处理更多的请求。Master将事件写入binlog,但并不知道Slave是否或何时已经接收且已处理。在异步复制的机制的情况下,如果Master宕机,事务在Master上已提交,但很可能这些事务没有传到任何的Slave上。假设有Master->Salve故障转移的机制,此时Slave也可能会丢失事务。MySQL复制默认是异步复制,异步复制提供了最佳性能。

2、同步复制( sync Replication )
主库将更新写入Binlog日志文件后,需要等待数据更新已经复制到从库中,并且已经在从库执行成功,然后才能返回继续处理其它的请求。同步复制提供了最佳安全性,保证数据安全,数据不会丢失,但对性能有一定的影响。

3、半同步复制( semi-sync Replication)
主库提交更新写入二进制日志文件后,等待数据更新写入了从服务器中继日志中,然后才能再继续处理其它请求。该功能确保至少有1个从库接收完主库传递过来的binlog内容已经写入到自己的relay log里面了,才会通知主库上面的等待线程,该操作完毕。
半同步复制,是最佳安全性与最佳性能之间的一个折中。
MySQL 5.5版本之后引入了半同步复制功能,主从服务器必须安装半同步复制插件,才能开启该复制功能。如果等待超时,超过rpl_semi_sync_master_timeout参数设置时间(默认值为10000,表示10秒),则关闭半同步复制,并自动转换为异步复制模式。当master dump线程发送完一个事务的所有事件之后,如果在rpl_semi_sync_master_timeout内,收到了从库的响应,则主从又重新恢复为增强半同步复制。
ACK (Acknowledge character)即是确认字符。

4、增强半同步复制(lossless Semi-Sync Replication、无损复制)
增强半同步是在MySQL 5.7引入,其实半同步可以看成是一个过渡功能,因为默认的配置就是增强半同步,所以,大家一般说的半同步复制其实就是增强的半同步复制,也就是无损复制。
增强半同步和半同步不同的是,等待ACK时间不同
rpl_semi_sync_master_wait_point = AFTER_SYNC(默认)
半同步的问题是因为等待ACK的点是Commit之后,此时Master已经完成数据变更,用户已经可以看到最新数据,当Binlog还未同步到Slave时,发生主从切换,那么此时从库是没有这个最新数据的,用户看到的是老数据。
增强半同步将等待ACK的点放在提交Commit之前,此时数据还未被提交,外界看不到数据变更,此时如果发送主从切换,新库依然还是老数据,不存在数据不一致的问题。

二、读写分离

2.1、读写分离的概念

读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。

2.2、读写分离存在的意义

因为数据库的“写”(写10000条数据可能要3分钟)操作是比较耗时的。
但是数据库的“读”(读10000条数据可能只要5秒钟)。
所以读写分离,解决的是,数据库的写入,影响了查询的效率。

2.3、什么时候要读写分离

数据库不一定要读写分离,如果程序使用数据库较多时,而更新少,查询多的情况下会考虑使用。利用数据库主从同步,再通过读写分离可以分担数据库压力,提高性能。

2.4、读写分离原理

读写分离就是只在主服务器上写,只在从服务器上读。基本的原理是让主数据库处理事务性操作,而从数据库处理 select 查询。数据库复制被用来把主数据库上事务性操作导致的变更同步到集群中的从数据库。

2.5、MySQL 读写分离分为两种

基于程序代码内部实现
在代码中根据 select、insert 进行路由分类,这类方法也是目前生产环境应用最广泛的。
优点是性能较好,因为在程序代码中实现,不需要增加额外的设备为硬件开支;缺点是需要开发人员来实现,运维人员无从下手。
但是并不是所有的应用都适合在程序代码中实现读写分离,像一些大型复杂的Java应用,如果在程序代码中实现读写分离对代码改动就较大。

基于中间代理层实现
代理一般位于客户端和服务器之间,代理服务器接到客户端请求后通过判断后转发到后端数据库,有以下代表性程序。
(1)MySQL-Proxy。MySQL-Proxy 为 MySQL 开源项目,通过其自带的 lua 脚本进行SQL 判断。
(2)Atlas。是由奇虎360的Web平台部基础架构团队开发维护的一个基于MySQL协议的数据中间层项目。它是在mysql-proxy 0.8.2版本的基础上,对其进行了优化,增加了一些新的功能特性。360内部使用Atlas运行的mysql业务,每天承载的读写请求数达几十亿条。支持事物以及存储过程。
(3)Amoeba。由陈思儒开发,作者曾就职于阿里巴巴。该程序由Java语言进行开发,阿里巴巴将其用于生产环境。但是它不支持事务和存储过程。

五、主从复制实践

Master 服务器:192.168.252.158
Slave1 服务器:192.168.252.160
Slave2 服务器:192.168.252.161(模拟增加从库)

1、关闭防火墙和安全机制


systemctl stop firewalld systemctl disable firewalld setenforce 0


2、Mysql主从服务器时间同步

# 安装ntp和ntpdate
yum -y install ntp ntpdate

# 同步时间
ntpdate cn.pool.ntp.org

# 定时更新时间
crontab -e

* */1 * * * /usr/sbin/ntpdate cn.pool.ntp.org

一、Master服务器

1、配置Mysql主服务器

vim my.cnf
server_id=1
#添加,主服务器开启二进制日志
log_bin=master-bin
binlog_format=ROW
#添加,允许slave从master复制数据时可以写入到自己的二进制日志
log_slave_updates=true

2、重启Mysql


systemctl restart mysqld


3、检查是否开启bin_log日志


show variables like '%log_bin%';


4、创建账号并授权


mysql -u root -p123456; # 给从服务器创建账号并授权 GRANT REPLICATION SLAVE ON *.* TO 'myslave'@'192.168.252.%' IDENTIFIED BY '123456'; # 刷新 FLUSH PRIVILEGES;


5、查看主服务器权限


show master status;


Mysql--底层结构、Redolog/Undolog/Binlog详解与区别、通过Binlog恢复数据、主从复制与读写分离详解一、MySQL底层执行原理详解_服务器_09

 #File 列显示日志名,Position 列显示偏移量

二、Slave服务器

1、从服务器的mysql配置

vim my.cnf

#修改,注意id与Master的不同,两个Slave的id也要不同
server-id = 2
#添加,开启中继日志,从主服务器上同步日志文件记录到本地                                
relay-log=relay-lgg-bin
#添加,定义中继日志文件的位置和名称,一般和relay-log在同一目录                    
relay-log-index=slave-relya-bin.index   
#当 slave 从库宕机后,假如 relay-log 损坏了,导致一部分中继日志没有处理,则自动放弃所有未执行的 relay-log,并且重新从 master 上获取日志,这样就保证了relay-log 的完整性。默认情况下该功能是关闭的,将 relay_log_recovery 的值设置为 1 时, 可在 slave 从库上开启该功能,建议开启。
relay_log_recovery = 1

2、重启Mysql


systemctl restart mysqld


 3、添加主服务器


mysql -u root -p123456
#配置同步,注意 master_log_file 和 master_log_pos 的值要与Master查询的一致
CHANGE master to master_host='192.168.252.158',master_user='myslave',master_password='123456',master_log_file='master-bin.000003',master_log_pos=552;


4、开启从服务器


start slave;


开启:start slave; 重置:reset slave; 停止:stop slave;

 5、查看状态

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.252.158
                  Master_User: myslave
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000003
          Read_Master_Log_Pos: 552
               Relay_Log_File: relay-lgg-bin.000004
                Relay_Log_Pos: 767
        Relay_Master_Log_File: master-bin.000003
             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: 552
              Relay_Log_Space: 1573
              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: a684f7f5-5370-11ed-8e20-0242ac110004
             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 more updates
           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
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

// 确保 IO 和 SQL 线程都是 Yes,代表同步正常。

Slave_IO_Running: Yes

# 负责与主机的io通信

Slave_SQL_Running: Yes  #负责自己的slave mysql进程

三、添加新的Slave服务器

 1、从服务器的mysql配置

vim my.cnf
                        
#修改,注意id与Master的不同,两个Slave的id也要不同
server-id = 3                    
#添加,开启中继日志,从主服务器上同步日志文件记录到本地
relay-log=relay-log-bin
#添加,定义中继日志文件的位置和名称,一般和relay-log在同一目录
relay-log-index=slave-relay-bin.index
relay_log_recovery = 1

2、重启Mysql


systemctl restart mysqld


 3、添加主服务器


mysql -u root -p123456
#配置同步,注意 master_log_file 和 master_log_pos 的值要与Master查询的一致
CHANGE master to master_host='192.168.252.158',master_user='myslave',master_password='123456',master_log_file='master-bin.000003',master_log_pos=552;


4、开启从服务器


start slave;


六、读写分离实践

参考:

http://www.mycat.org.cn/

http://www.mycat.org.cn/mycat1.html

https://github.com/MyCATApache/Mycat-Server/wiki/2.1-docker%E5%AE%89%E8%A3%85Mycat