MySQL架构
-
架构分层
MySQL整体分为3层:客户端层,Server层和存储引擎层。
-
binlog日志由Server层生成;
-
redo log是InnoDB特有的日志,由InnoDB引擎生成。
-
归档日志(binlog)
内容介绍
-
概念
数据库的Server层面,也有自己的日志,称为binlog(归档日志)
-
理解逻辑日志binlog
- 写入新binlog文件,不让之前的逻辑影响
执行一次flush logs命令行,就会在data目录下新增一个mysql-bin.00000x文件。
## 登陆MySQL命令行
mysql -uroot -p
## 刷新binlog
flush logs;
## 确认刷新binlog成功
show master status;
## 查询binlog日志位置
show variables like 'log_bin%';
- 测试数据
## 创建表
CREATE TABLE `User` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) CHARACTER SET gb2312 COLLATE gb2312_chinese_ci NOT NULL,
`age` int(11) UNSIGNED NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;
## 新增
INSERT `User` VALUES("1", "张三", 18);
INSERT `User` VALUES("2", "李四", 20);
## 修改
DELETE FROM `User` WHERE id = 1;
- 翻译binlog二进制文件
sudo /usr/local/mysql/bin/mysqlbinlog --base64-output=DECODE-ROWS -v mysql-bin.000006 > mysqlbin.sql
这是翻译出来的sql文件,是因为我在mysqlbinlog -v参数加工而成的。
-
总结
逻辑日志里边就是记录着sql语句,通过sql语句记录着逻辑的变化,比如insert, update等动作,但不是记录具体数据,那个由物理日志完成。
特点分析
-
与redo log的区别
-
redo log是innoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都能使用;
-
redo log是循环写,空间固定会用完;binlog是追加写入。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
-
-
binlog写入策略
通过与redo log的区别,我们知道,binlog是追加写入的,所以与redo log写入相比,没有擦除的概念。
写入逻辑
事务执行过程中,先把日志写到binlog cahce,事务提交的时候,再把binlog cache写到binlog文件中(落盘)。
流程分析
-
每个线程都有自己的binlog cache,但是共用同一份binlog文件;
-
图中的write,指的就是把日志写入到数据库系统的page cache,并没有把数据持久化到磁盘,所以速度很快;
-
图中的fsync,才是将数据持久化到磁盘的操作;
-
write 和 fsync 的时机,是由参数 sync_binlog参数 控制的:
sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。
-
-
注意事项
在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。
但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。
重做日志(redo log)
-
概念介绍
InnoDB为了能够支持事务一系列操作,而事务有4种特性(ACID):原子性、一致性、隔离性、持久性,在事务操作中,要么全部执行,要么全部不执行,这就是事务的目的。
而我们的redo log用来保证事务的持久性,即我们常说的ACID中的D。这里的说的持久性,是说最后落盘到redo log文件(即常见的ib_logfile文件),因为最后我们异常情况的恢复,都是根据文件来做恢复的。
-
WAL(预写日志Write Ahead Log)
在计算机体系中,CPU处理速度和硬盘的速度,是不在同一个数量级上的,为了让它们速度匹配,从而催生了我们的内存模块,但是内存有一个特点,就是掉电之后,数据就会丢失,不是持久的,我们需要持久化的数据,最后都需要存储到硬盘上。
InnoDB引擎设计者也利用了类似的设计思想,先写内存,在写硬盘,这样就不会因为redo log写硬盘IO而导致数据库性能问题。在InnoDB中,这种技术有一个专业名称,叫做Write-Ahead-Log(预先日志持久化)
redo log buffer --(write)–> page cache --(fsync)–> 磁盘文件
-
redo log写入策略
上边是保证了处理的速度,但是怎么样保证写入到硬盘的可靠性呢?
InnoDB引擎的设计者也设计了一种写入的策略,首先有一个后台线程,每隔1秒,就会把redo log buffer中的日志,调用write写到文件系统的page cache,然后调用fsync持久化到磁盘(即redo log文件 ib_logfile0 ib_logfile1)。
为了控制redo log写入策略,InnoDB提供了innodb_flush_log_at_trx_commit配置参数,它有三种取值:
-
设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中;
-
设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
-
设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。
如果不是对性能要求高的,一般把该参数设置为 1。
-
-
redo log的擦除
通过上边的设计,速度和可靠性的问题都解决了,但是我们仔细想想,还会有什么问题?
随着文件的增加,落盘的速度会越来越慢,这里就涉及到一个删除日志文件的算法,即我们的redo log擦除。
redo log 的大小是固定的,比如可以配置一组4个文件,每个文件大小是8M,那么这个redo log总共就可以记录32M的操作,这个参数可以通过innodb_log_file_size设置。
下图是具体的擦除算法:ib_logfile 从头开始写,写到末尾就又回到开头循环写。
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后移动并且循环的,擦除记录前要把记录更新到数据文件,write pos与check point之间为剩余可用写入的空间。
何时会擦除redo log并更新到数据文件中
-
系统空闲时;
-
Redo log文件没有空闲空间时,即write pos追上check point的时候;
-
MySQL Server正常关闭时
-
-
crash-safe
redo log的存在使得数据库具有crash-safe能力,即如果Mysql 进程异常重启了,系统会自动去检查redo log,将未写入到Mysql的数据从redo log恢复到Mysql中去。
当数据库发生异常重启时,系统会自动定位到上次checkpoint的位置,同时,每个数据页中也存在一个LSN,当redo log中的LSN大于数据页中的LSN时,说明重启前redo log中的数据未完全写入数据页中,那么将从数据页中记录的LSN开始,从redo log中恢复数据。
比如redolog 的LSN 是 13000,数据库页的LSN是 10000,那么说明重启前有部分数据未完全刷入到磁盘的数据页中,那么系统将会恢复redo log 中LSN从10000开始到13000的记录到数据页中。
当redo log中的LSN小于数据页中的LSN时,说明数据页已经被刷到该位置,所以不需要进行恢复。
两阶段提交(2PC)
-
内容介绍
这里讲的两阶段提交,就是纯粹的指redo log和binlog日志的两阶段提交。
而两阶段提交的目的就是让redo log和binlog两个日志逻辑上一致。
如果redo log持久化并进行了提交,而binlog未持久化数据库就crash了,则从库从binlog拉取数据会少于主库,造成不一致。因此需要内部事务来保证两种日志的一致性。
-
流程分析
-
将语句执行;
-
记录redo log,并将记录状态设置为prepare;
-
通知Server,已经修改好了,可以提交事务了;
-
将更新的内容写入binlog;
-
commit,提交事务;
-
将redo log里这个事务相关的记录状态设置为commited
prepare: redolog写入log buffer,并fsync持久化到磁盘,在redolog事务中记录2PC的XID,在redolog事务打上prepare标识。
commit: binlog写入log buffer,并fsync持久化到磁盘,在binlog事务中记录2PC的XID,同时在redolog事务打上commit标识 其中,prepare和commit阶段所提到的“事务”,都是指内部XA事务,即2PC
-
-
恢复步骤
redolog中的事务如果经历了2PC中的prepare阶段,则会打上prepare标识,如果经历commit阶段,则会打上commit标识(此时redolog和binlog均已落盘)。
-
按顺序扫描redolog,如果redolog中的事务既有prepare标识,又有commit标识,就直接提交(复制redolog disk中的数据页到磁盘数据页)。
-
如果redolog事务只有prepare标识,没有commit标识,则说明当前事务在commit阶段crash了,binlog中当前事务是否完整未可知,此时拿着redolog中当前事务的XID(redolog和binlog中事务落盘的标识),去查看binlog中是否存在此XID:
a. 如果binlog中有当前事务的XID,则提交事务(复制redolog disk中的数据页到磁盘数据页)。
b. 如果binlog中没有当前事务的XID,则回滚事务(使用undolog来删除redolog中的对应事务)。
可以将mysql redolog和binlog二阶段提交和广义上的二阶段提交进行对比,广义上的二阶段提交,若某个参与者超时未收到协调者的ack通知,则会进行回滚,回滚逻辑需要开发者在各个参与者中进行记录。mysql二阶段提交是通过xid进行恢复。
-
参考链接
-
MySQL的redo log和bin log 真香
https://mp.weixin.qq.com/s/M5CXNhMEh2QCLZp59HsSbA
-
Mysql面试题系列-什么是crash-safe能力?什么是两阶段提交?
https://blog.csdn.net/Belingda/article/details/107172933