MySQL主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志binlog(归档日志)、事务日志redo log(重做日志)、undo log(回滚日志)。
MySQL InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性,使用 undo log(回滚日志) 来保证事务的原子性。
MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性。
redo log
InnoDB 存储引擎独有的,它让 MySQL 拥有了崩溃恢复能力。MySQL实例挂了,重启时InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
在更新Buffer Pool
里更新数据后,会把”在某页数据也上做了什么修改”记录到重做日志缓存(redo log buffer
)里,等着刷盘到redo log
后清空重做日志缓存。
重做日志缓存(redo log buffer
)刷盘到redo log
的时机:
redo log buffer空间不足时:redo log buffer中缓存的redo log已经占满了log buffer总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
事务日志缓冲区满:InnoD使用一个事务日志缓冲区(transaction log buffer)来暂时存储事务的重做日志条目。当缓冲区满时,会触发日志的刷新,将日志写入磁盘。
Checkpoint(检查点):InnoDB定期会执行检查点操作,将内存中的脏数据(已修改但尚未写入磁盘的数据)刷新到磁盘,并且会将相应的重做日志一同刷新,以确保数据的一致性。
后台刷新线程:InnoDB启动了一个后台线程,负责周期性(每隔1秒)地将脏页(已修改但尚未写入磁盘的数据页)刷新到磁盘(把
redo log buffer
中的内容写到文件系统缓存(page cache
),然后调用fsync
刷盘),并将相关的重做日志一同刷新。正常关闭服务器:MySQL关闭的时候,redo log都会刷入到磁盘里去。
可以通过
innodb_flush_log_at_trx_commit
参数控制:0:设置为 0 的时候,表示每次事务提交时不进行刷盘操作。这种方式性能最高,但是也最不安全,因为如果 MySQL 挂了或宕机了,可能会丢失最近 1 秒内的事务。
1:设置为 1 的时候,表示每次事务提交时都将进行刷盘操作。这种方式性能最低,但是也最安全,因为只要事务提交成功,redo log记录就一定在磁盘里,不会有任何数据丢失。
2:设置为 2 的时候,表示每次事务提交时都只把 log buffer 里的 redo log 内容写入 page cache(文件系统缓存)。page cache 是专门用来缓存文件的,这里被缓存的文件就是 redo log 文件。这种方式的性能和安全性都介于前两者中间。
刷盘策略innodb_flush_log_at_trx_commit
的默认值为1,设置为1的时候才不会丢失任何数据。为了保证事务的持久性,我们必须将其设置为 1。
为什么不每次直接把修改后的数据页刷到磁盘?实际上,数据页大小是16KB
,刷盘比较耗时,可能就修改了数据页里的几 Byte
数据, 没必要把完整的数据刷到磁盘。而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。如果是写 redo log,一行记录可能就占几十 Byte
,只包含表空间号、数据页号、磁盘文件偏移量、更新值,再加上是顺序写,所以刷盘速度很快。
binlog
binlog 是逻辑日志,记录内容是语句的原始逻辑,属于MySQL Server
层。 不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志,并且是顺序写 。
MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性。
binlog 日志有三种格式,可以通过binlog_format
参数指定:
- statement:记录的内容是
SQL
语句原文。 - row: 记录的内容不再是简单的
SQL
语句了,还包含操作的具体数据。记录的内容看不到详细信息,要通过mysqlbinlog
工具解析出来。通常情况下都是指定为row
,可以为数据库的恢复与同步带来更好的可靠性。但是需要更大的空间来记录,恢复与同步时会更消耗 IO 资源,影响执行速度。 - mixed: 前两者的混合,MySQL会判断这条
SQL
语句是否可能引起数据不一致,如果是就用row
格式,否则就用statement
格式。
事务执行过程中,先把日志写到binlog cache
,事务提交的时候,再把binlog cache
写到 binlog 文件中。因为一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache
。可以通过binlog_cache_size
参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap
)。刷盘时机由参数sync_binlog
控制,默认是1
:
- 0:表示每次提交事务都只把
binlog cache
写入到page cache
,由系统自行判断什么时候执行fsync
持久化到磁盘。虽然性能得到提升,但是机器宕机,page cache
里面的binlog
会丢失。 - 1: 每次提交事务都会执行
fsync
,就如同redo log日志刷盘流程一样。 - N(N>1):每次提交事务都把
binlog cache
写入到page cache
,累积N
个事务后才fsync
。出现 IO 瓶颈的场景里,将sync_binlog
设置成一个比较大的值,可以提升性能。但是,如果机器宕机,会丢失最近N
个事务的 binlog 日志。
两阶段提交
redo log(重做日志)让 InnoDB 存储引擎拥有了崩溃恢复能力。binlog(归档日志)保证了 MySQL 集群架构的数据一致性。
在执行更新语句过程,会记录 redo log 与 binlog 两块日志,以基本的事务为单位,redo log 在事务执行过程中可以不断写入,而 binlog 只有在提交事务时才写入,所以 redo log 与 binlog 的写入时机不一样。
两阶段提交是为了避免“事务执行过程中存储引擎层写完 redo log 日志后,Server层写binlog日志期间发生了异常,之后用binlog恢复数据丢失数据,而原库用redo log恢复,从而引发数据不一致”。
两阶段提交就是将 redo log 的写入拆成了两个步骤prepare
和commit
,事务执行过程中存储引擎层不断写入redo log为prepare
阶段,当事务提交时写入binlog之后把redo log设置为commit
阶段。这样做的即使binlog写入异常,MySQL再根据redo log恢复数据的时候,发现redo log还处在prepare
阶段,并且没有对应的binlog,就会执行回滚该事务(如果可以通过事务id找到对应的binlog日志,则不会回滚)。
undo log
每一个事务对数据的修改都会被记录到 undo log ,当执行事务过程中出现错误或者需要执行回滚操作的话,MySQL 可以利用 undo log 将数据恢复到事务开始之前的状态。
undo log 属于逻辑日志,记录的是 SQL 语句,比如说事务执行一条 DELETE 语句,那 undo log 就会记录一条相对应的 INSERT 语句。
undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。
PS:MVCC
的实现依赖于: 隐藏字段、Read View、undo log 。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID
和 Read View
来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR
找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View
之前已经提交的修改和该事务本身做的修改。