武神龙 發表於 2025-7-16 18:54:00

MySQL 15 日志相关问题追问

<p>先放一下两阶段提交的图,在后续问题中会用到:</p>
<div align="center"><img src="https://img2024.cnblogs.com/blog/3389949/202507/3389949-20250716184410572-284200978.png" width="30%"></div>
<h3 id="问题">问题</h3>
<p>在MySQL 02中,讲到为什么要使用两阶段提交时用的是反证法,说明了如果不使用两阶段提交,会导致MySQL出现主备数据不一致等问题。</p>
<p>那么如果在两阶段提交的不同瞬间,MySQL如果发生异常重启,是怎么保证数据完整性的呢?</p>
<p>如果在图中时刻A,也就是写入redo log后、写binlog前发生了崩溃,由于此时binlog还没写,redo log也还没提交,所以崩溃恢复的时候,该事务会回滚。因为binlog还没写,也不会传到备库。</p>
<p>如果在图中时刻B,也就是binlog写完,redo log还没commit前发送崩溃,会怎么样呢?</p>
<p>先看一下崩溃恢复时的判断规则:</p>
<ul>
<li>
<p>如果redo log里的事务是完整的,也就是已经有commit标识,则直接提交;</p>
</li>
<li>
<p>如果redo log里的事务只有完整的prepare,则判断对应的事务binlog是否存在并完整:</p>
<ul>
<li>
<p>是,则提交事务;</p>
</li>
<li>
<p>否,回滚事务。</p>
</li>
</ul>
</li>
</ul>
<p>因此,时刻B崩溃恢复过程中事务会被提交。</p>
<h3 id="追问1mysql怎么知道binlog是完整的">追问1:MySQL怎么知道binlog是完整的?</h3>
<p>一个事务的binlog有完整的格式:</p>
<ul>
<li>
<p>statement格式的binlog,最后会有COMMIT语句;</p>
</li>
<li>
<p>row格式的binlog,最后会有一个XID event作为标识。</p>
</li>
</ul>
<p>在MySQL 5.6.2 版本后,还引入了<code>binlog-checksum</code>参数,用来验证binlog内容的正确性。对于binlog日志由于磁盘原因可能在日志中间出错的情况,MySQL可以通过校验该参数的结果来发现。</p>
<h3 id="追问2redo-log和binlog是怎么关联起来的">追问2:redo log和binlog是怎么关联起来的?</h3>
<p>两者有一个共同的数据字段XID。崩溃恢复的时候,会按顺序扫描redo log:</p>
<ul>
<li>
<p>如果碰到既有prepare,又有commit的redo log,就直接提交;</p>
</li>
<li>
<p>如果碰到只有prepare,而没有commit的redo log,就拿着XID去binlog找对应的事务。</p>
</li>
</ul>
<h3 id="追问3为什么设计为prepare的redo-log完整binlog重启就能恢复">追问3:为什么设计为,prepare的redo log+完整binlog,重启就能恢复?</h3>
<p>这个问题也与数据与备份的一致性有关。在时刻B,binlog已经写完,之后会被从库使用,因此在主库上也要提交这个事务,才能做到一致性。</p>
<h3 id="追问4如果这样为什么还要两阶段提交为什么不先把redo-log写完再写binlog而等崩溃恢复要求两个日志都完整">追问4:如果这样为什么还要两阶段提交?为什么不先把redo log写完,再写binlog。而等崩溃恢复要求两个日志都完整?</h3>
<p>两阶段提交是经典的分布式系统问题,并不是MySQL独有的。如果必须要说明这样设计的原因,那就是事务的持久性问题。</p>
<p>对于InnoDB来说,如果redo log提交完成,事务就不能回滚。而如果redo log直接提交,然后binlog写入失败,InnoDB又无法回滚,那么数据和binlog又不一致了。</p>
<h3 id="追问5只用binlog来支持崩溃恢复可以吗">追问5:只用binlog来支持崩溃恢复可以吗?</h3>
<p>即把流程改为:… -&gt; 数据更新到内存 -&gt; 写binlog -&gt; 提交事务。</p>
<p>答案是不可以的。</p>
<p>从历史原因说,InnoDB不是MySQL的原生存储引擎,而MyISAM设计之初就没有支持崩溃恢复。</p>
<p>从实现上说,如果只用binlog:</p>
<div align="center"><img src="https://img2024.cnblogs.com/blog/3389949/202507/3389949-20250716184526405-1914561318.png" width="20%"></div>
<p>如图,假如在binlog2写完但整个事务还没有commit时,MySQL发生crash,重启后引擎内部事务2会回滚,但对于事务1来说,系统认为已经提交完成,不会再应用一次binlog1。</p>
<p>如果InnoDB使用的是WAL技术,执行事务的时候,写完内存和日志,事务就算完成。如果之后崩溃,要依赖日志来恢复数据页。那么这种情况下,由于不应用binlog1,事务1也可能丢失,而且是数据页级别的丢失。此时,binlog里没有记录数据页的更新细节,是补不回来的。</p>
<h3 id="追问6那能只用redo-log不要binlog吗">追问6:那能只用redo log,不要binlog吗?</h3>
<p>如果只从崩溃恢复的角度来说是可以的。</p>
<p>使用binlog主要是它有着redo log无法替代的功能:</p>
<ul>
<li>
<p>归档。redo log是循环写,历史日志无法保留,起不到归档的作用。</p>
</li>
<li>
<p>MySQL高可用的基础就是binlog复制。</p>
</li>
<li>
<p>很多公司有异构系统,这些系统靠消费MySQL的binlog来更新自己的数据。关掉binlog的话,这些下游系统就没法输入。</p>
</li>
</ul>
<h3 id="追问7redo-log一般设置多大">追问7:redo log一般设置多大?</h3>
<p>如果redo log太小,会导致很快写满。</p>
<p>对于几个TB的磁盘,一般将redo log设置为4个文件,每个文件1GB。</p>
<h3 id="追问8正常运行的实例数据写入后的最终落盘是从redo-log更新过来的还是从buffer-pool更新过来的">追问8:正常运行的实例,数据写入后的最终落盘,是从redo log更新过来的还是从buffer pool更新过来的?</h3>
<p>redo log并没有记录数据页的完整数据,所以它并没有能力自己去更新磁盘数据页,也就不存在“数据最终落盘,是由redo log更新过去”的情况。</p>
<ul>
<li>
<p>如果是正常运行的实例,数据页被修改以后,跟磁盘的数据页不一致,称为脏页。最终数据落盘,就是把内存中的数据页写盘;</p>
</li>
<li>
<p>在崩溃恢复场景,如果一个数据页在崩溃恢复时丢失了更新,InnoDB会将其读到内存,然后让redo log更新内存内容。更新完成后,同上。</p>
</li>
</ul>
<h3 id="追问9redo-log-buffer是什么在写入时是先修改内存还是先写redo-log文件">追问9:redo log buffer是什么?在写入时,是先修改内存,还是先写redo log文件?</h3>
<p>redo log buffer是一块内存,是在事务还没commit时,先保存redo日志内容的。</p>
<p>真正把日志写到redo log文件(文件名为ib_logfile+数字),是在执行commit语句时候完成的。</p><br><br>
来源:https://www.cnblogs.com/san-mu/p/18988173
頁: [1]
查看完整版本: MySQL 15 日志相关问题追问