深入理解MySQL Binlog:从原理到实践
Posted Ryan.zheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解MySQL Binlog:从原理到实践相关的知识,希望对你有一定的参考价值。
binlog 介绍
记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。
作用:
- 复制:MySQL Replication在Master端开启binlog,Master把它的二进制日志传递给slaves并回放来达到master-slave数据一致的目的
- 数据恢复:通过mysqlbinlog工具恢复数据
存储格式:二进制
存储内容:
记录更改数据的语句,不存 select 和 show 等查询语句,查询语句可以通过 access.log 查看
影响:
开启binlog会使性能稍微变慢
管理 binlog
配置
开启binlog
修改 /etc/my.cnf
,在配置文件中加入 log-bin
配置,表示启用binlog,如果没有给定值,写成 log-bin=
,则默认名称为主机名。(注:名称若带有小数点,则只取第一个小数点前的部分作为名称)
[mysqld]
log-bin=my-binlog-name
也可以通过 SET SQL_LOG_BIN=1
命令来启用 binlog,通过 SET SQL_LOG_BIN=0
命令停用 binlog。启用 binlog 之后须重启MySQL才能生效。
ps:查看mysql进程指定的配置文件,如果没有则是读取的默认配置文件。查看msyql默认读取的my.cnf目录
mysql --help | grep \'my.cnf\'
顺序排前的优先
日志文件名
binlog日志文件有两种:
- 索引文件,后缀为 .index,用于记录所有有效的二进制文件
- 二进制文件,后缀为.0000*,记录数据库所有的DDL和DML事件
以下3种情况会创建一个新的日志文件:
- 重新启动mysql
- 刷新日志
- 日志文件大小达到 max_binlog_size
日志文件实际可能会比max_binlog_size大一点,因为事务是整体写入文件的,从不在文件之间拆分
日志内容
# 查看binlog记录的事件
show binlog events;
binlog是一个二进制文件集合,每个binlog文件以一个4字节的魔数开头,接着是一组Events:
- 魔数:0xfe62696e对应的是0xfebin;
- Event:每个Event包含header和data两个部分;header提供了Event的创建时间,哪个服务器等信息,data部分提供的是针对该Event的具体信息,如具体数据的修改;
- 第一个Event用于描述binlog文件的格式版本,这个格式就是event写入binlog文件的格式;
- 其余的Event按照第一个Event的格式版本写入;
- 最后一个Event用于说明下一个binlog文件;
- binlog的索引文件是一个文本文件,其中内容为当前的binlog文件列表
max_binlog_size
的最小值是4096字节,最大值和默认值是 1GB (1073741824字节)。事务被写入到binlog的一个块中,所以它不会在几个二进制日志之间被拆分。因此,如果你有很大的事务,为了保证事务的完整性,不可能做切换日志的动作,只能将该事务的日志都记录到当前日志文件中,直到事务结束,你可能会看到binlog文件大于 max_binlog_size 的情况。
日志格式
- STATEMENT:基于SQL语句的复制(statement-based replication, SBR),
MySQL 5.7.7
之前的默认格式 - ROW:基于行的复制(row-based replication, RBR),默认使用BASE64编码,需要解码,
MySQL 5.7.7
之后的默认格式 - MIXED:混合模式复制(mixed-based replication, MBR),推荐使用
STATEMENT格式可能会导致主从不一致,
例如使用以下函数的语句无法被正确复制:
* LOAD_FILE()
* UUID()
* USER()
* FOUND_ROWS()
* SYSDATE() (除非启动时启用了 –sysdate-is-now 选项)
修改日志格式,或set global binlog_format=ROW;
[mysqld]
binlog_format=ROW
Statement
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO, 提高了性能。
每一条会修改数据的sql都会记录在binlog中
缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行的时候相同的结果。另外mysql的复制,像一些特定函数的功能,slave与master要保持一致会有很多相关问题。
Row
优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以row的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题
5.1.5版本的MySQL才开始支持row level
的复制,它不记录sql语句上下文相关信息,仅保存哪条记录被修改。
缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。
Mixed
从5.1.8版本开始,MySQL提供了Mixed格式,实际上就是Statement与Row的结合。
在Mixed模式下,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。
mysqlbinlog 工具
由于binlog文件内容是二进制格式,可以使用官方提供的 mysqlbinlog 工具来查看
mysqlbinlog [options] log-files
# 查看bin-log二进制文件(带查询条件)
mysqlbinlog -vv --base64-output=decode-rows /var/lib/mysql/binlog.000001 \\
--start-datetime="2023-06-01 00:00:00" \\
--stop-datetime="2023-06-30 00:00:00" \\
--start-position="4" \\
--stop-position="500" \\
-d database_name
执行后,输出如下内容
截取一段来简单说明下:
# at 1653
#230605 21:04:05 server id 1 end_log_pos 1792 CRC32 0x287f5a5d Query thread_id=8 exec_time=183 error_code=0
SET TIMESTAMP=1685970245/*!*/;
insert into test2 values(1, \'ryan\', 1)
/*!*/;
上面输出包括信息:
- position: 位于文件中的位置,即第一行的(# at 1653),说明该事件记录从文件第1653个字节开始
- timestamp: 事件发生的时间戳,即第二行的(#230605 21:04:05 )
- server id: 服务器标识(1)
- end_log_pos 表示下一个事件开始的位置(即当前事件的结束位置+1)
- thread_id: 执行该事件的线程id (thread_id=8)
- CRC32 0x287f5a5d:CRC32是一种用于校验数据完整性的校验和算法。CRC32算法可以将数据转换为一个32位的校验和值,用于检测数据是否被篡改或损坏
- exec_time: 事件执行的花费时间
- error_code: 错误码,0意味着没有发生错误
- type:事件类型Query
实践
一般恢复逻辑,每天备份,根据具体的时间点找到对应的binlog,使用mysqlbinlog导出,然后剔除掉误操作的SQL,在新的库上进行重放,然后在替换线上的表。
日志产生的性能影响:
由于日志的记录带来的直接性能损耗就是数据库系统中最为昂贵的IO资源。
MySQL的日志主要包括错误日志(ErrorLog),二进制日志(Binlog),查询日志(QueryLog),慢查询日志(SlowQueryLog)等。
在默认情况下,系统仅仅打开错误日志,关闭了其他所有日志,以达到尽可能减少IO损耗提高系统性能的目的。
但是在一般稍微重要一点的实际应用场景中,都至少需要打开二进制日志,因为这是MySQL很多存储引擎进行增量备份的基础,也是MySQL实现复制的基本条件。
有时候为了进一步的mysql性能优化,定位执行较慢的SQL语句,很多系统也会打开慢查询日志来记录执行时间超过特定数值(由我们自行设置)的SQL语句。
一般情况下,在生产系统中很少有系统会打开查询日志。因为查询日志打开之后会将MySQL中执行的每一条Query都记录到日志中,会该系统带来比较大的IO负担,而带来的实际效益却并不是非常大。一般只有在开发测试环境中,为了定位某些功能具体使用了哪些SQL语句的时候,才会在短时间段内打开该日志来做相应的分析。
所以,在MySQL系统中,会对性能产生影响的MySQL日志(不包括各存储引擎自己的日志)主要就是Binlog了。
官方文档:
https://dev.mysql.com/doc/refman/5.7/en/binary-log.html
附:常用binlog操作命令
# 一、配置信息
# 是否启用binlog日志
show variables like \'log_bin\';
# 查看详细的日志配置信息
show global variables like \'%log%\';
# mysql数据存储目录
show variables like \'%dir%\';
# 查看binlog的目录
show global variables like "%log_bin%";
# 查看当前服务器使用的binlog文件及大小
show binary logs;
# 查看主服务器使用的binlog文件及大小
# 二、管理binlog
# 查看所有binlog的日志列表
show master logs;
# 查看最后一个binlog日志的编号名称及最后一个时间结束的位置pos
show master status;
# 刷新binlog,会生成一个新编号的binlog日志文件
flush log;
# 清空所有binlog日志(慎用)
reset master;
# 设置binlog文件保存事件,过期删除,单位天
set global expire_log_days=3;
# 删除指定日期前的日志索引中binlog日志文件
purge master logs before \'2019-03-09 14:00:00\';
# 删除指定日志文件
purge master logs to \'master.000003\';
# 删除slave的中继日志
reset slave;
# 三、事件查询命令
# 查看 binlog 内容
show binlog events;
# 查看具体一个binlog文件的内容 (in 后面为binlog的文件名)
show binlog events in \'master.000001\';
# IN \'log_name\' :指定要查询的binlog文件名(不指定就是第一个binlog文件)
# FROM pos :指定从哪个pos起始点开始查起(不指定就是从整个文件首个pos点开始算)
# LIMIT [offset,] :偏移量(不指定就是0)
# row_count :查询总条数(不指定就是所有行)
show binlog events [IN \'log_name\'] [FROM pos] [LIMIT [offset,] row_count];
my.cnf 配置
binlog_format = MIXED //binlog日志格式
log_bin =目录/mysql-bin.log //binlog日志名
expire_logs_days = 7 //binlog过期清理时间
max_binlog_size = 100m //binlog每个日志文件大小
binlog-do-db=需要备份的数据库名,如果备份多个数据库,重复设置这个选项即可
binlog-ignore-db=不需要备份的数据库苦命,如果备份多个数据库,重复设置这个选项即可
“binlog_cache_size”:在事务过程中容纳二进制日志SQL语句的缓存大小。二进制日志缓存是服务器支持事务存储引擎并且服务器启用了二进制日志(—log-bin选项)的前提下为每个客户端分配的内存,注意,是每个Client都可以分配设置大小的binlogcache空间。如果读者朋友的系统中经常会出现多语句事务的华,可以尝试增加该值的大小,以获得更有的性能。当然,我们可以通过MySQL的以下两个状态变量来判断当前的binlog_cache_size的状况:Binlog_cache_use和Binlog_cache_disk_use。
“max_binlog_cache_size”:和”binlog_cache_size”相对应,但是所代表的是binlog能够使用的最大cache内存大小。当我们执行多语句事务的时候,max_binlog_cache_size如果不够大的话,系统可能会报出“Multi-statementtransactionrequiredmorethan’max_binlog_cache_size’bytesofstorage”的错误。
“max_binlog_size”:Binlog日志最大值,一般来说设置为512M或者1G,但不能超过1G。该大小并不能非常严格控制Binlog大小,尤其是当到达Binlog比较靠近尾部而又遇到一个较大事务的时候,系统为了保证事务的完整性,不可能做切换日志的动作,只能将该事务的所有SQL都记录进入当前日志,直到该事务结束。这一点和Oracle的Redo日志有点不一样,因为Oracle的Redo日志所记录的是数据文件的物理位置的变化,而且里面同时记录了Redo和Undo相关的信息,所以同一个事务是否在一个日志中对Oracle来说并不关键。而MySQL在Binlog中所记录的是数据库逻辑变化信息,MySQL称之为Event,实际上就是带来数据库变化的DML之类的Query语句。
“sync_binlog”:这个参数是对于MySQL系统来说是至关重要的,他不仅影响到Binlog对MySQL所带来的性能损耗,而且还影响到MySQL中数据的完整性。对于“sync_binlog”参数的各种设置的说明如下:
sync_binlog=0,当事务提交之后,MySQL不做fsync之类的磁盘同步指令刷新binlog_cache中的信息到磁盘,而让Filesystem自行决定什么时候来做同步,或者cache满了之后才同步到磁盘。
sync_binlog=n,当每进行n次事务提交之后,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。
在MySQL中系统默认的设置是sync_binlog=0,也就是不做任何强制性的磁盘刷新指令,这时候的性能是最好的,但是风险也是最大的。因为一旦系统Crash,在binlog_cache中的所有binlog信息都会被丢失。而当设置为“1”的时候,是最安全但是性能损耗最大的设置。因为当设置为1的时候,即使系统Crash,也最多丢失binlog_cache中未完成的一个事务,对实际数据没有任何实质性影响。从以往经验和相关测试来看,对于高并发事务的系统来说,“sync_binlog”设置为0和设置为1的系统写入性能差距可能高达5倍甚至更多。
另:
MySQL的复制(Replication),实际上就是通过将Master端的Binlog通过利用IO线程通过网络复制到Slave端,然后再通过SQL线程解析Binlog中的日志再应用到数据库中来实现的。所以,Binlog量的大小对IO线程以及Msater和Slave端之间的网络都会产生直接的影响。
MySQL中Binlog的产生量是没办法改变的,只要我们的Query改变了数据库中的数据,那么就必须将该Query所对应的Event记录到Binlog中。那我们是不是就没有办法优化复制了呢?当然不是,在MySQL复制环境中,实际上是是有8个参数可以让我们控制需要复制或者需要忽略而不进行复制的DB或者Table的,分别为:
Binlog_Do_DB:设定哪些数据库(Schema)需要记录Binlog;
Binlog_Ignore_DB:设定哪些数据库(Schema)不要记录Binlog;
Replicate_Do_DB:设定需要复制的数据库(Schema),多个DB用逗号(“,”)分隔;
Replicate_Ignore_DB:设定可以忽略的数据库(Schema);
Replicate_Do_Table:设定需要复制的Table;
Replicate_Ignore_Table:设定可以忽略的Table;
Replicate_Wild_Do_Table:功能同Replicate_Do_Table,但可以带通配符来进行设置;
Replicate_Wild_Ignore_Table:功能同Replicate_Ignore_Table,可带通配符设置;
通过上面这八个参数,我们就可以非常方便按照实际需求,控制从Master端到Slave端的Binlog量尽可能的少,从而减小Master端到Slave端的网络流量,减少IO线程的IO量,还能减少SQL线程的解析与应用SQL的数量,最终达到改善Slave上的数据延时问题。
实际上,上面这八个参数中的前面两个是设置在Master端的,而后面六个参数则是设置在Slave端的。虽然前面两个参数和后面六个参数在功能上并没有非常直接的关系,但是对于优化MySQL的Replication来说都可以启到相似的功能。当然也有一定的区别,其主要区别如下:
如果在Master端设置前面两个参数,不仅仅会让Master端的Binlog记录所带来的IO量减少,还会让Master端的IO线程就可以减少Binlog的读取量,传递给Slave端的IO线程的Binlog量自然就会较少。这样做的好处是可以减少网络IO,减少Slave端IO线程的IO量,减少Slave端的SQL线程的工作量,从而最大幅度的优化复制性能。当然,在Master端设置也存在一定的弊端,因为MySQL的判断是否需要复制某个Event不是根据产生该Event的Query所更改的数据
所在的DB,而是根据执行Query时刻所在的默认Schema,也就是我们登录时候指定的DB或者运行“USEDATABASE”中所指定的DB。只有当前默认DB和配置中所设定的DB完全吻合的时候IO线程才会将该Event读取给Slave的IO线程。所以如果在系统中出现在默认DB和设定需要复制的DB不一样的情况下改变了需要复制的DB中某个Table的数据的时候,该Event是不会被复制到Slave中去的,这样就会造成Slave端的数据和Master的数据不一致的情况出现。同样,如果在默认Schema下更改了不需要复制的Schema中的数据,则会被复制到Slave端,当Slave端并没有该Schema的时候,则会造成复制出错而停止。
而如果是在Slave端设置后面的六个参数,在性能优化方面可能比在Master端要稍微逊色一点,因为不管是需要还是不需要复制的Event都被会被IO线程读取到Slave端,这样不仅仅增加了网络IO量,也给Slave端的IO线程增加了RelayLog的写入量。但是仍然可以减少Slave的SQL线程在Slave端的日志应用量。虽然性能方面稍有逊色,但是在Slave端设置复制过滤机制,可以保证不会出现因为默认Schema的问题而造成Slave和Master数据不一致或者复制出错的问题。
MySQL进阶-10深入理解redolog,undolog和binlog的底层原理
MySql系列整体栏目
深入理解redolog,undolog和binlog的底层原理和关系
一, 深入理解Redolog日志底层原理
再看本篇文章之前,可以结合【9】深入理解mysql执行的底层机制 这篇文章来了解mysql内部执行sql的过程。
1,innodb引擎底层事务原理
事务的四大特性主要是是acid,分别是原子性、一致性、隔离性和持久性。其原子性是 通过这个undolog 来保证的,持久性是 通过redolog 来实现的,隔离性是通过事务的 读写锁+mvcc机制 来实现的。在这四大特性中,一致性C是最重要的,他是最终目的,只有其他三个实现了,才有可能实现这个一致性,即 一致性是最终目的,其他三个就是实现的手段 。
1.1,WAL
在事务的具体实现的机制中,mysql是通过这个WAL(Write-ahead logging),即预写日志的方式来实现的。
举个基本的例子,假设一个转钱的操作,如果A给B转钱,此时A减了100,而在B在加100的时候,突然断电了,那么在数据库重启的时候,就得恢复他断电前的状态,此时就需要一个日志来记录他事务操作前的状态了,通过这个日志中的记录来恢复之前的状态,判断其是否需要回滚操作,还是需要重做等操作。而实现这个WBL机制,主要是通过这个redolog和undolog这两种日志的方式实现。
mysql中除了使用这种主流的WAL机制之外,还会使用一些Commit Logging丶Shadow Paging等。mysql默认使用这个WAL机制。
2,redolog日志文件
2.1,为什么要redolog日志文件
依旧拿一个更新语句举例,如下图,redolog是发生在第4步和第5步,在数据在buffer pool中进行数据更新之后,会把具体的逻辑语句,如哪一页哪一行要更新的字段以及更新的值是啥,先加入到redolog buffer中,随后再然后通过追加的方式持久化到磁盘上的redolog文件中,再不考虑server层的操作,即跳过6,7步,那么就会启动一个后台线程进行一个落盘的操作。
这里假设innodb内部没有这个redolog这个日志文件,就是在数据更新之后直接从buffer pool将数据给落盘,如果在落盘的时候数据页没有发生损坏,那么双写机制就触发不了,就没有换页的操作,此时发生断电,那么还没来的及落盘的数据就会直接丢失了,这就会影响数据的安全性和持久化。
除了断电这个问题,还有可能因为一个小小的更新语句,就直接将整页数据给进行落盘操作,这样就很浪费成本;或者遇到一个sql很复杂,需要涉及的表很多,并且数据量很大,那么就需要更改很多页目录的数据,如果这些页目录在磁盘上的分布是随机的,那么在刷盘写数据的时候,就会产生大量的随机io,因此也很影响内部的执行效率和浪费空间。
所以为了解决数据的安全性以及innodb的执行效率,就引入了这个redolog日志文件,就是在落盘时先写一份到redolog中,并且写数据是顺序写,在redolog写成功之后,再进行落盘操作,如果出现断电问题,就用这个redolog这个文件进行数据的重做。
2.2,redolog的内部结构
redolog主要是记录了哪些数据做了什么修改,因此这个redolog有一个通用的格式,如下图:
key | value |
---|---|
type | 就是值数据的类型,类型总共有53种 |
Space ID | 表空间id,即修改的对应表空间的id |
page Number | 当前页的页号 |
data | 要修改的内容 |
2.3,redolog的刷盘时机
在innodb的体系结构中,可以发现在左边的内存结构中是有一个Log Buffer的缓冲区的,该缓冲区就是对应的redolog缓冲区,在该缓冲区中,其默认大小为16M,并且内部分成了多个小块,每一块的大小空间为512kb。在数据写入这个redolog的缓冲区的时候,会从头到尾的以追加的方式将数据写入这些块中,如果后面的块快写满,那么会将前面头部的块的数据删掉,然后再从头到尾开始存储数据,磁盘上面的redolog页结构和这个一样。
而这个缓冲区刷盘的情况有好几种,如下:
1,log buffer空间不足时,虽然说redolog缓冲区数据存满会将前面的数据删除再重新存储,但是删除之前也是要考虑数据的持久化的,innodb内部认为,当数据量达到redolog缓冲区空间的一半大小时,就需要强制的进行一次刷盘操作,从而把日志落盘到磁盘上。
2,事务提交时,在事务提交时,也是需要将缓冲区中对应的那些日志数据刷新到磁盘的
3,后台默认线程,后台也是会启动一个默认的线程进行一个刷盘操作的,如每一秒刷新一次
4,服务器手动关闭,在服务器关闭时,也是需要将未刷盘的数据全部的进行一次刷盘操作。
2.4,Log Sequence Number
简称LSN,顾名思义,这个就是一个日志序列号。和undolog一样,在执行一条更新或者插入语句的时候,就会生成一个事务id,这里的序列号和undolog的事务id的作用是一样的,主要是是保证数据的唯一性。该值是一个自增的序列号,初始大小为8704,后面每增加一条数据,其对应的序列号的值就会加1。即LSN的值越小,那么其事务产生的越早。
除了这个日志之外,innodb内部还有其他的LSN用来记录已经刷盘的事务id。可以通过具体的命令来查看系统中各种LSN的值
SHOW ENGINE INNODB STATUS\\G
key | value |
---|---|
Log sequence number | 已经写入redolog buffer的日志量(累加值) |
Log flushed up to | 已经写入磁盘的redo日志量 |
2.5,innodb_flush_log_at_trx_commit
在数据进行刷盘的时候,数据也不是直接的从redolog缓冲区将数据直接刷到磁盘内部,而是需要调用操作系统,通过操作系统进行一个落盘的操作。在操作系统中,会有一个操作系统的缓冲区,数据会先在缓冲区中存放,然后再通过操作系统的调度将数据进行一个落盘的操作。
这个系统变量的名称就是innodb_flush_log_at_trx_commit,可以提供给外部调用,如kafka,mysql都对这个变量进行了调用。这个变量总共有三个参数,分别是0、1、2,不同的参数代表着不同的含义:
该值为0时,表示在事务提交时不立即向磁盘中同步redo日志,这个任务是交给后台线程做的。
该值为1时,表示在事务提交时需要将redo日志同步到磁盘,可以保证事务的持久性,这个是mysql设置的系统默认值,当事务提交时,会进行一个强制刷盘的操作。
该值为2时,表示在事务提交时需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘。如果数据还在操作系统缓存中突然断电,那么未落盘的数据也会丢失。
3,undolog日志
上面主要了解redolog日志,redolog主要是为了保证这个数据的持久性,ACID中的D就是靠这个redolog来保证的。而接下来要讨论的undolog日志,undolog日志主要是为了保证数据的原子性,在mvcc那篇https://blog.csdn.net/zhenghuishengq/article/details/127889365,中也详细的分析了这个undolog,也详细的描述了undolog日志版本链路和readview结合来保证事务的隔离性,接下来再对这个undolog分析一波。
3.1,undolog回滚的方式
undolog日志中主要是记录被修改或者新增的值的记录,当需要回滚时,则将这个存储undolog的值用来回滚。其回滚情况主要有如下几种:
1,当插入一条记录时,需要把这条记录的主键值记下来,回滚的时候只需要把这个主键值对应的记录删掉。
2,当删除了一条记录,需要要把这条记录中的内容都记下来,回滚时再把由这些内容组成的记录插入到表中。
3,当修改了一条记录,需要要把修改这条记录前的旧值都记录下来,回滚时再把这条记录更新为旧值。
3.2,undolog事务id形成机制
在mysql中,主要有只读事务和读写事务。在只读事务中,普通的表是不能进行增删改操作的,而临时的表是可以进行增删改的;而读写事务中,是都可以进行增删改操作的。同时在只读事务中,由于临时表可以进行增删改,因此只读事务的临时表中也是可以产生这个事务id的。
undolog产生的事务id和这个LSN这个序列号是一样的,也是一个自增的全局变量id,在innodb中,每一行数据都会有一个存储这个事务id的地方,在之前的行格式中也谈到过,这些隐藏聚簇索引id,事务id,回滚指针等都存储在这些行格式,中。如下图中的row_id,trx_id,roll_ptr等
3.3,插入操作
在数据进行插入的操作时,如果此时表中有着二级索引,那么此时除了向主键索引所在的B+树中存放整行数据,还要向二级索引中存放对应的列以及主键id这个字段。如果出现数据回滚的情况,那么不仅要删除主键索引中的数据,也要删除二级索引中对应的数据,但是这个id即存在一级索引中,也存在二级索引中,因此undolog在记录这个插入操作的数据的时候,直接将该行数据的主键id值存放在这个undolog的版本链路中,如果出现回滚的话,是那么直接通过id删除,就能把数据全部回滚回去了。
3.4,删除操作
在数据进行删除时, 会需要用到行格式中的一个delete_mask 字段,这个字段在记录头信息里面,如果该字段为0,则表示没有删除,该字段为1,表示已经删除。
在innodb中,为了保证mvcc的多版本并发控制机制,redolog在删除时引入了一个中间状态,即在删除时先引用逻辑删除,将delete_mask这个字段将值变为1,随后在事务提交之后,再通过后台启动一个线程,通过轮询方式去收集这个字段为1的那些行数据,然后再将值给删除。
由于所有的数据都是通过双向链表的方式串在一起,因此在删除的时候,只需要将链表的前驱和后继修改即可,并且会将这个需要删除的数据,挂载这个Page Free页的上面。Page Free页面上的数据表示该空间上面的页面可以被重复利用。
3.5,更新操作
在update方面,就会相对的比新增和删除两种方式更加的麻烦一些,主要分为更新主键和不更新主键。
3.5.1,不更新主键
在不更新主键时,又分为就地更新和物理删除再插入。如果在对某个字段做更新时,其要更新的值的长度不变,那么就会直接选择就地更新,如原来的值为"张三",现在将值改为"李四",改变的值的长度不变,那么就不会影响整页数据的结构,其选择就地更新;如果更新值的长度和原来的长度不一样,如原来是一个"张三",现在变成了"张三丰",他的值很明显不一样,那么就会选择先删除旧记录,再插入新的记录。换句话说,就是看空间大小是否发生改变,不变则就地更新,变则先删除后插入。
3.5.2,更新主键
更新主键操作和删除操作有点类似,为了保证mvcc机制,也是通过记录头信息中的delete mask字段来作为一个中间状态,即将这个字段的值改为1,然后再事务提交之后再将这条数据删除。删除之后再新增一条数据,根据新的主键值在B+树中找到新的定位,然后将数据新增到此地。就是更新主键可以看成是两步操作,先delete操作,再insert操作,因此在更新主键时,就会产生两条undo日志。
4,事务的整体执行流程
通过上面的redolog和undolog的可知,redolog是为了保证事务的持久性,他具有重做的功能,undolog是为了保证事务的原子性,他具有撤回的功能。因此在开启一个事务时,可以总结一下各个日志中的作用已经整个事务的执行流程。
1,以一个更新语句为例,在开启事务之后,会将磁盘中要更新的数据以页的方式加载到buffer pool缓冲区中
2,在执行更新语句时,会通过undolog记录被修改的值。在innodb中,为了保证undolog日志本身的持久性,因此也会通过一个redolog日志来记录里面新增的记录,如undolog的版本控制链路新增一个结点时,redolog就会记录在这条链表的什么位置加入了哪个结点,记录完之后将记录加入到Redolog的缓冲区中,再通过一定的方式进行刷盘持久化,所以在整个事务的过程中,redolog比undolog先刷盘。随后将要改变的值加入到buffer pool中
3,在buffer pool中执行sql的更新,将要更新逻辑语句也加入到Redolog缓冲区中,此时也会有一个redolog刷盘操作。
4, 在提交事务时,也会让对应的redolog日志记录进行一个刷盘操作。
5,随后undolog会进行刷盘操作,buffer pool中的数据也会通过后台的线程进行一个数据刷盘的操作。
6,在undolog日志和redolog日志,数据页全部刷盘完成之后,事务执行完毕。
5,redolog和binlog的关系
从下图来看,redolog是属于引擎层的,即属于innodb存储引擎特有的,而binlog是属于server层的,即所有的引擎层共有,innodb有,MyIsam也有。从整个流程来看,binlog和redolog都是对一份数据进行的存储,并且在存储过程中,需要通过事务标记来保证两个日志中记录的值一样。
5.1,为什么用redolog恢复数据而不用binlog
1,binlog 会记录表中所有更改操作,包括更新删除数据,更改表结构等等,主要用于人工恢复数据,如开了binlog删库也不用跑路;而redolog主要是mysql内部使用的,在数据库突然崩溃mysql内部会自动的通过这个redolog进行一个重做的操作。
2,redolog是Innodb引擎层特有的,binlog是Server层实现的
3,redolog记录的是物理日志,如在哪一页哪一行哪个字段做了什么修改,其效率更高;binlog就是原始的逻辑,和原来的sql差不多
4,redolog默认大小为48M,其内存是有限的,因此其内部是通过循环写的方式去保存数据,并且其内部只记录为刷盘的数据,已刷盘的数据会自动的从这个redolog中删除;binlog采用的是追加写,所有的记录都会保存。而在恢复数据的时候只需要恢复这部分未刷盘的即可,不需要全部就行一个对比再操作。
5,如果再一个事务中出现两条相同的sql,如set age = age + 1 where id = 2,如果binlog一条刷盘成功一条刷盘失败,那么他是不能区分这两条是有没有全部刷盘成功或者失败的,换句话说就是由于已经有一条成功了,那么就不能保证是第一条成功了还是第二条成功了,因此不管是全部恢复还是全部不恢复,内部的数据肯定不对;而redolog就不一样了,只需要把redolog内部未刷盘的进行一个刷盘操作就好了。
5.2,binlog和redolog如何保证数据的一致性
mysql中主要使用2pc,两阶段提交来保证数据的一致性。第一阶段就是先做一个准备工作,开启一个事务提交器,让所有的资源准备好,然后会去收集所有的资源状态,当所有资源的状态都准备好了之后,再进入第二阶段,发出一个commit的命令,所有的资源进行一个commit提交。
以上是关于深入理解MySQL Binlog:从原理到实践的主要内容,如果未能解决你的问题,请参考以下文章