- 数据库如何持久化数据?
数据库一般都需要硬盘来进行数据持久化 ,数据库在持续提供对外服务的过程会不断的进行写入操作(增、删、改),为了保证每次成功的写入都能落盘,就需要每次进行写入操作都调用fsync
操作进行磁盘写入。操作系统的fsync
将在硬盘写入成功后返回,确保数据落盘成功。
以上方式在修改成功落盘之后再提交事务,就可以确保一次提交成功事务的持久化。但是这种方式往往受硬盘IO性能的影响,况且很多情况下修改的数据都需要进行随机IO,性能更差。
内存的写入与读取性能远远高于磁盘,于是MySQL
引入了各种Buffer
,减少硬盘IO次数。引入Buffer
后用户的查询和写入操作都可以直接操作Buffer
,性能大幅度提升。但是Buffer
中的数据会在系统重启后丢失,所以也需要进行落盘。又回到了前边提到的落盘受IO
性能影响导致吞吐量下降。
在此基础上引入异步分批写入
可以保证数据库的高性能,但是异步存在一个致命问题是事务已经提交了,后续的异步落盘写入失败,就违背了ACID
中的持久性
特性。
于是MySQL
使用了一种折中的方式预写入(WAL)
,通过及时落盘保证持久性
,通过顺序写
和精简日志
保证写入效率。其中包括redo log
、undo log
及binlog
。
- Redo Log是什么?
Redo Log
一种物理日志,记录对数据页的修改内容,用于保证事务的持久性
和数据库或操作系统崩溃后的故障恢复
。内部记录了需要修改的数据页
、页中数据的偏移量
、修改的字段
和修改的值
。
- Redo Log 的写入
MySQL
在事务提交之前,本次事务修改的Redo log
会被写入到硬盘,并且只有在写入成功后,事务才能提交成功。这样就确保了成功提交的事务被持久化到了硬盘中。
不过Redo Log
的写入是先写入Redo Log Buffer
,然后根据具体的Redo Log刷盘策略,写入磁盘。可以通过更改innodb_flush_log_at_trx_commit
参数控制刷盘策略:
- 设置为
0
,每秒将Redo log buffer
写入到操作系统的page cache
中,并执行fsync
刷盘,极端情况本规下丢失一秒的数据; - 默认为
1
,每次事务提交将Redo log buffer
写入到操作系统的Page Cache
中,并执行fsync
输盘,性能稍差,但是不会丢失已提交数据; - 设置为
2
,每次事务提交将Redo log buffer
写入到操作系统的Page Cache
中,然后每秒执行刷盘策略,极端情况下丢失一秒的数据。
对于0
和2
中的设置,并不是严格的一秒一次,会受到其他操作的影响提前触发,例如执行DDL
语句。
- Redo Log的故障恢复
当系统或数据库崩溃时,InnoDB
会使用Redo Log
中的记录进行数据恢复。根据Redo log
记录的页号
、偏移量
和修改内容
修改数据页
的内容,并根据每个redo log
中的全局唯一增量日志序列号lsn
避免日志被重复执行。
因为Redo log
是基于页的修改,在InnoDB
中每个页默认16K
,操作系统每次写入文件的大小为4K
,这4K
可以保证原子性
,但是无法确保16K
分4次写入的原子性
,16K
写入不完整时就会出现页损坏
,Redo Log
无法恢复损坏的页,需要先通过Dobule Write Buffer
文件恢复完整的数据页,再执行Redo log
。Redo Log
在事务执行修改的过程中,会进行写入,而Doubule Write
在事务提交之前,将产生的脏页写入Double Write
中,然后再将脏页
落盘。