文章目录
一、前言
二、bin log 二进制日志
三、undo log 回滚日志
四、redo log 重做日志
五、补充
MySQL作为最流行的开源数据库,其重要性不言而喻。日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。常见的日志有以下几种:
作为开发,我们重点需要关注的是 二进制日志bin log(归档日志)、 事务日志redo log(重做日志) 和 undo log(回滚日志),本文接下来会详细介绍这三种日志。
下面就带着这个问题,看看这三种日志是怎么工作的。
1)连接层
最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2)服务层
第二层架构主要完成大多数的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3)引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。数据库中的索引是在存储引擎层实现的。
4)存储层
数据存储层,主要是将数据(如: redolog、undolog、数据、索引、二进制日志、错误日志、查询日志、慢查询日志等)存储在文件系统之上,并完成与存储引擎的交互。
和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
其中slowlog、binlog、errorlog、relaylog归属于MySQL服务层;undolog、redolog归属于引擎层,为innodb所特有。
MySQL有不同类型的日志:慢查询日志、通用查询日志、错误日志、事务日志、二进制日志等几大类,在MySQL8之后又新增了两种日志——中继日志、数据定义语句日志。其中比较重要的是二进制日志binlog (归档日志) 、事务日志redo log(重做日志) 和 undo log(回滚日志)。
MySQL日志主要包括八种
除了二进制日志外,其他日志均为文本文件。默认情况下,所有日志均创建于MySQL数据目录中
查询日志中记录了客户端的所有操作语句(包括所有的增删改查、DDL、DML、DQL语句),而二进制日志不包含查询数据的SQL语句。默认情况下,查询日志是未开启的。如果需要开启查询日志,可以设置以下配置:
如果想要禁用查询日志,可将general_log
设置为0,而后重启MySQL服务sudo systemctl restart mysql
慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于 min_examined_row_limit 的所有SQL语句的日志,默认未开启。long_query_time 默认为10秒,最小为0,精度可以到微秒。如果要开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
# 开启MySQL慢日志查询开关
slow_query_log=1
# 执行时间参数,设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
同理,如果想要禁用慢查询日志,可将slow_query_log
设置为0,而后重启MySQL服务sudo systemctl restart mysql
错误日志是MySQL中最重要的日志之一,它记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,建议首先查看此日志。
该日志是默认开启的,默认存放目录/var/log/,默认的日志文件名为mysqld.log。查看日志位置:
-- 登录mysql,查看系统变量
show variables like '%log_error';
二进制日志(BINLOG)记录了所有的DDL(数据定义语言,创建库、表)语句和DML(数据操纵语言,增删改)语句,但不包括数据查询(SELECT、SHOW)语句。
作用:
细节:
在MySQL8版本中,默认二进制日志是开启着的,涉及到的参数如下:
show variables like '%log_bin';
常用的5.7版本可能只有以下参数:
mysql> SELECT VERSION();
+-----------+
| VERSION() |
+-----------+
| 5.7.35 |
+-----------+
1 row in set (0.00 sec)
mysql> show variables like '%log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | OFF |
| sql_log_bin | ON |
+---------------+-------+
2 rows in set (0.00 sec)
binlog日志有三种格式:分别为STATMENT、ROW和MIXED,具体格式及特点如下:
日志格式 | 含义 |
---|---|
STATEMENT | 基于SQL语句的日志记录,记录的是SQL语句,对数据进行修改的SQL都会记录在日志文件中 |
ROW | 默认格式,基于行的日志记录,记录是每一行的数据变更 |
MIXED | 混合了STATEMENT和ROW两种格式,默认采用STATEMENT,在某些特殊情况下会自动切换为ROW进行记录 |
show variables like '%binlog_format%';
-- 如果想修改二进制日志格式
-- 1.vim /etc/my.cnf
-- 2.往文件内添加 binlog_format=STATEMENT
-- 3.systemctl restart mysqld
具体日志格式详解
由于日志是以二进制方式存储的,不能直接读取,需要通过二进制日志查询工具mysqlbinlog来查看,具体语法:
mysqlbinlog [options] log-files
参数选项:
-d, --database=name 指定数据库名称,只列出指定的数据库相关操作
-o, --offset=# 忽略掉日志中的前n行命令
-v, --verbose 将行事件(数据变更)重构为SQL语句
-vv 将行事件(数据变更)重构为SQL语句,并输出注释信息
mysqlbinlog -v binlog.00002
mysql中有score记录成绩,执行 update score set math = math + 1;
ROW格式下,执行mysqlbinlog -v binlog.00002
,得到如下内容
STATEMENT格式下,执行mysqlbinlog binlog.00003
,得到如下内容
对于比较繁忙的业务系统,每天生成的binlog数据巨大,如果长时间不清除,将会占用大量磁盘空间。可以通过以下几种方式清理日志:
MySQL指令 | 含义 |
---|---|
reset master; | 删除全部binlog日志,删除之后,日志编号将从binlog.000001重新开始 |
purge master logs to 'binlog.******'; | 删除******编号之前的所有日志 |
purge master logs before 'yyyy-mm-dd hh24:mi:ss'; | 删除日志为“yyyy-mm-ddhh24:mi:ss"之前产生的所有日志 |
事务执行过程中,先把日志写入binlog cache,事务提交时,再把binlog cache写到binlog文件中。一个事务的binlog不能被拆开,确保一次性写入,系统将给每个线程分配一块内存作为binlog cache
对于InnoDB存储引擎而言,只有在事务提交时才会记录binlog,那么binlog什么时候才会将内存中的数据刷到磁盘呢?其实mysql是通过sync_binlog参数控制binlog的刷盘时机,取值范围是0-N【write和fsync的时机,由参数sync_binlog控制,默认为0】
在出现IO瓶颈时,将sync_binlog设置成一个较大的值,能提升性能。同样的,若机器宕机会丢失最近N个事务的binlog日志
undo log(回滚日志):是Innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC。
它是InnoDB存储引擎在insert、update、delete的时候产生的便于数据回滚的日志。在数据更新之前,MySQL就需要先把更新前的数据记录到 undo log 日志中,当事务回滚时,可以利用 undo log 来进行回滚。作用包含两个——提供回滚、MVCC(多版本并发控制)。undo log主要分为两种:
事务需要保证原子性,也是说事务中的操作要么全部完成,要么什么也不做。如果事务执行到一半,出错了怎么办-回滚。但是怎么回滚呢,靠 undo 日志。undo 日志就是我们执行sql的逆操作
undo 日志有两个作用:提供回滚和多个行版本控制(MVCC) 数据页里一行数据的格式 见3.3版本链,其中 roll_point 会指向一个undo 日志 undo 日志一般会在事务提交时被删除,但是如果 undo 日志为 MVCC 服务 则暂时保留 一个事务会产生多个 undo 日志,mysql有专门的 undo 页 保存 undo 日志。innodb 会为每一个事务单独分配 undo 页链表(最多分配 4 个链表)
比如现在Tom的账户余额有100,现在有一个事务需要把Tom的账户余额更新为300,大致的流程如下图:
当我们创建了上面的这张表,我们在查看表结构的时候,就可以显式的看到 id、user_name、balance、wealth 这四个字段。 实际上除了这四个字段以外,InnoDB还会自动的给我们添加三个隐藏字段,分别是:
隐藏字段 | 含义 |
---|---|
DB_TRX_ID | 最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID。 |
DB_ROLL_PTR | 回滚指针,指向这条记录的上一个版本,用于配合undo log,指向上一个版本。 |
DB_ROW_ID | 隐藏主键,如果表结构没有指定主键,将会生成该隐藏字段。 |
而上述的前两个字段是肯定会添加的, 是否添加最后一个字段DB_ROW_ID,得看当前表有没有主键,如果有主键,则不会添加该隐藏字段。
不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录。
然后,有四个并发事务同时在访问这张表。
事务日志redo log(重做日志):是Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复。比如MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据
介绍下 缓冲池与数据页的概念
缓冲池(buffer pool):主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度 数据页(page):是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。页中存储的是行数据
MySQL中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到Buffer Pool中。后续的查询都是先从Buffer Pool中找,没有命中再去硬盘加载,减少硬盘IO开销,提升性能。
更新表数据的时候,也是如此,发现Buffer Pool里存在要更新的数据,就直接在Buffer Pool里更新。然后会把在某个数据页上做了什么修改记录到重做日志缓存(redo log buffer)里,接着刷盘到redo log文件里。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面。
redo log的更新流程如下,以一次update操作为例
什么是 redo log?为了方便理解,先举个来自极客时间的例子:
还记得《孔乙己》这篇文章,饭店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。
如果有人要赊账或者还账的话,掌柜一般有两种做法:
一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉; 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。
在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。
这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?
为什么需要 redo log?
为什么需要写Redo Log Buffer 和 Redo Log Flle?而不是直接持久化到磁盘?
跟上述案例类似,在 MySQL 中,如果每一次的更新要写进磁盘,这么做会带来严重的性能问题:
为了解决这个问题,MySQL 的设计者就用了类似掌柜粉板的思路来提升更新效率。这种思路在 MySQL 中叫 WAL(Write-Ahead Logging),意思就是:先写 redo log 日志,后写磁盘。日志和磁盘就对应上面的粉板和账本。
具体到 MySQL 是这样的:有记录需要更新,InnoDB 把记录写到 redo log 中,并更新**内存**中的数据页,此时更新就算完成。同时,后台线程会把操作记录更新异步到磁盘中的数据页。
PS:当需要更新的数据页在内存中时,就会直接更新内存中的数据页;不在内存中时,在可以使用 change buffer(篇幅有限,后面写文章再聊) 的情况下,就会将更新操作记录到 change buffer 中,并将这些操作记录到 redo log 中;如果此时有查询操作,则触发 merge 操作,返回更改后的记录值。
有些人说 InnoDB 引擎把日志记录写到 redo log 中,redo log 在哪,不也是在磁盘上么?
对,这也是一个写磁盘的过程,但是与更新过程不一样的是,更新过程是在磁盘上随机 IO、费时, 而写 redo log 是在磁盘上顺序 IO、效率要高。
PPS:redo log 的存在就是把全局的随机写,变换为局部的顺序写,从而提高效率。
redo log 记录了事务对数据页做了哪些修改。它包括两部分:分别是内存中的日志缓冲(redo log buffer)和磁盘上的日志文件(redo log file)。
mysql 每执行一条 DML 语句,先将记录写入 redo log buffer,后续某个时间点再一次性将多个操作记录写到 redo log file。也就是我们上面提到的 WAL 技术。
计算机操作系统告诉我们:用户空间下的缓冲区数据是无法直接写入磁盘的。因为中间必须经过操作系统的内核空间缓冲区(OS Buffer)。
所以,redo log buffer 写入 redo logfile 实际上是先写入 OS Buffer,然后操作系统调用 fsync() 函数将日志刷到磁盘。过程如下:
mysql 支持三种将 redo log buffer 写入 redo log file 的时机,可以通过 innodb_flush_log_at_trx_commit 参数配置,各参数值含义如下:建议设置成1,这样可以保证MySQL 异常重启之后数据不丢失。
参数值 | 含义 |
---|---|
0(延迟写) | 事务提交时不会将 redo log buffer 中日志写到 os buffer,而是每秒写入os buffer 并调用 fsync() 写入到 redo logfile 中。也就是说设置为0时 是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。 |
1(实时写、实时刷新) | 事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo logfile 中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能差。 |
2(实时写、延迟刷新刷新) | 每次提交都仅写入到 os buffer,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file。 |
InnoDB 的 redo log 是固定大小的。比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么 redo log file 可以记录 4GB 的操作。从头开始写。写到末尾又回到开头循环写。如下图:
上图中,write pos 表示 redo log 当前记录的 LSN (逻辑序列号) 位置,一边写一遍后移,写到第 3 号文件末尾后就回到 0 号文件开头; check point 表示数据页更改记录刷盘后对应 redo log 所处的 LSN(逻辑序列号) 位置,也是往后推移并且循环的。
PS:check point 是当前要擦除的位置,它与数据页中的 LSN 应当是一致的。
write pos 到 check point 之间的部分是 redo log 的未写区域,可用于记录新的记录;check point 到 write pos 之间是 redo log 已写区域,是待刷盘的数据页更改记录。
当 write pos 追上 check point 时,表示 redo log file 写满了,这时候有就不能执行新的更新。得停下来先擦除一些记录(擦除前要先把记录刷盘),再推动 check point 向前移动,腾出位置再记录新的日志。
有了 redo log ,即在 InnoDB 存储引擎中,事务提交过程中任何阶段,MySQL 突然奔溃,重启后都能保证事务的完整性,已提交的数据不会丢失,未提交完整的数据会自动进行回滚。这个能力称为 crash-safe,依赖的就是 redo log 和 undo log 两个日志。
比如:重启 innodb 时,首先会检查磁盘中数据页的 LSN ,如果数据页的 LSN 小于日志中 check point 的 LSN ,则会从 checkpoint 开始恢复。
掉电等故障恢复
redo log一旦提交意味着持久化了,但是有时候需要对其进行rollback操作,那就需要undo log
主从:写数据时只写主库,在读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行;
redo log 和 binlog 主要有三种不同:
逻辑日志:可以简单理解为记录的就是sql语句
物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更
如果不小心整个数据库的数据被删除了,能使用redo log文件恢复数据吗?
不可以使用redo log文件恢复,只能使用binlog文件恢复。
因为redo log文件是循环写,是会边写边擦除日志的,只记录未被刷入磁盘的数据的物理日志,已经刷入磁盘的数据都会从redo log文件里擦除;
binlog文件保存的是全量的日志,也就是保存了所有数据变更的情况,理论上只要记录在binlog上的数据,都可以恢复,所以如果不小心整个数据库的数据被删除了,得用binlog文件恢复数据。
了解了binlog、redolog两种日志的概念,再来看看执行器和 InnoDB 引擎在执行 update 语句时的流程:
整个过程如下图所示,其中橙色框表示是在 InnoDB 内部执行的,绿色框表示是在执行器中执行的:
在执行更新语句过程中,会记录redolog、binlog两块日志,以基本的事务为单位。
redolog在事务执行过程中可以不断写入,binlog只在提交事务时写入
为了解决日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案
从图中可以看出,在最后提交事务的时候,有3个步骤:
先写处于prepare状态的redo log,事务提交后,再写处于commit状态的redo log。由于redo log的提交分为prepare和commit两个阶段,所以称之为两阶段提交。
redo log(重做日志)让 InnoDB 存储引擎拥有了崩溃恢复能力;bin log(归档日志)保证了MySQL集群架构的数据一致性
为什么需要两阶段提交?
由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题(redo log与bin log两份日志之间的逻辑不一致,会出现什么问题)。
仍然用前面的 update 语句来做例子,update table set age=age+1 where id=2。假设当前 id=2 的行,字段 age 的值是 22,再假设执行update 语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?
再看一个场景,redo log 设置commit阶段发生异常,那会不会回滚事务呢?
并不会回滚事务,虽然redo log是处于prepare 阶段,但是能通过事务id找到对应的bin log日志,所以MySQL认为是完整的,就会提交事务恢复数据。
所以,如果不使用"两阶段提交",数据库的状态就有可能和用 binlog 恢复出来的不一致。
另外:sync_binlog 这个参数建议设置成 1,表示每次事务的binlog都持久化到磁盘,这样可以保证MySQL异常重启之后 binlog 不丢失。
MySQL主从复制的核心就是二进制日志binlog
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言,创建库、表)语句和 DML(数据操纵语言,增删改)语句,但不包括数据查询(SELECT、SHOW)语句。
实际上主从同步是基于binlog进行数据同步的。在主从复制过程中,会基于3个线程来操作,一个主库线程、两个从库线程
复制过程就是将binlog中的数据从主库传输到从库上,这个过程一般是异步的,即主库上执行事务操作的线程不会等待复制binlog的线程同步完成。主从复制概括为 :在Master端开启binlog ,从库将master的binlog拷贝到它的中继日志relay log,slave端重放binlog 从而达到主从数据一致
简单来说分成三步:
MySQL复制时异步且串行化的,重启后从接入点开始复制。
具体详细过程如下:
在完成主从复制之后,你就可以在写数据时只写主库、读数据时只读从库,这样即使写请求会锁表或者锁记录,也不会影响读请求的执行。
从库数量增加,从库连接上来的/O线程也比较多,主库也要创建同样多的log dump线程来处理复制的请求,对主库资源消耗比较高,同时还受限于主库的网络带宽。
所以在实际使用中,一个主库一般跟2~3个从库(1套数据库,1主2从1备主),这就是一主多从的MySQL集群结构。
主从同步要求
主从延迟原因
网络正常情况下,主从延迟的主要来源是备库接收完binlog和执行完这个事务之间的时间差
主从延迟的直接表现:从库消费中继日志的速度比主库生产binlog的速度慢
减少主从延迟的方案
数据一致性问题的解决
若操作的数据存储在同一个数据库中,那么对数据进行更新时,可对记录加写锁,这样在读取时就不会发生数据不一致的情况,但从库的作用仅为备份,未起到读写分离、分担主库读压力的作用
读写分离情况下,解决主从同步中数据不一致的问题,就是解决主从之间数据复制方式的问题。若按照数据一致性的从弱到强划分,有3种复制方式:异步复制、半同步复制、组复制
异步复制
半同步复制
组复制
异步复制、半同步复制都无法最终保证数据一致性问题
组复制技术,MRG(MySQL Group Replication),于MySQL在5.7.17推出的一种新的数据复制技术,基于Paxos协议的状态机复制
MGR如何工作?
将多个节点共同组成一个复制组,在执行读写(RW)事务时,需要通过一致性协议层同意,当同意节点数量大于(N/2+1)时才可进行提交,针对只读(RO)事务则不需要组内同意,直接COMMIT即可
在一个复制组内有多个节点组成,它们各自维护了自己的数据副本,并且在一致性协议层实现了原子消息和全局有序消息,从而保证组内数据一致性
参考:
MySQL面试题(最全、超详细)—— 定位慢查询、undo log与redo log
《MySQL》系列 - 十张图详解 MySQL 日志(建议收藏)
MySQL进阶(日志)——MySQL的日志 & bin log (归档日志) & 事务日志redo log(重做日志) & undo log(回滚日志)
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。