二进制日志binlog(服务层的日志),又称归档日志。
binlog是逻辑日志,记录的是SQL语句的原始逻辑,比如”给ID=2这一行的a字段加1 ",日志内容是二进制的,根据日记格式参数的不同,可能基于SQL语句、基于数据本身或者二者的混合
binlog主要记录数据库的变化情况(逻辑日志),内容包括数据库所有的更新操作。所有涉及数据变动的操作,都要记录进二进制日志中。
binlog可对数据进行复制和备份,常用作主从库的同步、数据回滚/恢复。
binlog有两种模式
statement 模式是记录sql语句,
row模式记录行的内容,更新前和更新后都有;
事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
一个事务的 binlog 不能拆开,不论事务多大要确保一次性写入。
系统给 binlog cache 分配一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。超过参数规定大小,就暂存到磁盘。
事务提交时,执行器把 binlog cache 里完整事务写入 binlog 中,并清空 binlog cache
每个线程有自己 binlog cache,但是共用同一份 binlog 文件
write:把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,速度快
fsync:将数据持久化到磁盘,fsync 才会占磁盘 IOPS
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 日志
Statement:每一条会修改数据的sql都会记录在binlog中。
优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。(相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。)
缺点:statement 格式的 binlog 可能会导致主备不一致。由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。另外mysql 的复制,像一些特定函数功能,slave可与master上要保持一致会有很多相关问题(如sleep()函数, last_insert_id(),以及user-defined functions(udf)会出现问题).
Row:不记录sql语句上下文相关信息,仅保存哪条记录被修改。
优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题
缺点: row 格式很占空间,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如一条update语句,修改多条记录,则binlog中每一条修改都会有记录,这样造成binlog日志量会很大,特别是当执行alter table之类的语句的时候,由于表结构修改,每条记录都发生改变,那么该表每一条记录都会记录到日志中。
mixed: 是以上两种的混合使用,
statement 格式的 binlog 可能会导致主备不一致,所以要使用 row 格式。但 row 格式的很占空间,mixed 格式可以利用 statment 格式的优点,同时又避免了数据不一致的风险,
mixed 格式,MySQL 会判断SQL 语句是否可能引起主备不一致,如可能,就用 row 格式,否则就用 statement 格式
一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种,新版本的MySQL中队row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录。
至于update或者delete等修改数据的语句,还是会记录所有行的变更。
重做日志(物理日志),InnoDB特有,用来记录事务操作引起数据的变化,记录的是数据页的物理修改。
redo log是物理日志,记录实际的SQL语句,如“update T set c=c+1 where ID=2; ”
redo log日志可分为两个部分
保存在易失性内存中的缓存日志redo log buff
保存在磁盘上的redo log日志文件redo log file
crash-safe(崩溃安全能力):redo log保证即使数据库发生异常重启,之前提交的记录都不会丢失;
WAL技术
一条记录需要更新时,InnoDB引擎就会先把记录写到redo log ,并更新内存。InnoDB引擎在适当时,将操作记录更新刷入磁盘,往往是在系统空闲的时候,即WAL技术(Write-Ahead Logging):先写日志,再写磁盘;
redo log是固定大小的,每次写redo log满的时候会将前面的更新到磁盘,擦除redo log在重写开始记录;
redo log 和 binlog 都是顺序写,磁盘的顺序写比随机写速度要快;
组提交机制,可以大幅度降低磁盘的 IOPS 消耗。
脏数据(脏页):指内存中未刷到磁盘的数据。
由于redo log日志的大小固定,为能够持续不断对更新记录写入,在redo log日志中设置了两个标志位置,checkpoint和write_pos,分别表示记录擦除和记录写入的位置。redo log日志的数据写入示意图可见下图。
当write_pos标志到了日志结尾时,会从结尾跳至日志头部进行重新循环写入。所以redo log的逻辑结构并不是线性的,而是可看作一个圆周运动。write_pos与checkpoint中间的空间可用于写入新数据,写入和擦除都是往后推移,循环往复的。
当write_pos追上checkpoint时,表示redo log日志已经写满。这时不能继续执行新的数据库更新语句,需要停下来先删除一些记录,执行checkpoint规则腾出可写空间。
checkpoint规则:checkpoint触发后,将buffer中脏数据页和脏日志页都刷到磁盘。
InnoDB 刷脏页策略
正确地设置 innodb_io_capacity 参数,可设置为磁盘的 IOPS,告诉 InnoDB 你磁盘的能力
刷盘策略
脏页比例,参数 innodb_max_dirty_pages_pct 是脏页比例上限,默认 75%,最好不要接近 75%
redo log 写盘速度
上面对脏数据刷盘,为保证日志文件持久化,要将日志记录从内存写入到磁盘。
为确保每次记录都能够写入到磁盘中的日志中,每次将redo log buffer中的日志写入redo log file的过程中都会调用一次操作系统的fsync操作。
fsync函数:包含在UNIX系统头文件#include 中,用于同步内存中所有已修改的文件数据到储存设备。
写入的过程中,还需要经过操作系统内核空间的os buffer
缓冲池buffer pool(redo log中最重要的概念),在内存中分配的一个区域,包含了磁盘中部分数据页的映射,作为访问数据库的缓冲。
当请求读取数据时,会先判断是否在缓冲池命中,如果未命中才会在磁盘上进行检索后放入缓冲池;
当请求写入数据时,会先写入缓冲池,缓冲池中修改的数据会定期刷新到磁盘中。这一过程也被称之为刷脏 。
因此,当数据修改时,除了修改buffer pool中的数据,还会在redo log中记录这次操作;当事务提交时,会根据redo log的记录对数据进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复,从而保证了事务的持久性,使得数据库获得crash-safe能力。
redo log 三种状态
日志存入 redo log buffer 中,物理上是在 MySQL 进程内存;
写到磁盘 (write),但没有持久化(fsync),物理上是在文件系统的 page cache 里面;
hard disk持久化到磁盘
innodb_flush_log_at_trx_commit 参数,控制 redo log 的写入策略
设置为 0 :表示每次事务提交都只把 redo log 留在 redo log buffer 中 ;
设置为 1 :表示每次事务提交时都将 redo log 直接持久化到磁盘;
设置为 2 :表示每次事务提交时都只是把 redo log 写到 page cache。
刷盘时机
InnoDB 有一个后台线程,每隔 1 秒把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘
redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。由于这个事务可能没有提交,这个写盘动作只是 write,没有 fsync,只到 page cache
并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘
组提交机制(group commit)
LSN:日志逻辑序列号(log sequence number)单调递增的,对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。
LSN 也会写到 InnoDB 的数据页中,确保数据页不会被多次执行重复的 redo log。
trx1 是第一个到达的,会被选为这组的 leader;
rx1 开始写盘时,组里已有三个事务,这时候 LSN 变成 160;
trx1 去写盘时,带的就是 LSN=160,因此等 trx1 返回时,所有 LSN 小于等于 160 的 redo log,都已经被持久化到磁盘
这时 trx2 和 trx3 就可以直接返回了。所以一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。
并发更新场景下,第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的效果就越好。
不同点 | binlog | redo log |
---|---|---|
试用引擎 | mysql的server层实现的日志记录功能,所有引擎都可以使用 | InnoDB引擎所特有 |
日志属性 | 逻辑日志,记录了sql语句的原始逻辑 | 物理日志,记录在什么数据上做了什么修改 |
记录方式 | 日志追加方式,binlog会自动追加写日志,当写到一定大小时会自动切换新文件继续记录 | 循环方式,redo log 大小固定,空间用完需要写磁盘擦除才能继续记录 |
回滚日志,InnoDB特有,对失败的回滚数据进行回滚
当事务对数据库进行修改,InnoDB引擎不仅会记录redo log,还会生成对应的undo log日志;
事务执行失败或调用rollback,导致事务需要回滚,就可以利用undo log中的信息将数据回滚到修改之前的样子。
undo log属于逻辑日志,对修改/插入的数据做反向记录(用于反向执行修改),插入操作(insert),回滚时会执行数据删除操作(delete)
undo log由两个作用,提供回滚,实现MVCC。
注:
参考 :超干货!为了让你彻底弄懂 MySQL 事务日志,我通宵搞出了这份图解!
文章节选自 极客时间《MySQL45讲》 如有不当,联系侵删