Mysql之undo log、redo log、binlog日志篇

undo log 回滚日志,是数据库系统中用于确保数据一致性 和支持事务回滚 以及MVCC 的一种日志机制。
redo log 重做日志,确保事务的持久性 和事务的原子性 。确保数据库发生崩溃,也能够将修改过的操作恢复到磁盘中,保证数据的持久性
binlog server层生成的日志,主要用于数据库备份以及主从复制。


undo log

工作原理:每当数据库执行一项修改操作时,会首先记录这项操作之前的数据(原始数据,执行前的数据状态),然后执行实际的修改操作。

  • 执行修改前备份数据,Unod log会栽树开始时备份数据的原始值,。Undo log会保存该数据修改前的状态。
  • 执行数据修改:INSERTUPDATEDELETE会改变数据表的内容
  • 事务提交:如果事务成功提交,,Undo log中的记录日志将会被抛弃,因为事务已经成功完成,数据已被永久更新。
  • 事务回滚:如果事务执行回滚,Undo log会按照记录的反向操作,将数据恢复到修改之前的状态。
    • INSERT --> DELETE
    • UPDATE --> 会更新为旧值
      • 如果不是主键列:在undo log中直接反向记录如何update。update是直接进行的。
      • 如果是主键列:update先删除该行,在插入一行目标行。
    • DELETE --> INSERT
      • delete为特殊操作。delete不会理解删除,而是讲delete对象标记一个delete flag,最终操作由purge线程进行完成。

之前我们在row格式中说过,row有两个字段分别是trx_id(操作记录的事务ID),roll_pointer指向上一个当前数据的旧版本信息,形成一个版本链。

MVCC通过Read View和Undo log实现的。对不同的事务隔离级别创建Read View的时机不同

