undo log
回滚日志:Innodb引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCCredo log
重做日志:Innodb引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复bin log
归档日志:Server层生成的日志,只要用于数据备份和主从复制
为什么需要undo log
两大作用:
- 实现事务回滚,保证事务的原子性
- 实现MVCC关键因素之一
undo log是一种用于撤销回退的日志。在事务没提交前,MySQL会记录更新前的数据到undo log日志文件里面,当事务回滚时,可以利用undo log回滚
- 在插入一条记录时,要把这条记录的主键值记录下来,回滚时只需要把对应的记录删掉
- 在删除一条记录时,要把这条记录中的内容都记录下来,回滚之后把这些内容重新插入到表中
- 在更新一条记录时,要把被更新的旧值记录下来,回滚之后再把这些列更新为旧值
一条记录的每次更新操作产生的undo log格式都有一个roll_pointer指针和一个trx_id事务id
- 通过trx_id可以知道该记录是被哪个事务修改的
- 通过roll_pointer指针可i将这些undo log串成一个链表,这些链表就被称为版本链
为什么需要Buffer Pool
MySQL的数据是存在磁盘中的,我们更新一条记录时,需要先从磁盘中读取该记录,然后在内存中修改着这条记录,修改完放入一个缓冲池Buffer Pool,以减少磁盘的I/O
有了Buffer Pool之后
- 当读取数据时,如果数据存在于Buffer Pool中,客户端就会直接读取Buffer Pool中的数据,否则再去磁盘中读取
- 当修改数据时,如果数据存在于Buffer Pool中,那就直接修改Buffer Pool中数据所在的页,然后将这个页设置为脏页(该页的内存数据和磁盘数据已经不一致),为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘
为什么需要redo log
两大作用
- 实现事物的持久性,让MySQL有crash-safe的能力
- 将写操作从随机写变成顺序写,提升了MySQL写入磁盘的能力
Buffer Pool能提到读写效率,但是Buffer Pool是基于内存的,而内存总是不可靠的,万一断电重启,还没来得及落盘的脏数据页就会丢失
为了防止数据丢失的问题,当有一条记录需要更新时,InnDB引擎会先更新内存(同时标记为脏页),然后将本次对这个页的修改以redo log的形式记录下来
后续,InnoDB引擎会在适当的时机,由后台线程将Buffer Pool中的脏页刷新到磁盘中,这就是WAL(Write-Ahead Logging)技术
redo log是物理日志,记录某个数据页做了什么修改,比如对XXX表空间中YYY数据页ZZZ偏移量的地方做了AAA更新,每当执行一个事物就会产生这样的一条或者多条物理日志
在提交事物时,只要先将redo log持久化磁盘即可,不需要等到将Buffer Pool中的脏页持久化到磁盘
当系统崩溃时,虽然脏页数据没有持久化,但是redo log已经持久化,MySQL重启之后可以根据redo log的内容,将所有数据恢复到最新状态
redo log和undo log的区别
- redo log记录了此次事物「完成后」的数据状态,记录的是更新之后的值
- undo log记录了此次事物「开始前」的数据状态,记录的是更新之前的值
redo log要写到磁盘,数据也要写到磁盘,为什么要多此一举?
写入redo log是追加写,而写入数据要先找到写入位置,然后才能写入到磁盘,是随机写。
磁盘的追加写比随机写高效的多,因此redo log写入磁盘的开销更小
产生的redo log是直接写入磁盘吗?
不是的,redo log也有自己的缓存——redo log buffer,每当产生一条redo log时,会先写入到redo log buffer,后续再持久化到硬盘
redo log刷盘时机
- MySQL正常关闭时
- 当redo log buffer中记录的写入量大于redo log buffer内存空间的一半时,会触发落盘
- InnoDB的后台线程每隔1秒,将redo log buffer持久化到磁盘
innodb_flush_log_at_trx_commit
参数
- 0,表示事务提交时不会主动出发写入磁盘操作
- 1,表示事务提交时,直接将redo log buffer里的redo log持久化到磁盘,保证数据不丢失,默认值
- 2,表示事务提交时,都只是将缓存再redo log buffer里的redo log写到redo log文件,**并不意味着写入到磁盘,**相对是一个折中的方案,只要操作系统不宕机,即使数据库崩溃了,也不会丢失数据。
redo log文件写满了怎么办?
默认情况下,InnoDB存储引擎有一个重做日志组redo log group,重做日志组由两个redo log文件组成,这两个redo log日志的文件名叫:ib_logfile0
和ib_logfile1
重做日志文件组是以循环写的方式工作的,从头开始写,写到末尾又从头开始,相当于一个环形
redo log是为了防止Buffer Pool中脏页丢失而设计的,随着系统的运行,Buffer Pool的脏页刷新到了磁盘中,那么redo log对应的记录也就没用了,这时候我们擦除这些旧记录,以腾出空间记录新的更新操作
redo log 是循环写的方式,相当于一个环形,InnoDB 用 write pos 表示 redo log 当前记录写到的位置,用 checkpoint 表示当前要擦除的位置,如下图:
图中的:
- write pos和checkpoint的移动方向都是顺时针方向
- write pos ~ checkpoint红色部分,用来记录新的更新操作
- checkpoint ~ write pos蓝色部分,待落盘的脏数据页记录
如果write pos追上了checkpoint,就意味着redo log文件满了,这时MySQL不能再执行新的更新操作,也就是说MySQL会被阻塞,此时会将Buffer Pool中的脏数据页刷新到磁盘。
为什么需要bin log
bin log由Server层产生,事务提交的时候,会_将该事务执行过程中产生的所有bin log统一写入bin log文件_
bin log文件记录了所有数据库表结构和表数据的修改日志,不会记录查询类的操作,比如SELECT
和SHOW
操作
为什么有了bin log还要有redo log?
这个问题跟 MySQL 的时间线有关系。
最开始 MySQL 里并没有 InnoDB 引擎,MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。
而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用 redo log 来实现 crash-safe 能力。
bin log和redo log区别
- 适用对象不同
- bin log由Server产生,所有引擎都可以使用
- redo log是InnoDB引擎产生的
- 文件格式不同
- bin log有3种文件格式,分别是Statement(默认)、Row、Mixed
Statement
:修改数据的SQL会被记录到bin log中,主从复制中slave端根据SQL语句重现。但Statement有动态函数的问题,可能会导致数据的不一致。Row
:记录行数据最终被修改成什么样了,缺点是每行数据的变化结构都会被记录,比如执行批量update语句,更新多少行数据就会产生多少条记录,使bin log过大。Mixed
:包含了Statement
和Row
模式。
- redo log是物理日志,记录的是在某个数据页做了什么修改,比如对XXX表空间的YYY数据页ZZZ偏移量的地方做了AAA的修改。
- bin log有3种文件格式,分别是Statement(默认)、Row、Mixed
- 写入方式不同
- bin log是追加写,写满一个文件就创建一个新的继续写,不会覆盖以前的日志,保存的是全量的日志。
- redo log是循环写,日志空间大小是固定的。
- 用途不同
- bin log用于备份恢复、主从复制
- redo log用于掉电等故障恢复
如果不小心整个数据库的数据被删除了,能使用redo log文件恢复数据吗?
不可以使用redo log恢复,只能使用bin log恢复
因为redo log文件是循环写,边写边擦除,bin log保存的是全量的日志
主从复制是怎样实现的?
MySQL主从复制依赖于bin log,复制的过程就是将bin log的数据从主库传输到从库上。
这个过程一般是异步的,也就是主库执行事务的线程不会等待复制bin log的线程同步完成
- MySQL主库在收到客户端请求后,会先写入bin log,再提交事务。
- 从库创建一个专门的I/O线程,连接主库的log dump线程,来接受主库的bin log日志,再把bin log信息写入relay log的中继日志里,返回给主库复制成功的相应。
- 从库创建一个用于回放bin log的线程,读取中继日志的内容,最终实现主从的一致性。
主从复制的三种模型:
- 同步复制:主库提交事务的线程要等待所有从库的响应,才返回客户端结果。
- 性能很差
- 可用性很差,主库和从库任何一个出现问题都会影响业务
- 异步复制(默认):不等待bin log同步到从库
- 一旦主库宕机,数据就会丢失
- 半同步复制:一部分从库复制成功就行
从库是不是越多越好?
不是的,因为从库数量增加,从库连接上来的I/O线程也比较多,主库也需要创建同样数量的log dump线程来处理复制请求,对主库资源消耗较高。
bin log什么时候刷盘?
MySQL会给每个线程都分配一个bin log cache,事务执行过程中,先把日志写到bin log cache,事务提交的时候,再把bin log cache写到bin log文件中。
一个事务bin log是不能被拆开的,因此无论这个事务多大,也要保证一次性写入,如果事务的bin log被拆开,从库的多个事务就会分段执行,不能保证原子性。
事务提交时,执行器把bin log cache里完整的事务写入到bin log文件中,并清空bin log cache
虽然每个线程都有自己的bin log cache,但最终都写到一个bin log文件中。
图中的write就是把日志写到bin log文件中,但是还没有把数据持久化到磁盘
MySQL提供一个参数sync_binlog
来控制bin log刷写频率:
- sync_binlog = 0:表示每次提交事务都只write,不fsync,后续由操作系统决定何时将数据持久化到磁盘
- sync_binlog = 1:表示每次提交事务都会马上write,然后马上执行fsync
- sync_binlog = N:表示每次提交事务都write,但累积到N个事务才执行fsync
为什么要有两阶段提交?
事务提交后,redo log和bin log都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态
- redo log刷入磁盘,MySQL宕机,bin log没来得及写入。主库重启后,主库从redo log中恢复更改之后的值,但是bin log丢失了这条语句,导致主从数据不一致。
- bin log刷入磁盘,MySQL宕机,redo log没来得及写入。主库重启后,由于redo log没写,相当于这个事务无效,但是bin log被从机读取到,导致数据不一致
两阶段提交把单个事务的提交拆成两个阶段,分别是准备(Prepare)和提交(Commit)阶段,每个阶段都由协调者和参与者共同完成。
为了保证两个日志的一致性,MySQL使用了内部XA事务,内部XA事务由bin log作为协调者,存储引擎作为参与者。
- prepare阶段:将XID(内部XA事务的ID)写到redo log,同时将redo log对应的事务状态设置为prepare,然后将redo log持久化到磁盘
- commit阶段:将XID写入bin log,然后将bin log持久化到磁盘,接着调用引擎的提交事务接口,将redo log状态设置为commit。
异常重启会出现什么现象?
A、B时刻,redo log都处于prepare阶段
MySQL重启后会按顺序扫描redo log,碰到处于prepare状态的redo log,就拿着redo log中的XID去bin log中查看是否存在此XID
- 如果bin log中没有当前内部XA事务的
XID
,说明redo log完成刷盘,但bin log还没有刷盘,则回滚事务。对应时刻A - 如果bin log中有当前内部XA事务的
XID
,说明redo log 和 bin log都已经完成刷盘。对应时刻B
对于prepare阶段的redo log,既可以提交事务又可以回滚事务,这取决于是否能在bin log中查找到与redo log相同的XID
两阶段提交是以bin log写成功为事务提交成功的标识,因为bin log写入成功了,就意味着能在bin log中找到与redo log中相同的XID