MySQL 日志 主要包括二进制日志 binlog(归档日志)、事务日志 redo log(重做日志)和 undo log(回滚日志)。
redo log
redo log 是InnoDB存储引擎级别的日志,能支持MySQL实现崩溃恢复。
如果 MySQL实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据。
MySQL 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 Buffer Pool 中。
后续的查询都是先从 Buffer Pool 中找,没有命中再去硬盘加载,减少硬盘 IO 开销,提升性能。
更新表数据的时候,也是如此,发现 Buffer Pool 里存在要更新的数据,就直接在 Buffer Pool 里更新。
然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(redo log buffer)里,接着刷盘到 redo log 文件里。
理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
刷盘时机
InnoDB 存储引擎为 redo log 的刷盘策略提供了 innodb_flush_log_at_trx_commit 参数,它支持三种策略:
● 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
● 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
● 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
innodb_flush_log_at_trx_commit 参数默认为 1 ,也就是说当事务提交时会调用 fsync 对 redo
log 进行刷盘
另外,InnoDB 存储引擎有一个后台线程,每隔1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用 fsync 刷盘。
除了后台线程每秒1次的轮询操作,还有一种情况,当 redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动刷盘。
在个日志文件组中还有两个重要的属性,分别是write pos、checkpoint
● write pos是当前记录的位置,一边写一边后移
● checkpoint 是当前要擦除的位置,也是往后推移
每次刷盘 redo log 记录到日志文件组中,write pos 位置就会后移更新。
每次 MySQL 加载日志文件组恢复数据时,会清空加载过的
redo log 记录,并把 checkpoint 后移更新。
write pos 和 checkpoint 之间的还空着的部分可以用来写入新的 redo log 记录。
如果 write pos 追上
checkpoint ,表示日志文件组满了,这时候不能再写入新的
redo log 记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。
binlog
redo log 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 InnoDB 存储引擎。
而 binlog 是逻辑日志,记录内容是语句的原始逻辑,类似于“update b set name = bb2”,属于MySQL Server 层。
一般来说MySQL数据库的数据备份、主备、主主、主从都需要依靠binlog来同步数据,保证数据一致性。
写入机制
binlog的写入时机也非常简单,事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog
cache写到binlog文件中。
因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。
我们可以通过binlog_cache_size参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap)。
binlog日志刷盘流程如下
● 上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快
● 上图的 fsync,才是将数据持久化到磁盘的操作
write和fsync的时机,可以由参数sync_binlog控制,默认是0。
为0的时候,表示每次提交事务都只write,由系统自行判断什么时候执行fsync。
虽然性能得到提升,但是机器宕机,page cache里面的 binglog 会丢失。
为了安全起见,可以设置为1,表示每次提交事务都会执行fsync,就如同binlog 日志刷盘流程一样。
最后还有一种折中方式,可以设置为N(N>1),表示每次提交事务都write,但累积N个事务后才fsync。
在出现IO瓶颈的场景里,将sync_binlog设置成一个比较大的值,可以提升性能。
同样的,如果机器宕机,会丢失最近N个事务的binlog日志。
两阶段提交
redo log(重做日志)让InnoDB存储引擎拥有了崩溃恢复能力。
binlog(归档日志)保证了MySQL集群架构的数据一致性。
虽然它们都属于持久化的保证,但是侧重点不同。
在执行更新语句过程,会记录redo log与binlog两块日志,以基本的事务为单位,redo log在事务执行过程中可以不断写入,而binlog只有在提交事务时才写入,所以redo log与binlog的写入时机不一样。
redo log与binlog两份日志可能存在不一致的情况。
假设我们执行SQL语句为update a set age=20 where id=6。
写完redo log日志后,binlog日志写期间发生了异常,会出现什么情况呢?
由于binlog没写完就异常,这时候binlog里面没有对应的修改记录。因此,之后用binlog日志恢复数据时,就会少这一次更新,最终redolog 恢复的数据和 binlog恢复的数据会不一致。
为了解决两份日志之间的逻辑一致问题,InnoDB存储引擎使用两阶段提交方案。
原理很简单,将redo log的写入拆成了两个步骤prepare和commit,这就是两阶段提交。
使用两阶段提交后,写入binlog时发生异常也不会有影响,因为MySQL根据redo log日志恢复数据时,发现redo log还处于prepare阶段,并且没有对应binlog日志,就会回滚该事务。
redo log设置commit阶段发生异常,那会不会回滚事务呢?
并不会回滚事务,它会执行上图框住的逻辑,虽然redo log是处于prepare阶段,但是能通过事务id找到对应的binlog日志,所以MySQL认为是完整的,就会提交事务恢复数据。
undo log
如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,在 MySQL 中,恢复机制是通过
回滚日志(undo log) 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用
回滚日志
中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
通过binlog恢复数据demo
在开发过程中常存在误删除,错误更新等情况。 这时候我们可以通过mysql的binlog日志恢复数据。
1. 首先确认开启 mysql binlog 日志
修改mysql的配置文件。linux是/etc/my.cnf,windows是mysql的安装目录/my.ini
在[mysqld]下面加上log-bin=mysql-bin 一行代码。加完后重起mysql即可。
2. 进入 mysql, mysql -uroot -p
3. 查看所有的二进制日志文件 : show binary logs
4. 查看test数据库 b 表的数据 : select * from b;
5. 修改b数据表数据,id为2的数据 : update b set name = bb
where id = 2;
6. 格式化查看binlog日志文件 : show binlog events in ‘mysql-bin.000001‘ \G
从上图中可以看到做修改的操作结束位置(end_log_pos)303
7. 推出mysql操作页面, 通过命令恢复数据
mysqlbinlog --stop-position=302 /data/mysqldb/mysql-bin.000001
| grep mysql -uroot -p
命令含义:将302之前的所有事件操作重新执行一边(不包括刚才的修改操作)
8. mysqlbinlog 相关命令的语法格式:
mysqlbinlog mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名
例:将302到500之间的所有事件操作重新执行一边
mysqlbinlog --start-position=302
--stop-position=500 /data/mysqldb/mysql-bin.000001 | grep mysql -uroot -p
常用参数选项解释:
--start-position=875 起始pos点
--stop-position=954 结束pos点
--start-datetime="2016-9-25 22:01:08"
起始时间点
--stop-datetime="2019-9-25 22:09:46" 结束时间点
--database=b指定只恢复b数据库(一台主机上往往有多个数据库,只限本地log日志)
不常用选项:
-u --user=name 连接到远程主机的用户名
-p --password[=name] 连接到远程主机的密码
-h --host=name 从远程主机上获取binlog日志
--read-from-remote-server 从某个MySQL服务器上读取binlog日志
总结
MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。
MySQL数据库的数据备份、主备、主主、主从都需要依靠binlog来同步数据,保证数据一致性。