读提交\]每次进行select都会生成一个read view。事务期间多次读取同一条数据会出现前后不一致的情况,可能在事务期间另外一个事务对数据进行了修改并提交。 \[可从复读\]启动事务时生成一个Read View,整个事务期间都使用的是这个Read view,确保在整个事务期间读取到的诗句都是在事务启动时的记录。 MVCC为多版本并发控制,Read view中字段和row格式里的字段对比,是否满足可见性。如果不满足可见性,就会沿着Unod log版本链进行查找到满足可见性的数据,从而保证控制并发事务访问同一个记录的行为。 #### redo log 工作原理: * 事务开始:事务执行时,Innodb会将修改操作(INSERT DELETE UPDATE)记录到read log日志中 * 日志缓冲区:所有的Read log操作首先会被写入日志缓冲区 **(log buffer)** , * 刷写磁盘:但事务提交时,Innodb会将日志缓冲区的Redo log刷写到Redo log文件中。即使数据没有写入数据表中,但是Redo log已经落盘。 * 如果事务提交之后发生宕机情况,重启之后就可以通过redo log恢复事务 redo log写入的方式为顺序写,也就是追加操作,因此写入磁盘开销更少。 其实redo log也不是立即写入磁盘,redo log有自己的缓存,redo log buffer,每当产生一条redo log日志的时候,先写入redo log buffer中,然后再进行落盘。 redo log刷盘: * mysql正常关闭 * redo log buffer中记录的日志大于redo log buffer内存空间的一半 * innodb的后台线程每隔一秒,会讲redo log buffer持久化到磁盘中 * 根据策略设置参数innodb_flush_log_at_trx_commit参数 * 0:每次事务提交时,还是会讲redo log日志先写入redo log buffer中,该模式下事务提交的时候不会主动触发写入磁盘的操作 * 1:每次事务提交时,都将缓存在redo log buffer中的redo log日志直接写入磁盘 * 2:每次事务提交时,都只是缓存在redo log buffer里的redo log写到redo log文件中,这里的写入文件指的是写到内核空间的page cache等待每秒后台线程去刷盘。 redo log 文件 * redo log由两个文件组成,ib_logfile0和ib_logfile1组成 * 两个文件以循环写的方式工作的,从头开始写,写到文件尾又从头开始写,覆盖形式。 * ib_logfile0 --\> ib_logfile1 --\> ib_logfile1(满)--\> ib_logfile0 * redo log 为防止buffer pool中的脏页丢失而设计的,buffer pool的脏页刷写到磁盘中,那么redo log对应的记录也就没有了,擦除这些旧日志,腾出空间。 * 两个指针 * write pos表示**redo log**写入到的位置 * check point表示当前要擦除的位置 * write pos --\> checkpoint 这个空间为可以写入的空间,就是还是写多少 * checkpoint --\> wirte pos 为等待落盘的redo log数据(等待落盘脏页) * 这样的循环往复的写,就可不需要那么多的文件去记录redo log日志,起到一个循环利用的效果吧。 #### binlog 是记录所有可能导致数据变更的操作的日志文件,主要用于数据恢复和主从复制。 文件格式 * STATEMENT:每一条修改数据的sql都会被记录到binlog中,主从复制的slave端会格局sql重现语句。但是有可能会出现主从数据不一致的情况,比如执行动态函数uuid,now等。(默认格式) * ROW:记录数据最终被修改成什么样子。不出出现动态函数问题。这里会出现一个问题,就是row格式下会记录每一个update的变化结果,如果是批量的update,就是产生很多的记录,这样binlog日志就会过大。 * MIXED:结合了STATEMENT和ROW两种格式,根据不同的情况选择不同的模式。灵活。 主从复制过程 * 写入binlog:主库写入binlog日志,提交事务,并更新本地数据 * 同步binlog:把binlog复制到所有的从库上,把每个从库的binlog写到暂存日志中 * 回放binlog:更新存储引擎中的数据 * 整个过程为,主库写入binlog日志,从库I/O线程从主库获取binlog内容,写入到relay log(中继)日志中,从库sql线程读取relay,重放sql或数据变更。 主从复制模式: * 同步模式:mysql主库提交事务,等待所有的从库复制成功并响应,才会返回客户端结构。性能差,等同于实时同步。 * 异步复制:主库提交事务的线程并不会等待binlog同步到各个从库,就直接返回给客户端。自顾自己完成操作,其他的从库一概不理,成不成功与主库无关。(默认模式) * 半同步模式:介于同步模式和异步模式之间,事务线程不用等待所有的从库复制成功响应,只需要一部分复制成功响应就行。如果有多个从库,等待至少有一个从库响应就可以,然后返回给客户端。在这个模式下,兼顾了前两者模式的优点,即使主线主库宕机的情况,至少有一个从库有最新的数据,保证数据的完整性。 binlog刷盘 * 首先是每个线程都有一个binlog cache区域,是事务执行过程中,用于暂存binlog时间的内存区域,直到事务提交然后一次性写入磁盘。 * 一个线程只能处理一个事务,所以当一个事务开始的时候就会默认提交上一个事务,如果一个事务的binlog被拆开的时候,在从库执行就会被当作多个事务分段执行,破坏原子性。 * 所以事务执行的时候是存在binlog cache中,然后再写入内核的page cache中,然后由存储引擎的后台线程刷写到磁盘中。 * 可以设置mysql中的sync_fsync参数来设置实盘的频率 * 0:每次事务提交时只写入page cache中,不刷写磁盘,后续有操作系统决定和书将数据写入磁盘。 * 1:每次事务提交写入page cache中,然后直接执行刷写磁盘操作 * N(N \> 1):每次事务都写入page caceh中,累计到N个的事务的时候,将会进行写入磁盘的操作 #### 两段提交 redo log和binlog都会持久到硬盘中,如果出现任何一方没有成功,我感觉也算是违背了mysql一致性原则。这是我对两段式出现原因的理解。 * binlog 为 Server层 * redo log 为Innodb层 1. 数据更新的时候,将XID写入redo log中,首先写入redo log,同时把当前事务状态设置为prepare状态,然后将redo log持久化到硬盘中(innodb_flush_log_at_trx_commit = 1),直接刷盘。 2. 把XID写入binlog中,然后将binlog持久化到磁盘中(sync_binlog = 1)直接写入,然后调用引擎提交事务的接口,将redo log状态设置为commit。 总结:数据写入buffer pool和redo log缓冲区,并在prepare阶段记录。binlog在prepare阶段写入磁盘,事务提交,binlog已经落盘。将事务状态由prepare变更为commit,数据最终持久化到磁盘(redo log 刷盘)。 3.如果发生mysql重启的情况,会按照扫描顺序扫描redo log,碰到处于prepare状态的redo log,就会拿着redo log的XID去binlog查找XID是否存在。 * 不存在:说明redo log已经完成了刷盘,但是binlog没有刷盘,回滚事务 * 存在:说明redo log和binlog完成刷盘,事务提交 这个时候又来了一个问题,当配置 innodb_flush_log_at_trx_commit = 1 和 sync_binlog = 1 的时候,即每次都需要刷写磁盘,这样对I/O其实不是很友好。 这个时候就来了个组提交。 #### 组提交 * flush阶段:多个事务按照进入顺序讲binlog从cache写入文件(不刷盘) * sync阶段:对binlog文件sync(多个事务的binlog合并一次性刷盘) * commit阶段:各个事务按照顺序做innodb commit操作。 * 注意:每个阶段都有一个队列,每个队列都有一把锁,第一个进度队列的时候为leader,leader领带队列的所有事务,全权负责很个队列的操作,完成后通知队列内部事务操作结束。 过程: * flush 阶段: * 首先后去队里的事务组,将redo log中prepare阶段的数据进行刷盘,flush redo log * 将binlog数据写入文件,其实是把binlog写入page cache中 * 这个阶段主要是提供了redo log的组提交。 * sync 阶段: * 这里有两个机制: * binlog_group_commit_sync_delay = N。等待N微秒后,直接调用sync将page cache中的binlog刷盘。 * binlog_group_commit_sync_no_delay = N。如果队列中的事务数量达到N个之后,忽略`binlog_group_commit_sync_delay`设置,直接进行sync的操作,将page cache中的binlog写入磁盘中。 * commit 阶段: * 调用引擎的提交事务的接口,将redo log足行台设置为commit。 这样这个组提交就完成了。其实就是为了保证磁盘I/O开销小一些,组成一个组在进行操作。这个方案其实就和以前的小县城等车一样。要么就等车上的位置全部坐满之后发车,要么就是等到一定的时间节点之后发车,就是为了提升上座率,将成本降到最低

相关推荐
码小凡5 小时前
优雅!用了这两款插件,我成了整个公司代码写得最规范的码农
java·后端
星星电灯猴6 小时前
Charles抓包工具深度解析:如何高效调试HTTPHTTPS请求与API接口
后端
isfox6 小时前
Hadoop 版本进化论:从 1.0 到 2.0,架构革命全解析
大数据·后端
咯哥布林6 小时前
Ubuntu24安装MySQL8.4
mysql
normaling6 小时前
四、go语言指针
后端
yeyong6 小时前
用springboot开发一个snmp采集程序,并最终生成拓扑图 (二)
后端
掉鱼的猫7 小时前
Solon AI 五步构建 RAG 服务:2025 最新 AI + 向量数据库实战
java·redis·后端
java金融7 小时前
FactoryBean 和BeanFactory的傻傻的总是分不清?
java·后端