日志
日志分为三种:
undo log (回滚日志):用于事务回滚和MVCC
redo log (重做日志):用于故障恢复
binlog (归档日志):用于数据备份和主从复制
undo log
undo : 撤销
undo log :在Innodb存储引擎层生成的日志
一条记录的每一次更新操作产生的undo log格式都有一个roll_pointer指针和一个trx_id事务id:
- 通过trx_id可以知道该记录是被哪个事务修改的;
- 通过roll_pointer指针可以将这些undo log 串成一个链表,这个链表可以称为版本链
undo log 通过ReadView + undo log 实现MVCC(多版本并发控制)
undo log 两大作用:
- 实现事务回滚,保障事务的原子性。
- 实现MVCC关键因素之一。
Buffer pool(缓冲池)
建立Buffer pool的目的:提高读写效率。
MySQL读取一条记录时,先从磁盘读取该记录,然后在内存中修改该记录。修改完不直接写回磁盘,而是缓存起来,下次命中,直接从缓存中拿
InnoDB设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。
- 当读取数据时,如果数据存在于Buffer pool中,那么直接从Buffer pool中拿,否则从磁盘中拿。
- 修改数据时,如果数据存在于Buffer pool中,那么直接修改Buffer pool中数据所在的页,然后将其页设置为脏页,为了减少磁盘IO,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入磁盘。
Buffer pool中的数据存储,和磁盘一样,是以页为单位,与磁盘的数据交换也是以页为单位。一页默认为16KB。
Buffer pool 除了缓存【索引页】和【数据页】,还包括 Undo页,插入缓存、自适应哈希索引、锁信息等。
redo log
断电丢失问题:Buffer pool是基于内存的,如果断电重启,还未来的及写入磁盘的脏页数据就会丢失
解决:
为了防止断电丢失的问题,当有一条记录需要更新时,InnoDB引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以redo log的形式记录下来,这个时候,更新就算完成。后续由后台线程选择一个合适的时机将脏页写入磁盘。
这就是:WAL(Write-Ahead Logging)技术:MySQL的写操作并不是立刻写在磁盘上,而是先写日志,然后在合适的时间再写入磁盘。
什是redo log?
redo log 是物理日志,记录了某个数据页做了什么修改。
在事务提交时,只要先将redo log持久化到磁盘即可,可以不需要等到将缓存在Buffer pool里的脏页数据持久化到磁盘。
当系统崩溃时,虽然脏页数据没有写入磁盘,但是redo log已经写入磁盘,MySQL重启后,可以根据redo log的内容,将所有数据恢复到最新的状态。
被需改Undo 页面,需要记录对应的redo log吗?
需要
开始事务后,InnoDB层更新记录前,首先要记录相应的undo log,如果是更新操作,需要把更新的列的旧值记录下来,也就是生成一条undo log,undo log会写入Buffer pool中的Undo页面中。
不过,在内存修改undo 页面后,需要记录对于的redo log
redo log 和 undo log 的区别在哪?
redo log 记录了此次事务完成后的数据状态,记录的的是更新之后的值
undo log 记录了此次事务开始前的数据状态,记录的是更新之前的值
事务提交之前发生崩溃,重启后通过undo log回滚事务
事务提交之后发生崩溃,重启后通过redo log恢复事务
redo log 有自己的缓存 redo log buffer,每产生一条redo log时,会先写到redo log buffer,后续再持久化到磁盘中。
redo log buffer 默认大小为16MB,可以动态调整。
redo log 什么时候刷盘?
主要有以下几个时机:
- MySQL正常关闭时
- 当redo log buffer被写满一半时
- InnoDB的后台线程每隔1秒,将redo log buffer写入磁盘
- 每次事务提交时都将redo log buffer中的redo log直接写入磁盘。(这里有一个参数可以控制
innodb_flush_log_at_trx_commit
)
innodb_flush_log_at_trx_commit
0:事务提交后,redo log留在buffer中
1:事务提交后,redo log直接写入磁盘
2:事务提交后,redo log写入操作系统的Cache中
数据安全性:1 > 2 > 0
写入性能:0 > 2 > 1
当其值为 0 和 2时,InnoDB的后台线程每隔1秒
- 针对参数0:调用
write()
写入Cache中,再调用fsync()
持久化磁盘中。如果MySQL进程崩溃,会导致上一秒钟所有事物数据丢失- 调用
fsync()
,将Cache中的redo log写入磁盘。MySQL进程的崩溃不会丢失数据,只有OS崩溃或者系统断电的情况下,上一秒所有事物数据才会丢失
redo log 文件写满了怎么办?
redo log并不是无限大的,也就是说,redo log不会记录自数据库创建以来的所有redo操作。一条记录被更改,产生redo log,当buffer pool中的脏页持久化到磁盘,那么相应的redo log也就失去了作用。
默认情况下,InnoDB存储引擎有一个重做日志文件组(redo log group),其有两个redo log文件组成,大小一直。
重做日志文件组一循环写的方式工作的,从开头写到结尾,再从结尾写到开头。
binlog(归档日志)
undo log 和 redo log 是由InnoDB存储引擎产生的
binlog 是由Server层产生的
binlog 文件记录了所有数据库表结构变更和表数据修改,不会记录查询类操作。
binlog有三种格式类型
- STATEMENT(默认格式):记录SQL语句,但是存在动态函数的问题,比如获取当期时间、uuid
- ROW:记录行数据最终被修改成什么样了。如果批量执行update语句,那可能会导致binlog文件过大。
- MIXED:包含了上述两种模式,根据不同情况自动使用ROW和STATEMENT。
redo log 和 binlog 的区别
- 适用对象不同。
binlog由Server层实现,所有引擎都可以使用,
redo log是InnoDB存储引擎实现。- 文件格式不同。
binlog有三种格式类型。
redo log是物理日志,记录的是某个数据页做了什么修改,比如对xxx表空间中的yyy数据页zzz偏移量的地方做了aaa更新。- 写入方式不同。
binlog是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的,保存全量的日志。
redo log 是循环写,日志空间大小是固定的。- 用途不同。
binlog用于备份恢复,主从复制。
redo log 用于掉电故障恢复。
恢复整个数据库,应该用binlog
主从复制是怎么实现的?
主从复制的过程就是将binlog中的数据从主库传输到从库上。
这个过程一般是异步
的,也就是主库上执行事务操作的线程不会等待复制binlog的线程同步完成。
MySQL集群的主从复制过程梳理成3个阶段:
- 写入binlog:主库写binlog日志,提交事务,并更新本地存储数据。
- 同步binlog:把binlog复制到所有从库上,每个从库把binlog写到暂存日志中。
- 回放binlog:回放binlog,并更新存储引擎中的数据。
主从复制模型
主要有三种:
- 同步复制:MySQL主库提交事物的线程等待所有从库的复制成功响应,才返回客户端结果。
- 异步复制(默认模型):MySQL主库提交事务的线程并不会等待binlog同步到个从库,就直接返回客户端结果。这种模式一旦主库宕机,数据就会发生丢失。
- 半同步复制:介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行。这种半同步复制的方式,兼顾两者的优点,即使主库宕机,至少还有个从库有最新的数据,不存在数据丢失的风险。
binlog什么时候刷盘?
-
事务执行过程中,先把日志写到binlog cache(Server层的cache,MySQL给每个线程分配了一片内存空间用于缓存binlog,可以用binlog_cache_size参数控制大小),事务提交的时候,再把binlog cache写到binlog文件中。
-
一个事务的binlog是不能被拆开的,无论这个事务有多大,也要要保证一次性写入。
-
事务提交时,执行器把binlog cache里的完整事务写入到binlog文件中,并清空binlog cache
-
虽然每个线程都有自己的binlog cache,但最终都会写到一个binlog文件中。
-
图中的write,指的就是把日志写入到binlog文件,但是并没有持久化到磁盘。这时,数据在OS的cache中。
-
图中的fsync,才是将数据持久化到磁盘的操作,这里就会涉及磁盘I/O。
MySQL提供一个sync_binlog
参数来控制数据库的binlog刷到磁盘上的频率 -
sync_binlog = 0:表示每次提交事务都只write,不fsync,后续交由操作系统决定合适将数据持久化到磁盘。(MySQL系统默认为0)
-
sync_binlog = 1:表示每次事务都会write,然后马上fsync。
-
sync_binlog = N(N > 1):表示每次提交事务都会write,但累计N个事务后才会fsync
一条update语句执行流程
两阶段提交
事务提交后,redo log 和 binlog 都要持久化到磁盘中,但这是两个独立的逻辑,可能出现半成功的状态,造成主从数据不一致
解决:两阶段提交
两阶段提交把单个事务的提交拆分成2个阶段:**准备阶段(Prepare)**和 提交阶段(Commit)。
每个阶段都由 协调者 和 参与者 共同完成
大致的思想是:把redo log的提交分成两个阶段,将binlog的提交插入其中
未来保证这两个日志的一致性,MySQL使用了内部XA事务,内部事务有binlog作为协调者,存储引擎为参与者。
两阶段提交过程
- prepare阶段:将XID(内部事务的ID)写入到redo log, 同时将redo log对应的的事物状态设置为prepare,然后将redo log写入磁盘。
- commit阶段:将XID写入binlog,然后将binlog写入磁盘,将redo log状态设置为commit。
两阶段提交是以binlog写成功作为事务成功的标识。
redo log 可以在事务没提交之前持久化到磁盘,但是binlog必须在事务提交之后,才可以持久化到磁盘。
异常崩溃会出现什么现象
- 时刻A崩溃:redo log处于prepare状态,redo log完成刷盘,binlog没有刷盘。磁盘中的redo log中有XID,binlog没刷盘,没有XID,则回滚事务。
- 时刻B崩溃:redo log处于prepare状态,redo log完成刷盘,binlog完成刷盘。磁盘中的redo log 和 binlog 都有XID,则提交事务。
两阶段提交存在的问题
- 磁盘I/O次数高:每一次进行两阶段提交,都会进行两次磁盘IO。
- 锁竞争激烈:在多事务的情况下,需要加一个锁来保证两阶段提交的原子性。锁的是整个两阶段提交过程。
组提交
MySQL引入了**binlog组提交**(group commit)机制,当多个事务提交的时候,会将多个binlog刷盘操作合并成一个,从而减少磁盘I/O次数。组提交机制,prepare阶段没变,只针对commit阶段,将其分为了三个阶段:
- flush阶段(不刷盘):多个事务按照进入的顺序将binlog从cache(这里的cache应该是MySQL Server层的binlogcache)写入文件。也就是把binlog写入OS的Cache,为刷盘做准备。
- sync阶段(刷盘):对binlog文件做fsync操作(多个事务的binlog合并一次刷盘)
- commit阶段(返回OK):各个事务按顺序做InnoDB commit操作
锁的粒度减小了,这样就使得多个阶段可以并发执行,从而提升效率。
第一个进入队列的事物会成为leader,leader领导所在队列的所有事务,全权负责整队的操作,完成后通知其他事物操作结束。
有binlog组提交,redo log也有组提交(MySQL 5.7)
redo log的刷盘操作推迟到flush阶段,也就是说prepare阶段融合在flush阶段。