笔记整理自 【宋红康】MySQL数据库(mysql安装/基础/高级/优化),并从《MySQL实战45讲》作为补充
事务有4种特性:原子性、一致性、隔离性和持久性。那么事务的四种特性到底是基于什么机制实现呢?
1、思路1
在事务提交完成之前把该事务所修改的所有页面都刷新 到磁盘
缺点:
2、思路2
把 修改 了哪些东西 记录一下 即可。
redo 日志本质上只是记录了一下事务对数据库进行了哪些修改
比如,某个事务将系统 表空间中 第10号 页面中偏移量为 100 处的那个字节的值 1 改成 2 。我们只需要记录一下:将第0号表 空间的10号页面的偏移量为100处的值更新为 2 。
这样在事务提交时 就会把产述内容刷新到磁盘中。即使之后系统崩溃了,重启之后只要 按照上述内容所记录的步骤重新更新一下数据页 那么该事务对数据库中所做的修改就可以被恢复出来
优点:
redo日志降低了刷盘频率
redo日志占用的空间非常小
redo日志是顺序写入磁盘的
保证原子性的操作时,必须以组的形式来记录 redo 日志。例如向某个索引对应的 插入一条记录的过程必须是原子的 ,不能说插了一半之后就停止了。
某个需要保证原子性的操作所产生 一系 redo 日志,必须以一条类型为 MLOG_MULTl _REC_END redo 日志结尾,如图所示
只有解析到类型为 MLOG_MULTl_REC_END redo 日志时,才认为解析到了一组完整的 redo 才会进行恢复
如果 type 字段的第个比特为1代表这个需要保证原子性的操作只产生了一条单一的 redo 日志
把对底层页面进行一次原子访问的过程称为一个 Mini-Transaction (MTR )。一个MTR可以包含一组redo日志,在进行崩溃恢复时,需要把这一组redo日志作为一个不可分割的整体来处理。
一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条 redo日志
重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的
参数设置:innodb_log_buffer_size
重做日志文件 (redo log file) ,保存在硬盘中,是持久的。
为了更好地管理redo日志,把通过 MTR 生成的日志都放在了大 小为 512 字节的页(block)中。
里面包含的具体字段:
log block header中的属性:
log block trailer的属性:
向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的写入并不是直接写入磁盘的,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日志文件不止一个,而是以一个日志文件组 的形式出现的.这些文件以 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的概念。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得 停下来,清空一些记录,把 checkpoint 推进一下
以一个更新事务为例,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。
MySQL整体来看,其实就有两块:一块是Server层,它主要做的是MySQL功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。
redo log是InnoDB引擎特有的日志,而Server层也有自己的日志,称为binlog(归档日志)。
接下来我们对更新流程进行分析:
create table T(ID int primary key, c int);
update T set c=c+1 where ID=2;
将redo log的写入拆成了两个步骤:prepare和commit,这就是"两阶段提交"。
为了保证数据库的状态就有和用它的日志恢复出来的库的状态一致,简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
当在2之前崩溃时
重启恢复:后发现没有commit,回滚。
备份恢复:没有binlog 。一致
当在3之前崩溃
重启恢复:虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。备份:有binlog。一致