MySQL 日志主要包括 错误日志、查询日志、慢查询日志、事务日志、二进制日志等等几大类。其中比较重要的就属二进制日志binlog(归档日志)、事务日志redo log(重做日志)和undo log(回滚日志)。
redo log 是InnoDB引擎独有的,它让数据库有了崩溃恢复的能力。
如果MySQL在服务过程中宕机或崩溃了,MySQL就可以在重新启动的过程中通过redo log恢复数据,保证数据的持久性和完整性。
之前说过,数据库中的数据是以页为单位,当你查询某个数据时,会把该条数据所在的页整个加载到 Buffer Pool
(缓存池) 中。
后续的查找都是先从 Buffer Pool
中找,如果没有就去硬盘找,减少硬盘IO,提升性能。
更新数据时也是先修改 Buffer Pool
中的数据,会将修改记录到redo log buffer
中然后标记该页为脏页。系统会定期(一秒)将脏页刷盘到硬盘中,同时将 对应的 redo log buffer
中的记录刷盘到redo日志中。
redo log buffer
中的redo记录有多种刷盘策略,同时也有多种刷盘时机。
redo log 是物理日志,直接记录了 在哪张数据也的哪个位置修改了什么,直接将经纬度告诉你了,不只是告诉你一个地名。
InnoDB 刷新重做日志的时机有几种情况:
InnoDB 将 redo log 刷到磁盘上有几种情况:
事务提交:当事务提交时,log buffer 里的 redo log 会被刷新到磁盘(可以通过innodb_flush_log_at_trx_commit
参数控制,后文会提到)。
log buffer 空间不足时:log buffer 中缓存的 redo log 已经占满了 log buffer 总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
事务日志缓冲区满:InnoDB 使用一个事务日志缓冲区(transaction log buffer)来暂时存储事务的重做日志条目。当缓冲区满时,会触发日志的刷新,将日志写入磁盘。
Checkpoint(检查点):InnoDB 定期会执行检查点操作,将内存中的脏数据(已修改但尚未写入磁盘的数据)刷新到磁盘,并且会将相应的重做日志一同刷新,以确保数据的一致性。
后台刷新线程:InnoDB 启动了一个后台线程,负责周期性(每隔 1 秒)地将脏页(已修改但尚未写入磁盘的数据页)刷新到磁盘,并将相关的重做日志一同刷新。
正常关闭服务器:MySQL 关闭的时候,redo log 都会刷入到磁盘里去。
总之,InnoDB 在多种情况下会刷新重做日志,以保证数据的持久性和一致性。
我们要注意设置正确的刷盘策略innodb_flush_log_at_trx_commit
。根据 MySQL 配置的刷盘策略的不同,MySQL 宕机之后可能会存在轻微的数据丢失问题。
innodb_flush_log_at_trx_commit
的值有 3 种,也就是共有 3 种刷盘策略:
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。
另外,InnoDB 存储引擎有一个后台线程,每隔1
秒,就会把 redo log buffer
中的内容写到文件系统缓存(page cache
),然后调用 fsync
刷盘。
就是因为有如此多的刷盘策略和刷盘时机就有可能导致:
一个没有提交事务的 redo log 记录,也可能会刷盘。
硬盘上存储的 redo log 日志文件不只一个,而是以一个日志文件组的形式出现的,每个的redo
日志文件大小都是一样的。
比如可以配置为一组4
个文件,每个文件的大小是 1GB
,整个 redo log 日志文件组可以记录4G
的内容。
它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,
在这个日志文件组中还有两个重要的属性,分别是 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
推进一下。
redo log在 执行sql语句时记录,在一个事务中,首先记录在 redo log buffer
中,在事务提交时,进行刷盘到硬盘。
网上很多各种各样的说法,redo log 先记录在 redo log buffer
中,然后再写入 page cache
中,再根据刷盘策略和时机进行刷盘。
没必要纠结,我们只需要多看多想。
现在我们来思考一个问题:只要每次把修改后的数据页直接刷盘不就好了,还有 redo log 什么事?
它们不都是刷盘么?差别在哪里?
1 Byte = 8bit 1 KB = 1024 Byte 1 MB = 1024 KB 1 GB = 1024 MB 1 TB = 1024 GB
实际上,数据页大小是16KB
,刷盘比较耗时,可能就修改了数据页里的几 Byte
数据,有必要把完整的数据页刷盘吗?
而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。
如果是写 redo log,一行记录可能就占几十 Byte
,只包含表空间号、数据页号、磁盘文件偏移 量、更新值,再加上是顺序写,所以刷盘速度很快。
所以用 redo log 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
binlog是逻辑日志,记录的内容是语句的原始逻辑,类似于“给ID = 111这一行的字段a加1”。
不管用什么存储引擎,只要发生了表数据更新,都会产生 binlog 日志。
那 binlog 到底是用来干嘛的?
可以说 MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性。
binlog 会记录所有涉及更新数据的逻辑操作,并且是顺序写。
binlog 日志有三种格式,可以通过binlog_format
参数指定。
statement
row
mixed
statement
记录的是sql语句原文,也就是记录的就是sql语句。
但是直接记录sql语句在不同时间段可能出现问题,比如我sql语句中使用了聚合函数查看当前时间,由于有时间差,这就会导致时间不一样。或者是某个经常变动的表的记录数,数量由于时间差也可能不一样。
也就需要row
了 使用 row
可以将这些聚合函数变成一个具体的值,这就保证了一致性。
但是 row
格式需要更大的容量来记录,比较占空间,恢复与同步时也会更消耗IO资源,影响执行速度。
所以就有了一种折中的方案,指定为mixed
,记录的内容是前两者的混合。
MySQL 会判断这条SQL
语句是否可能引起数据不一致,如果是,就用row
格式,否则就用statement
格式。
binlog是在事务执行过程中写入到 binlog cache
中,事务提交时再将 binlog cache
中的记录写入到binlog文件中。
因为一个事务的 binlog 不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache
。
我们可以通过binlog_cache_size
参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap
)。
write
和fsync
的时机,可以由参数sync_binlog
控制,默认是1
。
为0
的时候,表示每次提交事务都只write
,由系统自行判断什么时候执行fsync
。
虽然性能得到提升,但是机器宕机,page cache
里面的 binlog 会丢失。
为了安全起见,可以设置为1
,表示每次提交事务都会执行fsync
,就如同 redo log 日志刷盘流程 一样。
最后还有一种折中方式,可以设置为N(N>1)
,表示每次提交事务都write
,但累积N
个事务后才fsync
。
每一个事务对数据的修改都会被记录到 undo log ,当执行事务过程中出现错误或者需要执行回滚操作的话,MySQL 可以利用 undo log 将数据恢复到事务开始之前的状态。
undo log 属于逻辑日志,记录的是 SQL 语句,比如说事务执行一条 DELETE 语句,那 undo log 就会记录一条相对应的 INSERT 语句。同时,undo log 的信息也会被记录到 redo log 中,因为 undo log 也要实现持久性保护。并且,undo-log 本身是会被删除清理的,例如 INSERT 操作,在事务提交之后就可以清除掉了;UPDATE/DELETE 操作在事务提交不会立即删除,会加入 history list,由后台线程 purge 进行清理。
undo log 是采用 segment(段)的方式来记录的,每个 undo 操作在记录的时候占用一个 undo log segment(undo 日志段),undo log segment 包含在 rollback segment(回滚段)中。事务开始时,需要为其分配一个 rollback segment。每个 rollback segment 有 1024 个 undo log segment,这有助于管理多个并发事务的回滚需求。
通常情况下, rollback segment header(通常在回滚段的第一个页)负责管理 rollback segment。rollback segment header 是 rollback segment 的一部分,通常在回滚段的第一个页。history list 是 rollback segment header 的一部分,它的主要作用是记录所有已经提交但还没有被清理(purge)的事务的 undo log。这个列表使得 purge 线程能够找到并清理那些不再需要的 undo log 记录。
另外,MVCC
的实现依赖于:隐藏字段、Read View、undo log。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID
和 Read View
来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR
找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View
之前已经提交的修改和该事务本身做的修改
由于 redo log 和binlog 的刷盘,网上的说法各种各样,这里我们统一用同一种
redo log :在sql语句执行时,记录redo log 到 redo log cache
中,事务提交时将 redo log cache
写入到 page cache
中,然后再根据刷盘策略和时机将 page cache
中的 redo log 刷盘到硬盘中。
binlog:在sql语句执行时记录 binlog 到 binlog cache
中,事务提交时,将 binlog cache
写入到 page cache
中,在更加刷盘策略和时机将 page ceche
中的binlog刷入到硬盘中。
二阶段提交:
redo log 首先标记为 prepare
状态,当binlog写入成功时,再将redo log 标记为 commit
状态。
总之,网上的说法很多,都不统一,都是按照自己的见解写的,所以我说的也不全对。大家如果有新看法可以说哦。