🏳️🌈个人网站:code宝藏 👈,欢迎访问🎉🎉
🙏如果大家觉得博主写的还不错的话,可以点点关注,及时获取我的最新文章
🤝非常感谢大家的支持与点赞👍
📚 笔记整理自 【宋红康】MySQL数据库(mysql安装/基础/高级/优化),并从《MySQL实战45讲》作为补充
文章目录
事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?
- 事务的隔离性由 锁机制 实现。 而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。
- REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持 久性。
- UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
redo日志
如何保证持久性?
1、思路1
在事务提交完成之前把该事务所修改的所有页面都刷新 到磁盘
缺点:
- 刷新一个完整的数据页太浪费了
- 随机10刷新起来比较慢。
2、思路2
把 修改 了哪些东西 记录一下 即可。
redo 日志本质上只是记录了一下事务对数据库进行了哪些修改
比如,某个事务将系统 表空间中 第10号 页面中偏移量为 100 处的那个字节的值 1 改成 2 。我们只需要记录一下:将第0号表 空间的10号页面的偏移量为100处的值更新为 2 。
这样在事务提交时 就会把产述内容刷新到磁盘中。即使之后系统崩溃了,重启之后只要 按照上述内容所记录的步骤重新更新一下数据页 那么该事务对数据库中所做的修改就可以被恢复出来
优点:
-
redo日志降低了刷盘频率
-
redo日志占用的空间非常小
-
redo日志是顺序写入磁盘的
redo日志格式
- type:这条redo日志的类型。
在MySQL 5.7.22版本中,设计InnoDB的大叔一共为redo日志设计了53种不同的类型。 - space ID:表空间ID
- page number:页号。
- data:这条redo日志的具体内容。
Mini-Transaction
以组的形式写入redo日志
保证原子性的操作时,必须以组的形式来记录 redo 日志。例如向某个索引对应的 插入一条记录的过程必须是原子的 ,不能说插了一半之后就停止了。
如何划分组?
- 一系列日志
某个需要保证原子性的操作所产生 一系 redo 日志,必须以一条类型为 MLOG_MULTl _REC_END redo 日志结尾,如图所示
只有解析到类型为 MLOG_MULTl_REC_END redo 日志时,才认为解析到了一组完整的 redo 才会进行恢复
- 一条日志
如果 type 字段的第个比特为1代表这个需要保证原子性的操作只产生了一条单一的 redo 日志
Mini-Transaction概念
把对底层页面进行一次原子访问的过程称为一个 Mini-Transaction (MTR )。一个MTR可以包含一组redo日志,在进行崩溃恢复时,需要把这一组redo日志作为一个不可分割的整体来处理。
一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条 redo日志
redo日志的写入redo buffer过程
redo的组成
-
重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的
参数设置:innodb_log_buffer_size
-
重做日志文件 (redo log file) ,保存在硬盘中,是持久的。
redo log block
为了更好地管理redo日志,把通过 MTR 生成的日志都放在了大 小为 512 字节的页(block)中。
里面包含的具体字段:
log block header中的属性:
- LOG_BLOCK_HDR NO:每一个block都有一个大于0的唯一编号,该属性就表示该编号值。
- LOG_BLOCK_HDR_DATA_LEN:表示block中已经使用了多少字节
- LOG_BLOCK_FIRST_REC_GROUP:一条redo日志也可以称为一条redo日志记录(redo log record),一个MTR会生成多条redo日志记录,这个MTR生成的这些redo日志记录被称为一个redo日志记录组(redo log record group)
- LOG_BLOCK_ CHECKPOINT_NO:表示checkpoint的序号;
log block trailer的属性:
- LOG_BLOCK_CHECKSUM:表示该block的校验值,用于正确性校验
redo 日志写入log buffer
向log buffer中写入redo日志的过程是顺序写入的。使用buf_free全局变量指明后续写入的redo日志应该写到log buffer中的哪个位置
现在假设有名为 T1、T2 的两个事务 每个事务都 包含 MTR,这几个 MTR 名字如下·
-
事务T1的两个 MTR 分别称为mtr_t1_1和mtr_t1_2
-
事务T2的两个 MTR 分别称为mtr_t2_1和mtr_t2_2
每个mtr都会产生一组redo日志,用示意图来描述一下这些mtr产生的日志情况:
不同的事务可能是 并发 执行的,所以 T1 、 T2 之间的 MTR 可能是 交替执行 的。每当 MTR 执行完成时,伴随该生成的一组日志就需要被复制到 log uffe 中。
redo log的刷盘策略
redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以 一 定的频率 刷入到真正的redo log file 中。
注意,redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存 (page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系 统自己来决定(比如page cache足够大了)。
那么对于InnoDB来说就存在一个问题,如果交给系统来同 步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务 时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
-
设置为0 :表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日 志的同步)
-
设置为1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值 )
-
设置为2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自 己决定什么时候同步到磁盘文件。
redo log file
1、 相关参数设置
- innodb_log_group_home_dir :指定 redo log 文件组所在的路径,默认值为 ./ ,表示在数据库 的数据目录下。
- innodb_log_files_in_group:指明redo log file的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认2个,最大100个。
- innodb_flush_log_at_trx_commit:控制 redo log 刷新到磁盘的策略,默认为1。
- innodb_log_file_size:单个 redo log 文件设置大小,默认值为 48M 。最大值为512G,注意最大值 指的是整个 redo log 系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大 于最大值512G。
2、日志文件组
磁盘上的redo日志文件不止一个,而是以一个日志文件组 的形式出现的.这些文件以 ib_logfile[ 数字 ]" (数字可 2…) 的形式进行命名。
在将redo日志写入日志文件组时,从ib_logfile0开始写起;如果ib_logfile0写满了,就接着ib_logfile1写。如果写到最后一个文件,重新转到ib_logfile0继续写
总共的redo日志文件大小其实就是: innodb_log_file_size × innodb_log_files_in_group 。 采用循环使用的方式向redo日志文件组里写数据的话,会导致后写入的redo日志覆盖掉前边写的redo日 志?当然!所以InnoDB的设计者提出了checkpoint的概念。
3、 checkpoint
- write pos是当前记录的位置,一边写一边后移,写到文件末尾后就回到文件开头
- checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得 停下来,清空一些记录,把 checkpoint 推进一下
redo的整体流程
以一个更新事务为例,redo log 流转过程,如下图所示:
第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加 写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
这其实就是Write-Ahead机制
Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。
有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
binlog日志
概念
MySQL整体来看,其实就有两块:一块是Server层,它主要做的是MySQL功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。
redo log是InnoDB引擎特有的日志,而Server层也有自己的日志,称为binlog(归档日志)。
redo log与binlog区别
- redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
- redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
- redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
整体流程分析
接下来我们对更新流程进行分析:
create table T(ID int primary key, c int);
update T set c=c+1 where ID=2;
- 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
将redo log的写入拆成了两个步骤:prepare和commit,这就是"两阶段提交"。
为什么要有两阶段提交?
为了保证数据库的状态就有和用它的日志恢复出来的库的状态一致,简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
-
当在2之前崩溃时
重启恢复:后发现没有commit,回滚。备份恢复:没有binlog 。一致
-
当在3之前崩溃
重启恢复:虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。备份:有binlog。一致