<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>MySQL是怎样运行的 on 随记</title><link>https://blog.liuyi2026.asia/zh/tags/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/</link><description>Recent content in MySQL是怎样运行的 on 随记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Sat, 18 Jan 2025 22:30:12 +0800</lastBuildDate><atom:link href="https://blog.liuyi2026.asia/zh/tags/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/index.xml" rel="self" type="application/rss+xml"/><item><title>第26章_写作本书时用到的一些重要的参考资料</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC26%E7%AB%A0_%E5%86%99%E4%BD%9C%E6%9C%AC%E4%B9%A6%E6%97%B6%E7%94%A8%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E9%87%8D%E8%A6%81%E7%9A%84%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99/</link><pubDate>Sat, 18 Jan 2025 22:30:12 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC26%E7%AB%A0_%E5%86%99%E4%BD%9C%E6%9C%AC%E4%B9%A6%E6%97%B6%E7%94%A8%E5%88%B0%E7%9A%84%E4%B8%80%E4%BA%9B%E9%87%8D%E8%A6%81%E7%9A%84%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99/</guid><description>&lt;p>第26章 写作本书时用到的一些重要的参考资料&lt;/p>
&lt;h1 id="感谢">感谢&lt;a class="anchor" href="#%e6%84%9f%e8%b0%a2">#&lt;/a>&lt;/h1>
&lt;p>我不生产知识，只是知识的搬运工。写作本小册的时间主要用在了两个方面：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>搞清楚事情的本质是什么。&lt;/p>
&lt;p>这个过程就是研究源码、书籍和资料。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>如何把我已经知道的知识表达出来。&lt;/p>
&lt;p>这个过程就是我不停的在地上走过来走过去，梳理知识结构，斟酌用词用句，不停的将已经写好的文章推倒重来，只是想给大家一个不错的用户体验。&lt;/p>
&lt;p>这两个方面用的时间基本上是一半一半吧，在搞清楚事情的本质是什么阶段，除了直接阅读&lt;code>MySQL&lt;/code>的源码之外，查看参考资料也是一种比较偷懒的学习方式。本书只是&lt;code>MySQL&lt;/code>进阶的一个入门，想了解更多关于&lt;code>MySQL&lt;/code>的知识，大家可以从下面这些资料里找点灵感。&lt;/p>
&lt;h1 id="一些链接">一些链接&lt;a class="anchor" href="#%e4%b8%80%e4%ba%9b%e9%93%be%e6%8e%a5">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>MySQL官方文档：
&lt;a target="_blank" href="https://dev.mysql.com/doc/refman/5.7/en/">https://dev.mysql.com/doc/refman/5.7/en/&lt;/a>&lt;/p>
&lt;p>&lt;code>MySQL&lt;/code>官方文档是写作本书时参考最多的一个资料。说实话，文档写的非常通俗易懂，唯一的缺点就是太长了，导致大家看的时候无从下手。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>MySQL Internals Manual：
&lt;a target="_blank" href="https://dev.mysql.com/doc/internals/en/">https://dev.mysql.com/doc/internals/en/&lt;/a>&lt;/p>
&lt;p>介绍MySQL如何实现各种功能的文档，写的比较好，但是太少了，有很多章节直接跳过了。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>何登成的github：
&lt;a target="_blank" href="https://github.com/hedengcheng/tech">https://github.com/hedengcheng/tech&lt;/a>&lt;/p>
&lt;p>登博的博客非常好，对事务、优化这讨论的细节也非常多，不过由于大多是PPT结构，字太少，对上下文不清楚的同学可能会一脸懵逼。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>orczhou的博客：
&lt;a target="_blank" href="http://www.orczhou.com/">http://www.orczhou.com/&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>Jeremy Cole的博客：
&lt;a target="_blank" href="https://blog.jcole.us/innodb/">https://blog.jcole.us/innodb/&lt;/a>&lt;/p>
&lt;p>Jeremy Cole大神不仅写作了&lt;code>innodb_ruby&lt;/code>这个非常棒的解析&lt;code>InnoDB&lt;/code>存储结构的工具，还对这些存储结构写了一系列的博客，在我几乎要放弃深入研究表空间结构的时候，是他老人家的博客把我又从深渊里拉了回来。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>那海蓝蓝（李海翔）的博客：
&lt;a target="_blank" href="https://blog.csdn.net/fly2nn">https://blog.csdn.net/fly2nn&lt;/a>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>taobao月报：
&lt;a target="_blank" href="http://mysql.taobao.org/monthly/">http://mysql.taobao.org/monthly/&lt;/a>&lt;/p>
&lt;p>因为MySQL的源码非常多，经常让大家无从下手，而taobao月报就是一个非常好的源码阅读指南。&lt;/p>
&lt;p>&lt;code>吐槽一下，这个taobao月报也只能当作源码阅读指南看，如果真的不看源码光看月报，那只能当作天书看，十有八九被绕进去出不来了。&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>MySQL Server Blog：
&lt;a target="_blank" href="http://mysqlserverteam.com/">http://mysqlserverteam.com/&lt;/a>&lt;/p>
&lt;p>MySQL team的博客，一手资料，在我不知道看什么的时候给了很多启示。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>mysql_lover的博客：
&lt;a target="_blank" href="https://blog.csdn.net/mysql_lover/">https://blog.csdn.net/mysql_lover/&lt;/a>&lt;/p>
&lt;ul>
&lt;li>Jorgen&amp;rsquo;s point of view：
&lt;a target="_blank" href="https://jorgenloland.blogspot.com/">https://jorgenloland.blogspot.com/&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>mariadb的关于查询优化的文档：
&lt;a target="_blank" href="https://mariadb.com/kb/en/library/query-optimizations/">https://mariadb.com/kb/en/library/query-optimizations/&lt;/a>&lt;/p>
&lt;p>不得不说mariadb的文档相比MySQL的来说就非常有艺术性了（里边儿有很多漂亮的插图），我很怀疑MySQL文档是程序员直接写的，mariadb的文档是产品经理写的。当我们想研究某个功能的原理，在MySQL文档干巴巴的说明中找不到头脑时，可以参考一下mariadb娓娓道来的风格。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>Reconstructing Data Manipulation Queries from Redo Logs：
&lt;a target="_blank" href="https://www.sba-research.org/wp-content/uploads/publications/WSDF2012_InnoDB.pdf">https://www.sba-research.org/wp-content/uploads/publications/WSDF2012_InnoDB.pdf&lt;/a>&lt;/p>
&lt;ul>
&lt;li>关于InnoDB事务的一个PPT：
&lt;a target="_blank" href="https://mariadb.org/wp-content/uploads/2018/02/Deep-Dive_-InnoDB-Transactions-and-Write-Paths.pdf">https://mariadb.org/wp-content/uploads/2018/02/Deep-Dive_-InnoDB-Transactions-and-Write-Paths.pdf&lt;/a>&lt;/li>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>非官方优化文档：
&lt;a target="_blank" href="http://www.unofficialmysqlguide.com/optimizer-trace.html">http://www.unofficialmysqlguide.com/optimizer-trace.html&lt;/a>&lt;/p>
&lt;p>这个文档非常好，非常非常好～&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>MySQL8.0的源码文档：
&lt;a target="_blank" href="https://dev.mysql.com/doc/dev/mysql-server">https://dev.mysql.com/doc/dev/mysql-server&lt;/a>&lt;/p>
&lt;h1 id="一些书籍">一些书籍&lt;a class="anchor" href="#%e4%b8%80%e4%ba%9b%e4%b9%a6%e7%b1%8d">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>《数据库查询优化器的艺术》李海翔著&lt;/p>
&lt;p>大家可以把这本书当作源码观看指南来看，不过讲的是5.6的源码，5.7里重构了一些，不过大体的思路还是可以参考的。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>《MySQL运维内参》周彦伟、王竹峰、强昌金著&lt;/p></description></item><item><title>第25章_工作面试老大难-锁</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC25%E7%AB%A0_%E5%B7%A5%E4%BD%9C%E9%9D%A2%E8%AF%95%E8%80%81%E5%A4%A7%E9%9A%BE-%E9%94%81/</link><pubDate>Sat, 18 Jan 2025 22:30:10 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC25%E7%AB%A0_%E5%B7%A5%E4%BD%9C%E9%9D%A2%E8%AF%95%E8%80%81%E5%A4%A7%E9%9A%BE-%E9%94%81/</guid><description>&lt;p>第25章 工作面试老大难-锁&lt;/p>
&lt;h1 id="解决并发事务带来问题的两种基本方式">解决并发事务带来问题的两种基本方式&lt;a class="anchor" href="#%e8%a7%a3%e5%86%b3%e5%b9%b6%e5%8f%91%e4%ba%8b%e5%8a%a1%e5%b8%a6%e6%9d%a5%e9%97%ae%e9%a2%98%e7%9a%84%e4%b8%a4%e7%a7%8d%e5%9f%ba%e6%9c%ac%e6%96%b9%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>上一章介绍了事务并发执行时可能带来的各种问题，并发事务访问相同记录的情况大致可以划分为3种：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>读-读&lt;/code>情况：即并发事务相继读取相同的记录。&lt;/p>
&lt;p>读取操作本身不会对记录有一毛钱影响，并不会引起什么问题，所以允许这种情况的发生。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>写-写&lt;/code>情况：即并发事务相继对相同的记录做出改动。&lt;/p>
&lt;p>我们前面说过，在这种情况下会发生&lt;code>脏写&lt;/code>的问题，任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时，需要让它们排队执行，这个排队的过程其实是通过&lt;code>锁&lt;/code>来实现的。这个所谓的&lt;code>锁&lt;/code>其实是一个内存中的结构，在事务执行前本来是没有锁的，也就是说一开始是没有&lt;code>锁结构&lt;/code>和记录进行关联的，如图所示：&lt;/p>
&lt;p>&lt;img src="img/25-01.png" alt="" />&lt;/p>
&lt;p>当一个事务想对这条记录做改动时，首先会看看内存中有没有与这条记录关联的&lt;code>锁结构&lt;/code>，当没有的时候就会在内存中生成一个&lt;code>锁结构&lt;/code>与之关联。比方说事务&lt;code>T1&lt;/code>要对这条记录做改动，就需要生成一个&lt;code>锁结构&lt;/code>与之关联：&lt;/p>
&lt;p>&lt;img src="img/25-02.png" alt="" />&lt;/p>
&lt;p>其实在&lt;code>锁结构&lt;/code>里有很多信息，不过为了简化理解，我们现在只把两个比较重要的属性拿了出来： - &lt;code>trx信息&lt;/code>：代表这个锁结构是哪个事务生成的。 - &lt;code>is_waiting&lt;/code>：代表当前事务是否在等待。&lt;/p>
&lt;p>如图所示，当事务&lt;code>T1&lt;/code>改动了这条记录后，就生成了一个&lt;code>锁结构&lt;/code>与该记录关联，因为之前没有别的事务为这条记录加锁，所以&lt;code>is_waiting&lt;/code>属性就是&lt;code>false&lt;/code>，我们把这个场景就称之为&lt;strong>获取锁成功，或者加锁成功&lt;/strong>，然后就可以继续执行操作了。&lt;/p>
&lt;p>在事务&lt;code>T1&lt;/code>提交之前，另一个事务&lt;code>T2&lt;/code>也想对该记录做改动，那么先去看看有没有&lt;code>锁结构&lt;/code>与这条记录关联，发现有一个&lt;code>锁结构&lt;/code>与之关联后，然后也生成了一个&lt;code>锁结构&lt;/code>与这条记录关联，不过&lt;code>锁结构&lt;/code>的&lt;code>is_waiting&lt;/code>属性值为&lt;code>true&lt;/code>，表示当前事务需要等待，我们把这个场景就称之为&lt;strong>获取锁失败，或者加锁失败，或者没有成功的获取到锁&lt;/strong>，画个图表示就是这样：&lt;/p>
&lt;p>&lt;img src="img/25-03.png" alt="" />&lt;/p>
&lt;p>在事务&lt;code>T1&lt;/code>提交之后，就会把该事务生成的&lt;code>锁结构&lt;/code>释放掉，然后看看还有没有别的事务在等待获取锁，发现了事务&lt;code>T2&lt;/code>还在等待获取锁，所以把事务&lt;code>T2&lt;/code>对应的锁结构的&lt;code>is_waiting&lt;/code>属性设置为&lt;code>false&lt;/code>，然后把该事务对应的线程唤醒，让它继续执行，此时事务&lt;code>T2&lt;/code>就算获取到锁了。效果图就是这样：&lt;/p>
&lt;p>&lt;img src="img/25-04.png" alt="" />&lt;/p>
&lt;p>我们总结一下后续内容中可能用到的几种说法，以免大家混淆：&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>不加锁&lt;/p>
&lt;p>意思就是不需要在内存中生成对应的&lt;code>锁结构&lt;/code>，可以直接执行操作。&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>获取锁成功，或者加锁成功&lt;/p>
&lt;p>意思就是在内存中生成了对应的&lt;code>锁结构&lt;/code>，而且锁结构的&lt;code>is_waiting&lt;/code>属性为&lt;code>false&lt;/code>，也就是事务可以继续执行操作。&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>获取锁失败，或者加锁失败，或者没有获取到锁&lt;/p>
&lt;p>意思就是在内存中生成了对应的&lt;code>锁结构&lt;/code>，不过锁结构的&lt;code>is_waiting&lt;/code>属性为&lt;code>true&lt;/code>，也就是事务需要等待，不可以继续执行操作。&lt;/p>
&lt;p>&lt;code>小贴士：这里只是对锁结构做了一个非常简单的描述，我们后边会详细介绍介绍锁结构的，稍安勿躁。&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>读-写&lt;/code>或&lt;code>写-读&lt;/code>情况：也就是一个事务进行读取操作，另一个进行改动操作。&lt;/p>
&lt;p>我们前面说过，这种情况下可能发生&lt;code>脏读&lt;/code>、&lt;code>不可重复读&lt;/code>、&lt;code>幻读&lt;/code>的问题。&lt;/p>
&lt;p>&lt;code>小贴士：幻读问题的产生是因为某个事务读了一个范围的记录，之后别的事务在该范围内插入了新记录，该事务再次读取该范围的记录时，可以读到新插入的记录，所以幻读问题准确的说并不是因为读取和写入一条相同记录而产生的，这一点要注意一下。&lt;/code>&lt;/p>
&lt;p>&lt;code>SQL标准&lt;/code>规定不同隔离级别下可能发生的问题不一样： - 在&lt;code>READ UNCOMMITTED&lt;/code>隔离级别下，&lt;code>脏读&lt;/code>、&lt;code>不可重复读&lt;/code>、&lt;code>幻读&lt;/code>都可能发生。 - 在&lt;code>READ COMMITTED&lt;/code>隔离级别下，&lt;code>不可重复读&lt;/code>、&lt;code>幻读&lt;/code>可能发生，&lt;code>脏读&lt;/code>不可以发生。 - 在&lt;code>REPEATABLE READ&lt;/code>隔离级别下，&lt;code>幻读&lt;/code>可能发生，&lt;code>脏读&lt;/code>和&lt;code>不可重复读&lt;/code>不可以发生。 - 在&lt;code>SERIALIZABLE&lt;/code>隔离级别下，上述问题都不可以发生。&lt;/p>
&lt;p>不过各个数据库厂商对&lt;code>SQL标准&lt;/code>的支持都可能不一样，与&lt;code>SQL标准&lt;/code>不同的一点就是，&lt;code>MySQL&lt;/code>在&lt;code>REPEATABLE READ&lt;/code>隔离级别实际上就已经解决了&lt;code>幻读&lt;/code>问题。&lt;/p>
&lt;p>怎么解决&lt;code>脏读&lt;/code>、&lt;code>不可重复读&lt;/code>、&lt;code>幻读&lt;/code>这些问题呢？其实有两种可选的解决方案：&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>方案一：读操作利用多版本并发控制（&lt;code>MVCC&lt;/code>），写操作进行&lt;code>加锁&lt;/code>。&lt;/p>
&lt;p>所谓的&lt;code>MVCC&lt;/code>我们在前一章有过详细的描述，就是通过生成一个&lt;code>ReadView&lt;/code>，然后通过&lt;code>ReadView&lt;/code>找到符合条件的记录版本（历史版本是由&lt;code>undo日志&lt;/code>构建的），其实就像是在生成&lt;code>ReadView&lt;/code>的那个时刻做了一次时间静止（就像用相机拍了一个快照），查询语句只能读到在生成&lt;code>ReadView&lt;/code>之前已提交事务所做的更改，在生成&lt;code>ReadView&lt;/code>之前未提交的事务或者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的记录，读记录的历史版本和改动记录的最新版本本身并不冲突，也就是采用&lt;code>MVCC&lt;/code>时，&lt;code>读-写&lt;/code>操作并不冲突。&lt;/p>
&lt;p>&lt;code>小贴士：我们说过普通的SELECT语句在READ COMMITTED和REPEATABLE READ隔离级别下会使用到MVCC读取记录。在READ COMMITTED隔离级别下，一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView，ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改，也就是避免了脏读现象；REPEATABLE READ隔离级别下，一个事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView，之后的SELECT操作都复用这个ReadView，这样也就避免了不可重复读和幻读的问题。&lt;/code>&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>方案二：读、写操作都采用&lt;code>加锁&lt;/code>的方式。&lt;/p></description></item><item><title>第24章_一条记录的多幅面孔-事务的隔离级别与MVCC</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC24%E7%AB%A0_%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E7%9A%84%E5%A4%9A%E5%B9%85%E9%9D%A2%E5%AD%94-%E4%BA%8B%E5%8A%A1%E7%9A%84%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB%E4%B8%8EMVCC/</link><pubDate>Sat, 18 Jan 2025 22:30:09 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC24%E7%AB%A0_%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E7%9A%84%E5%A4%9A%E5%B9%85%E9%9D%A2%E5%AD%94-%E4%BA%8B%E5%8A%A1%E7%9A%84%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB%E4%B8%8EMVCC/</guid><description>&lt;p>第24章 一条记录的多幅面孔-事务的隔离级别与MVCC&lt;/p>
&lt;h1 id="事前准备">事前准备&lt;a class="anchor" href="#%e4%ba%8b%e5%89%8d%e5%87%86%e5%a4%87">#&lt;/a>&lt;/h1>
&lt;p>为了故事的顺利发展，我们需要创建一个表： &lt;code>CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY KEY (number) ) Engine=InnoDB CHARSET=utf8;&lt;/code> &lt;code>小贴士：注意我们把这个hero表的主键命名为number，而不是id，主要是想和后边要用到的事务id做区别，大家不用大惊小怪～&lt;/code> 然后向这个表里插入一条数据： &lt;code>INSERT INTO hero VALUES(1, '刘备', '蜀');&lt;/code> 现在表里的数据就是这样的： &lt;code>mysql&amp;gt; SELECT * FROM hero; +--------+--------+---------+ | number | name | country | +--------+--------+---------+ | 1 | 刘备 | 蜀 | +--------+--------+---------+ 1 row in set (0.00 sec)&lt;/code>&lt;/p>
&lt;h1 id="事务隔离级别">事务隔离级别&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e9%9a%94%e7%a6%bb%e7%ba%a7%e5%88%ab">#&lt;/a>&lt;/h1>
&lt;p>我们知道&lt;code>MySQL&lt;/code>是一个&lt;code>客户端／服务器&lt;/code>架构的软件，对于同一个服务器来说，可以有若干个客户端与之连接，每个客户端与服务器连接上之后，就可以称之为一个会话（&lt;code>Session&lt;/code>）。每个客户端都可以在自己的会话中向服务器发出请求语句，一个请求语句可能是某个事务的一部分，也就是对于服务器来说可能同时处理多个事务。在事务简介的章节中我们说过事务有一个称之为&lt;code>隔离性&lt;/code>的特性，理论上在某个事务对某个数据进行访问时，其他事务应该进行排队，当该事务提交之后，其他事务才可以继续访问这个数据。但是这样子的话对性能影响太大，我们既想保持事务的&lt;code>隔离性&lt;/code>，又想让服务器在处理访问同一数据的多个事务时性能尽量高些，鱼和熊掌不可得兼，舍一部分&lt;code>隔离性&lt;/code>而取性能者也。&lt;/p>
&lt;h2 id="事务并发执行遇到的问题">事务并发执行遇到的问题&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e5%b9%b6%e5%8f%91%e6%89%a7%e8%a1%8c%e9%81%87%e5%88%b0%e7%9a%84%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;p>怎么个舍弃法呢？我们先得看一下访问相同数据的事务在不保证串行执行（也就是执行完一个再执行另一个）的情况下可能会出现哪些问题：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>脏写（&lt;code>Dirty Write&lt;/code>）&lt;/p>
&lt;p>如果&lt;strong>一个事务修改了另一个未提交事务修改过的数据&lt;/strong>，那就意味着发生了&lt;code>脏写&lt;/code>，示意图如下：&lt;/p>
&lt;p>&lt;img src="img/24-01.png" alt="" />&lt;/p>
&lt;p>如上图，&lt;code>Session A&lt;/code>和&lt;code>Session B&lt;/code>各开启了一个事务，&lt;code>Session B&lt;/code>中的事务先将&lt;code>number&lt;/code>列为&lt;code>1&lt;/code>的记录的&lt;code>name&lt;/code>列更新为&lt;code>'关羽'&lt;/code>，然后&lt;code>Session A&lt;/code>中的事务接着又把这条&lt;code>number&lt;/code>列为&lt;code>1&lt;/code>的记录的&lt;code>name&lt;/code>列更新为&lt;code>张飞&lt;/code>。如果之后&lt;code>Session B&lt;/code>中的事务进行了回滚，那么&lt;code>Session A&lt;/code>中的更新也将不复存在，这种现象就称之为&lt;code>脏写&lt;/code>。这时&lt;code>Session A&lt;/code>中的事务就很懵逼，我明明把数据更新了，最后也提交事务了，怎么到最后说自己什么也没干呢？&lt;/p></description></item><item><title>第23章_后悔了怎么办-undo日志(下)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC23%E7%AB%A0_%E5%90%8E%E6%82%94%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E-undo%E6%97%A5%E5%BF%97%E4%B8%8B/</link><pubDate>Sat, 18 Jan 2025 22:30:08 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC23%E7%AB%A0_%E5%90%8E%E6%82%94%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E-undo%E6%97%A5%E5%BF%97%E4%B8%8B/</guid><description>&lt;p>第23章 后悔了怎么办-undo日志（下）&lt;/p>
&lt;p>上一章我们主要介绍了为什么需要&lt;code>undo日志&lt;/code>，以及&lt;code>INSERT&lt;/code>、&lt;code>DELETE&lt;/code>、&lt;code>UPDATE&lt;/code>这些会对数据做改动的语句都会产生什么类型的&lt;code>undo日志&lt;/code>，还有不同类型的&lt;code>undo日志&lt;/code>的具体格式是什么。本章会继续介绍这些&lt;code>undo日志&lt;/code>会被具体写到什么地方，以及在写入过程中需要注意的一些问题。&lt;/p>
&lt;h1 id="通用链表结构">通用链表结构&lt;a class="anchor" href="#%e9%80%9a%e7%94%a8%e9%93%be%e8%a1%a8%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;p>在写入&lt;code>undo日志&lt;/code>的过程中会使用到多个链表，很多链表都有同样的节点结构，如图所示：&lt;/p>
&lt;p>&lt;img src="img/23-01.png" alt="" />&lt;/p>
&lt;p>在某个表空间内，我们可以通过一个页的页号和在页内的偏移量来唯一定位一个节点的位置，这两个信息也就相当于指向这个节点的一个指针。所以： - &lt;code>Pre Node Page Number&lt;/code>和&lt;code>Pre Node Offset&lt;/code>的组合就是指向前一个节点的指针 - &lt;code>Next Node Page Number&lt;/code>和&lt;code>Next Node Offset&lt;/code>的组合就是指向后一个节点的指针。&lt;/p>
&lt;p>整个&lt;code>List Node&lt;/code>占用&lt;code>12&lt;/code>个字节的存储空间。&lt;/p>
&lt;p>为了更好的管理链表，设计&lt;code>InnoDB&lt;/code>的大佬还提出了一个基节点的结构，里边存储了这个链表的&lt;code>头节点&lt;/code>、&lt;code>尾节点&lt;/code>以及链表长度信息，基节点的结构示意图如下：&lt;/p>
&lt;p>&lt;img src="img/23-02.png" alt="" />&lt;/p>
&lt;p>其中： - &lt;code>List Length&lt;/code>表明该链表一共有多少节点。 - &lt;code>First Node Page Number&lt;/code>和&lt;code>First Node Offset&lt;/code>的组合就是指向链表头节点的指针。 - &lt;code>Last Node Page Number&lt;/code>和&lt;code>Last Node Offset&lt;/code>的组合就是指向链表尾节点的指针。&lt;/p>
&lt;p>整个&lt;code>List Base Node&lt;/code>占用&lt;code>16&lt;/code>个字节的存储空间。&lt;/p>
&lt;p>所以使用&lt;code>List Base Node&lt;/code>和&lt;code>List Node&lt;/code>这两个结构组成的链表的示意图就是这样：&lt;/p>
&lt;p>&lt;img src="img/23-03.png" alt="" />&lt;/p>
&lt;p>&lt;code>小贴士：上述链表结构我们在前面的文章中频频提到，尤其是在表空间那一章重点描述过，不过我不敢奢求大家都记住了，所以在这里又强调一遍，希望大家不要嫌我烦，我只是怕大家忘了学习后续内容吃力而已～&lt;/code>&lt;/p>
&lt;h1 id="fil_page_undo_log页面">FIL_PAGE_UNDO_LOG页面&lt;a class="anchor" href="#fil_page_undo_log%e9%a1%b5%e9%9d%a2">#&lt;/a>&lt;/h1>
&lt;p>我们前面介绍表空间的时候说过，表空间其实是由许许多多的页面构成的，页面默认大小为&lt;code>16KB&lt;/code>。这些页面有不同的类型，比如类型为&lt;code>FIL_PAGE_INDEX&lt;/code>的页面用于存储聚簇索引以及二级索引，类型为&lt;code>FIL_PAGE_TYPE_FSP_HDR&lt;/code>的页面用于存储表空间头部信息的，还有其他各种类型的页面，其中有一种称之为&lt;code>FIL_PAGE_UNDO_LOG&lt;/code>类型的页面是专门用来存储&lt;code>undo日志&lt;/code>的，这种类型的页面的通用结构如下图所示（以默认的&lt;code>16KB&lt;/code>大小为例）：&lt;/p>
&lt;p>&lt;img src="img/23-04.png" alt="" />&lt;/p>
&lt;p>“类型为&lt;code>FIL_PAGE_UNDO_LOG&lt;/code>的页”这种说法太绕口，以后我们就简称为&lt;code>Undo页面&lt;/code>了。上图中的&lt;code>File Header&lt;/code>和&lt;code>File Trailer&lt;/code>是各种页面都有的通用结构，我们前面介绍过很多遍了，这里就不赘述了（忘记了的可以到讲述数据页结构或者表空间的章节中查看）。&lt;code>Undo Page Header&lt;/code>是&lt;code>Undo页面&lt;/code>所特有的，我们来看一下它的结构：&lt;/p>
&lt;p>&lt;img src="img/23-05.png" alt="" />&lt;/p>
&lt;p>其中各个属性的意思如下：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>TRX_UNDO_PAGE_TYPE&lt;/code>：本页面准备存储什么种类的&lt;code>undo日志&lt;/code>。&lt;/p>
&lt;p>我们前面介绍了好几种类型的&lt;code>undo日志&lt;/code>，它们可以被分为两个大类：&lt;/p></description></item><item><title>第22章_后悔了怎么办-undo日志(上)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC22%E7%AB%A0_%E5%90%8E%E6%82%94%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E-undo%E6%97%A5%E5%BF%97%E4%B8%8A/</link><pubDate>Sat, 18 Jan 2025 22:30:06 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC22%E7%AB%A0_%E5%90%8E%E6%82%94%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E-undo%E6%97%A5%E5%BF%97%E4%B8%8A/</guid><description>&lt;p>第22章 后悔了怎么办-undo日志（上）&lt;/p>
&lt;h1 id="事务回滚的需求">事务回滚的需求&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e5%9b%9e%e6%bb%9a%e7%9a%84%e9%9c%80%e6%b1%82">#&lt;/a>&lt;/h1>
&lt;p>我们说过&lt;code>事务&lt;/code>需要保证&lt;code>原子性&lt;/code>，也就是事务中的操作要么全部完成，要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况，比如： - 情况一：事务执行过程中可能遇到各种错误，比如服务器本身的错误，操作系统错误，甚至是突然断电导致的错误。 - 情况二：程序员可以在事务执行过程中手动输入&lt;code>ROLLBACK&lt;/code>语句结束当前的事务的执行。&lt;/p>
&lt;p>这两种情况都会导致事务执行到一半就结束，但是事务执行过程中可能已经修改了很多东西，为了保证事务的原子性，我们需要把东西改回原先的样子，这个过程就称之为&lt;code>回滚&lt;/code>（英文名：&lt;code>rollback&lt;/code>），这样就可以造成一个假象：&lt;strong>这个事务看起来什么都没做&lt;/strong>，所以符合&lt;code>原子性&lt;/code>要求。&lt;/p>
&lt;p>小时候我非常痴迷于象棋，总是想找厉害的大人下棋，赢棋是不可能赢棋的，这辈子都不可能赢棋的，又不想认输，只能偷偷的悔棋才能勉强玩的下去。&lt;code>悔棋&lt;/code>就是一种非常典型的&lt;code>回滚&lt;/code>操作，比如棋子往前走两步，&lt;code>悔棋&lt;/code>对应的操作就是向后走两步；比如棋子往左走一步，&lt;code>悔棋&lt;/code>对应的操作就是向右走一步。数据库中的回滚跟&lt;code>悔棋&lt;/code>差不多，你插入了一条记录，&lt;code>回滚&lt;/code>操作对应的就是把这条记录删除掉；你更新了一条记录，&lt;code>回滚&lt;/code>操作对应的就是把该记录更新为旧值；你删除了一条记录，&lt;code>回滚&lt;/code>操作对应的自然就是把该记录再插进去。说的貌似很简单的样子[手动偷笑😏]。&lt;/p>
&lt;p>从上面的描述中我们已经能隐约感觉到，每当我们要对一条记录做改动时（这里的&lt;code>改动&lt;/code>可以指&lt;code>INSERT&lt;/code>、&lt;code>DELETE&lt;/code>、&lt;code>UPDATE&lt;/code>），都需要留一手 —— &lt;strong>把回滚时所需的东西都给记下来&lt;/strong>。比方说： - 你插入一条记录时，至少要把这条记录的主键值记下来，之后回滚的时候只需要把这个主键值对应的记录删掉就好了。 - 你删除了一条记录，至少要把这条记录中的内容都记下来，这样之后回滚时再把由这些内容组成的记录插入到表中就好了。 - 你修改了一条记录，至少要把修改这条记录前的旧值都记录下来，这样之后回滚时再把这条记录更新为旧值就好了。&lt;/p>
&lt;p>设计数据库的大佬把这些为了回滚而记录的这些东东称之为撤销日志，英文名为&lt;code>undo log&lt;/code>，我们也可以土洋结合，称之为&lt;code>undo日志&lt;/code>。这里需要注意的一点是，由于查询操作（&lt;code>SELECT&lt;/code>）并不会修改任何用户记录，所以在查询操作执行时，并不需要记录相应的&lt;code>undo日志&lt;/code>。在真实的&lt;code>InnoDB&lt;/code>中，&lt;code>undo日志&lt;/code>其实并不像我们上面所说的那么简单，不同类型的操作产生的&lt;code>undo日志&lt;/code>的格式也是不同的，不过先暂时把这些容易让人脑子糊的具体细节放一放，我们先回过头来看看&lt;code>事务id&lt;/code>是个神马玩意儿。&lt;/p>
&lt;h1 id="事务id">事务id&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1id">#&lt;/a>&lt;/h1>
&lt;h2 id="给事务分配id的时机">给事务分配id的时机&lt;a class="anchor" href="#%e7%bb%99%e4%ba%8b%e5%8a%a1%e5%88%86%e9%85%8did%e7%9a%84%e6%97%b6%e6%9c%ba">#&lt;/a>&lt;/h2>
&lt;p>我们前面在介绍&lt;code>事务简介&lt;/code>时说过，一个事务可以是一个只读事务，或者是一个读写事务：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>我们可以通过&lt;code>START TRANSACTION READ ONLY&lt;/code>语句开启一个只读事务。&lt;/p>
&lt;p>在只读事务中不可以对普通的表（其他事务也能访问到的表）进行增、删、改操作，但可以对临时表做增、删、改操作。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>我们可以通过&lt;code>START TRANSACTION READ WRITE&lt;/code>语句开启一个读写事务，或者使用&lt;code>BEGIN&lt;/code>、&lt;code>START TRANSACTION&lt;/code>语句开启的事务默认也算是读写事务。&lt;/p>
&lt;p>在读写事务中可以对表执行增删改查操作。&lt;/p>
&lt;p>如果某个事务执行过程中对某个表执行了增、删、改操作，那么&lt;code>InnoDB&lt;/code>存储引擎就会给它分配一个独一无二的&lt;code>事务id&lt;/code>，分配方式如下：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>对于只读事务来说，只有在它第一次对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个&lt;code>事务id&lt;/code>，否则的话是不分配&lt;code>事务id&lt;/code>的。&lt;/p>
&lt;p>&lt;code>小贴士：我们前面说过对某个查询语句执行EXPLAIN分析它的查询计划时，有时候在Extra列会看到Using temporary的提示，这个表明在执行该查询语句时会用到内部临时表。这个所谓的内部临时表和我们手动用CREATE TEMPORARY TABLE创建的用户临时表并不一样，在事务回滚时并不需要把执行SELECT语句过程中用到的内部临时表也回滚，在执行SELECT语句用到内部临时表时并不会为它分配事务id。&lt;/code> - 对于读写事务来说，只有在它第一次对某个表（包括用户创建的临时表）执行增、删、改操作时才会为这个事务分配一个&lt;code>事务id&lt;/code>，否则的话也是不分配&lt;code>事务id&lt;/code>的。&lt;/p>
&lt;p>有的时候虽然我们开启了一个读写事务，但是在这个事务中全是查询语句，并没有执行增、删、改的语句，那也就意味着这个事务并不会被分配一个&lt;code>事务id&lt;/code>。&lt;/p>
&lt;p>说了半天，&lt;code>事务id&lt;/code>有什么子用？这个先保密，后边会一步步的详细介绍。现在只要知道只有在事务对表中的记录做改动时才会为这个事务分配一个唯一的&lt;code>事务id&lt;/code>。 &lt;code>小贴士：上面描述的事务id分配策略是针对MySQL 5.7来说的，前面的版本的分配方式可能不同～&lt;/code>&lt;/p>
&lt;h2 id="事务id是怎么生成的">事务id是怎么生成的&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1id%e6%98%af%e6%80%8e%e4%b9%88%e7%94%9f%e6%88%90%e7%9a%84">#&lt;/a>&lt;/h2>
&lt;p>这个&lt;code>事务id&lt;/code>本质上就是一个数字，它的分配策略和我们前面提到的对隐藏列&lt;code>row_id&lt;/code>（当用户没有为表创建主键和&lt;code>UNIQUE&lt;/code>键时&lt;code>InnoDB&lt;/code>自动创建的列）的分配策略大抵相同，具体策略如下： - 服务器会在内存中维护一个全局变量，每当需要为某个事务分配一个&lt;code>事务id&lt;/code>时，就会把该变量的值当作&lt;code>事务id&lt;/code>分配给该事务，并且把该变量自增1。 - 每当这个变量的值为&lt;code>256&lt;/code>的倍数时，就会将该变量的值刷新到系统表空间的页号为&lt;code>5&lt;/code>的页面中一个称之为&lt;code>Max Trx ID&lt;/code>的属性处，这个属性占用&lt;code>8&lt;/code>个字节的存储空间。 - 当系统下一次重新启动时，会将上面提到的&lt;code>Max Trx ID&lt;/code>属性加载到内存中，将该值加上256之后赋值给我们前面提到的全局变量（因为在上次关机时该全局变量的值可能大于&lt;code>Max Trx ID&lt;/code>属性值）。&lt;/p>
&lt;p>这样就可以保证整个系统中分配的&lt;code>事务id&lt;/code>值是一个递增的数字。先被分配&lt;code>id&lt;/code>的事务得到的是较小的&lt;code>事务id&lt;/code>，后被分配&lt;code>id&lt;/code>的事务得到的是较大的&lt;code>事务id&lt;/code>。&lt;/p>
&lt;h2 id="trx_id隐藏列">trx_id隐藏列&lt;a class="anchor" href="#trx_id%e9%9a%90%e8%97%8f%e5%88%97">#&lt;/a>&lt;/h2>
&lt;p>我们前面介绍&lt;code>InnoDB&lt;/code>记录行格式的时候重点强调过：&lt;strong>聚簇索引的记录除了会保存完整的用户数据以外，而且还会自动添加名为trx_id、roll_pointer的隐藏列，如果用户没有在表中定义主键以及UNIQUE键，还会自动添加一个名为row_id的隐藏列&lt;/strong>。所以一条记录在页面中的真实结构看起来就是这样的：&lt;/p>
&lt;p>&lt;img src="img/22-01.png" alt="" />&lt;/p>
&lt;p>其中的&lt;code>trx_id&lt;/code>列其实还蛮好理解的，就是某个对这个聚簇索引记录做改动的语句所在的事务对应的&lt;code>事务id&lt;/code>而已（此处的改动可以是&lt;code>INSERT&lt;/code>、&lt;code>DELETE&lt;/code>、&lt;code>UPDATE&lt;/code>操作）。至于&lt;code>roll_pointer&lt;/code>隐藏列我们后边分析～&lt;/p></description></item><item><title>第21章_说过的话就一定要办到-redo日志(下)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC21%E7%AB%A0_%E8%AF%B4%E8%BF%87%E7%9A%84%E8%AF%9D%E5%B0%B1%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8A%9E%E5%88%B0-redo%E6%97%A5%E5%BF%97%E4%B8%8B/</link><pubDate>Sat, 18 Jan 2025 22:30:05 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC21%E7%AB%A0_%E8%AF%B4%E8%BF%87%E7%9A%84%E8%AF%9D%E5%B0%B1%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8A%9E%E5%88%B0-redo%E6%97%A5%E5%BF%97%E4%B8%8B/</guid><description>&lt;p>第21章 说过的话就一定要办到-redo日志（下）&lt;/p>
&lt;h1 id="redo日志文件">redo日志文件&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%96%87%e4%bb%b6">#&lt;/a>&lt;/h1>
&lt;h2 id="redo日志刷盘时机">redo日志刷盘时机&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e5%88%b7%e7%9b%98%e6%97%b6%e6%9c%ba">#&lt;/a>&lt;/h2>
&lt;p>我们前面说&lt;code>mtr&lt;/code>运行过程中产生的一组&lt;code>redo&lt;/code>日志在&lt;code>mtr&lt;/code>结束时会被复制到&lt;code>log buffer&lt;/code>中，可是这些日志总在内存里呆着也不是个办法，在一些情况下它们会被刷新到磁盘里，比如：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>log buffer&lt;/code>空间不足时&lt;/p>
&lt;p>&lt;code>log buffer&lt;/code>的大小是有限的（通过系统变量&lt;code>innodb_log_buffer_size&lt;/code>指定），如果不停的往这个有限大小的&lt;code>log buffer&lt;/code>里塞入日志，很快它就会被填满。设计&lt;code>InnoDB&lt;/code>的大佬认为如果当前写入&lt;code>log buffer&lt;/code>的&lt;code>redo&lt;/code>日志量已经占满了&lt;code>log buffer&lt;/code>总容量的大约一半左右，就需要把这些日志刷新到磁盘上。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>事务提交时&lt;/p>
&lt;p>我们前面说过之所以使用&lt;code>redo&lt;/code>日志主要是因为它占用的空间少，还是顺序写，在事务提交时可以不把修改过的&lt;code>Buffer Pool&lt;/code>页面刷新到磁盘，但是为了保证持久性，必须要把修改这些页面对应的&lt;code>redo&lt;/code>日志刷新到磁盘。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>后台线程不停的刷刷刷&lt;/p>
&lt;p>后台有一个线程，大约每秒都会刷新一次&lt;code>log buffer&lt;/code>中的&lt;code>redo&lt;/code>日志到磁盘。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>正常关闭服务器时&lt;/p>
&lt;ul>
&lt;li>做所谓的&lt;code>checkpoint&lt;/code>时（我们现在没介绍过&lt;code>checkpoint&lt;/code>的概念，稍后会仔细介绍，稍安勿躁）&lt;/li>
&lt;li>其他的一些情况&amp;hellip;&lt;/li>
&lt;/ul>
&lt;h2 id="redo日志文件组">redo日志文件组&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%96%87%e4%bb%b6%e7%bb%84">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>MySQL&lt;/code>的数据目录（使用&lt;code>SHOW VARIABLES LIKE 'datadir'&lt;/code>查看）下默认有两个名为&lt;code>ib_logfile0&lt;/code>和&lt;code>ib_logfile1&lt;/code>的文件，&lt;code>log buffer&lt;/code>中的日志默认情况下就是刷新到这两个磁盘文件中。如果我们对默认的&lt;code>redo&lt;/code>日志文件不满意，可以通过下面几个启动参数来调节：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>innodb_log_group_home_dir&lt;/code>&lt;/p>
&lt;p>该参数指定了&lt;code>redo&lt;/code>日志文件所在的目录，默认值就是当前的数据目录。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>innodb_log_file_size&lt;/code>&lt;/p>
&lt;p>该参数指定了每个&lt;code>redo&lt;/code>日志文件的大小，在&lt;code>MySQL 5.7.21&lt;/code>这个版本中的默认值为&lt;code>48MB&lt;/code>，&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>innodb_log_files_in_group&lt;/code>&lt;/p>
&lt;p>该参数指定&lt;code>redo&lt;/code>日志文件的个数，默认值为2，最大值为100。&lt;/p>
&lt;p>从上面的描述中可以看到，磁盘上的&lt;code>redo&lt;/code>日志文件不只一个，而是以一个&lt;code>日志文件组&lt;/code>的形式出现的。这些文件以&lt;code>ib_logfile[数字]&lt;/code>（&lt;code>数字&lt;/code>可以是&lt;code>0&lt;/code>、&lt;code>1&lt;/code>、&lt;code>2&lt;/code>&amp;hellip;）的形式进行命名。在将&lt;code>redo&lt;/code>日志写入&lt;code>日志文件组&lt;/code>时，是从&lt;code>ib_logfile0&lt;/code>开始写，如果&lt;code>ib_logfile0&lt;/code>写满了，就接着&lt;code>ib_logfile1&lt;/code>写，同理，&lt;code>ib_logfile1&lt;/code>写满了就去写&lt;code>ib_logfile2&lt;/code>，依此类推。如果写到最后一个文件该咋办？那就重新转到&lt;code>ib_logfile0&lt;/code>继续写，所以整个过程如下图所示：&lt;/p>
&lt;p>&lt;img src="img/21-01.png" alt="" />&lt;/p>
&lt;p>总共的&lt;code>redo&lt;/code>日志文件大小其实就是：&lt;code>innodb_log_file_size × innodb_log_files_in_group&lt;/code>。 &lt;code>小贴士：如果采用循环使用的方式向redo日志文件组里写数据的话，那岂不是要追尾，也就是后写入的redo日志覆盖掉前面写的redo日志？当然可能了！所以设计InnoDB的大佬提出了checkpoint的概念，稍后我们重点介绍～&lt;/code>&lt;/p>
&lt;h2 id="redo日志文件格式">redo日志文件格式&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%96%87%e4%bb%b6%e6%a0%bc%e5%bc%8f">#&lt;/a>&lt;/h2>
&lt;p>我们前面说过&lt;code>log buffer&lt;/code>本质上是一片连续的内存空间，被划分成了若干个&lt;code>512&lt;/code>字节大小的&lt;code>block&lt;/code>。&lt;strong>将log buffer中的redo日志刷新到磁盘的本质就是把block的镜像写入日志文件中&lt;/strong>，所以&lt;code>redo&lt;/code>日志文件其实也是由若干个&lt;code>512&lt;/code>字节大小的block组成。&lt;/p>
&lt;p>&lt;code>redo&lt;/code>日志文件组中的每个文件大小都一样，格式也一样，都是由两部分组成：&lt;/p>
&lt;ul>
&lt;li>前2048个字节，也就是前4个block是用来存储一些管理信息的。&lt;/li>
&lt;li>从第2048字节往后是用来存储&lt;code>log buffer&lt;/code>中的block镜像的。&lt;/li>
&lt;/ul>
&lt;p>所以我们前面所说的&lt;code>循环&lt;/code>使用redo日志文件，其实是从每个日志文件的第2048个字节开始算，画个示意图就是这样：&lt;/p>
&lt;p>&lt;img src="img/21-02.png" alt="" />&lt;/p>
&lt;p>普通block的格式我们在介绍&lt;code>log buffer&lt;/code>的时候都说过了，就是&lt;code>log block header&lt;/code>、&lt;code>log block body&lt;/code>、&lt;code>log block trialer&lt;/code>这三个部分，就不重复介绍了。这里需要介绍一下每个&lt;code>redo&lt;/code>日志文件前2048个字节，也就是前4个特殊block的格式都是干嘛的，废话少说，先看图：&lt;/p>
&lt;p>&lt;img src="img/21-03.png" alt="" />&lt;br>
从图中可以看出来，这4个block分别是：&lt;/p></description></item><item><title>第20章_说过的话就一定要办到-redo日志(上)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC20%E7%AB%A0_%E8%AF%B4%E8%BF%87%E7%9A%84%E8%AF%9D%E5%B0%B1%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8A%9E%E5%88%B0-redo%E6%97%A5%E5%BF%97%E4%B8%8A/</link><pubDate>Sat, 18 Jan 2025 22:30:04 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC20%E7%AB%A0_%E8%AF%B4%E8%BF%87%E7%9A%84%E8%AF%9D%E5%B0%B1%E4%B8%80%E5%AE%9A%E8%A6%81%E5%8A%9E%E5%88%B0-redo%E6%97%A5%E5%BF%97%E4%B8%8A/</guid><description>&lt;p>第20章 说过的话就一定要办到-redo日志（上）&lt;/p>
&lt;h1 id="事先说明">事先说明&lt;a class="anchor" href="#%e4%ba%8b%e5%85%88%e8%af%b4%e6%98%8e">#&lt;/a>&lt;/h1>
&lt;p>本文以及接下来的几篇文章将会频繁的使用到我们前面介绍的&lt;code>InnoDB&lt;/code>记录行格式、页面格式、索引原理、表空间的组成等各种基础知识，如果大家对这些东西理解的不透彻，那么阅读下面的文字可能会有些吃力，为保证您的阅读体验，请确保自己已经掌握了我前面介绍的这些知识。&lt;/p>
&lt;h1 id="redo日志是什么">redo日志是什么&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%98%af%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h1>
&lt;p>我们知道&lt;code>InnoDB&lt;/code>存储引擎是以页为单位来管理存储空间的，我们进行的增删改查操作其实本质上都是在访问页面（包括读页面、写页面、创建新页面等操作）。我们前面介绍&lt;code>Buffer Pool&lt;/code>的时候说过，在真正访问页面之前，需要把在磁盘上的页缓存到内存中的&lt;code>Buffer Pool&lt;/code>之后才可以访问。但是在介绍事务的时候又强调过一个称之为&lt;code>持久性&lt;/code>的特性，就是说对于一个已经提交的事务，在事务提交后即使系统发生了崩溃，这个事务对数据库中所做的更改也不能丢失。但是如果我们只在内存的&lt;code>Buffer Pool&lt;/code>中修改了页面，假设在事务提交后突然发生了某个故障，导致内存中的数据都失效了，那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了，这是我们所不能忍受的（想想ATM机已经提示狗哥转账成功，但之后由于服务器出现故障，重启之后猫爷发现自己没收到钱，猫爷就被砍死了）。那么如何保证这个&lt;code>持久性&lt;/code>呢？一个很简单的做法就是&lt;strong>在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘&lt;/strong>，但是这个简单粗暴的做法有些问题：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>刷新一个完整的数据页太浪费了&lt;/p>
&lt;p>有时候我们仅仅修改了某个页面中的一个字节，但是我们知道在&lt;code>InnoDB&lt;/code>中是以页为单位来进行磁盘IO的，也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘，我们又知道一个页面默认是16KB大小，只修改一个字节就要刷新16KB的数据到磁盘上显然是太浪费了。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>随机IO刷起来比较慢&lt;/p>
&lt;p>一个事务可能包含很多语句，即使是一条语句也可能修改许多页面，倒霉催的是该事务修改的这些页面可能并不相邻，这就意味着在将某个事务修改的&lt;code>Buffer Pool&lt;/code>中的页面刷新到磁盘时，需要进行很多的随机IO，随机IO比顺序IO要慢，尤其对于传统的机械硬盘来说。&lt;/p>
&lt;p>咋办呢？再次回到我们的初心：&lt;strong>我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效，即使后来系统崩溃，在重启后也能把这种修改恢复出来&lt;/strong>。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘，只需要&lt;strong>把修改了哪些东西记录一下就好&lt;/strong>，比方说某个事务将系统表空间中的第100号页面中偏移量为1000处的那个字节的值&lt;code>1&lt;/code>改成&lt;code>2&lt;/code>我们只需要记录一下：&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>将第0号表空间的100号页面的偏移量为1000处的值更新为&lt;code>2&lt;/code>。&lt;/p>&lt;/blockquote>&lt;p>这样我们在事务提交时，把上述内容刷新到磁盘中，即使之后系统崩溃了，重启之后只要按照上述内容所记录的步骤重新更新一下数据页，那么该事务对数据库中所做的修改又可以被恢复出来，也就意味着满足&lt;code>持久性&lt;/code>的要求。因为在系统奔溃重启时需要按照上述内容所记录的步骤重新更新数据页，所以上述内容也被称之为&lt;code>重做日志&lt;/code>，英文名为&lt;code>redo log&lt;/code>，我们也可以土洋结合，称之为&lt;code>redo日志&lt;/code>。与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比，只将该事务执行过程中产生的&lt;code>redo&lt;/code>日志刷新到磁盘的好处如下：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>redo&lt;/code>日志占用的空间非常小&lt;/p>
&lt;p>存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的，关于&lt;code>redo&lt;/code>日志的格式我们稍后会详细介绍，现在只要知道一条&lt;code>redo&lt;/code>日志占用的空间不是很大就好了。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>redo&lt;/code>日志是顺序写入磁盘的&lt;/p>
&lt;p>在执行事务的过程中，每执行一条语句，就可能产生若干条&lt;code>redo&lt;/code>日志，这些日志是按照产生的顺序写入磁盘的，也就是使用顺序IO。&lt;/p>
&lt;h1 id="redo日志格式">redo日志格式&lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%a0%bc%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>通过上面的内容我们知道，&lt;code>redo&lt;/code>日志本质上只是&lt;strong>记录了一下事务对数据库做了哪些修改&lt;/strong>。 设计&lt;code>InnoDB&lt;/code>的大佬们针对事务对数据库的不同修改场景定义了多种类型的&lt;code>redo&lt;/code>日志，但是绝大部分类型的&lt;code>redo&lt;/code>日志都有下面这种通用的结构：&lt;/p>
&lt;p>&lt;img src="img/20-01.png" alt="" />&lt;/p>
&lt;p>各个部分的详细释义如下：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>type&lt;/code>：该条&lt;code>redo&lt;/code>日志的类型。&lt;/p>
&lt;p>在&lt;code>MySQL 5.7.21&lt;/code>这个版本中，设计&lt;code>InnoDB&lt;/code>的大佬一共为&lt;code>redo&lt;/code>日志设计了53种不同的类型，稍后会详细介绍不同类型的&lt;code>redo&lt;/code>日志。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>space ID&lt;/code>：表空间ID。&lt;/p>
&lt;ul>
&lt;li>&lt;code>page number&lt;/code>：页号。&lt;/li>
&lt;li>&lt;code>data&lt;/code>：该条&lt;code>redo&lt;/code>日志的具体内容。&lt;/li>
&lt;/ul>
&lt;h2 id="简单的redo日志类型">简单的redo日志类型&lt;a class="anchor" href="#%e7%ae%80%e5%8d%95%e7%9a%84redo%e6%97%a5%e5%bf%97%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>我们前面介绍&lt;code>InnoDB&lt;/code>的记录行格式的时候说过，如果我们没有为某个表显式的定义主键，并且表中也没有定义&lt;code>Unique&lt;/code>键，那么&lt;code>InnoDB&lt;/code>会自动的为表添加一个称之为&lt;code>row_id&lt;/code>的隐藏列作为主键。为这个&lt;code>row_id&lt;/code>隐藏列赋值的方式如下： - 服务器会在内存中维护一个全局变量，每当向某个包含隐藏的&lt;code>row_id&lt;/code>列的表中插入一条记录时，就会把该变量的值当作新记录的&lt;code>row_id&lt;/code>列的值，并且把该变量自增1。 - 每当这个变量的值为256的倍数时，就会将该变量的值刷新到系统表空间的页号为&lt;code>7&lt;/code>的页面中一个称之为&lt;code>Max Row ID&lt;/code>的属性处（我们前面介绍表空间结构时详细说过）。 - 当系统启动时，会将上面提到的&lt;code>Max Row ID&lt;/code>属性加载到内存中，将该值加上256之后赋值给我们前面提到的全局变量（因为在上次关机时该全局变量的值可能大于&lt;code>Max Row ID&lt;/code>属性值）。&lt;/p>
&lt;p>这个&lt;code>Max Row ID&lt;/code>属性占用的存储空间是8个字节，当某个事务向某个包含&lt;code>row_id&lt;/code>隐藏列的表插入一条记录，并且为该记录分配的&lt;code>row_id&lt;/code>值为256的倍数时，就会向系统表空间页号为7的页面的相应偏移量处写入8个字节的值。但是我们要知道，这个写入实际上是在&lt;code>Buffer Pool&lt;/code>中完成的，我们需要为这个页面的修改记录一条&lt;code>redo&lt;/code>日志，以便在系统奔溃后能将已经提交的该事务对该页面所做的修改恢复出来。这种情况下对页面的修改是极其简单的，&lt;code>redo&lt;/code>日志中只需要&lt;strong>记录一下在某个页面的某个偏移量处修改了几个字节的值，具体被修改的内容是什么就好了&lt;/strong>，设计&lt;code>InnoDB&lt;/code>的大佬把这种极其简单的&lt;code>redo&lt;/code>日志称之为&lt;code>物理日志&lt;/code>，并且根据在页面中写入数据的多少划分了几种不同的&lt;code>redo&lt;/code>日志类型： - &lt;code>MLOG_1BYTE&lt;/code>（&lt;code>type&lt;/code>字段对应的十进制数字为&lt;code>1&lt;/code>）：表示在页面的某个偏移量处写入1个字节的&lt;code>redo&lt;/code>日志类型。 - &lt;code>MLOG_2BYTE&lt;/code>（&lt;code>type&lt;/code>字段对应的十进制数字为&lt;code>2&lt;/code>）：表示在页面的某个偏移量处写入2个字节的&lt;code>redo&lt;/code>日志类型。 - &lt;code>MLOG_4BYTE&lt;/code>（&lt;code>type&lt;/code>字段对应的十进制数字为&lt;code>4&lt;/code>）：表示在页面的某个偏移量处写入4个字节的&lt;code>redo&lt;/code>日志类型。 - &lt;code>MLOG_8BYTE&lt;/code>（&lt;code>type&lt;/code>字段对应的十进制数字为&lt;code>8&lt;/code>）：表示在页面的某个偏移量处写入8个字节的&lt;code>redo&lt;/code>日志类型。 - &lt;code>MLOG_WRITE_STRING&lt;/code>（&lt;code>type&lt;/code>字段对应的十进制数字为&lt;code>30&lt;/code>）：表示在页面的某个偏移量处写入一串数据。&lt;/p></description></item><item><title>第19章_从猫爷被杀说起-事务简介</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC19%E7%AB%A0_%E4%BB%8E%E7%8C%AB%E7%88%B7%E8%A2%AB%E6%9D%80%E8%AF%B4%E8%B5%B7-%E4%BA%8B%E5%8A%A1%E7%AE%80%E4%BB%8B/</link><pubDate>Sat, 18 Jan 2025 22:30:02 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC19%E7%AB%A0_%E4%BB%8E%E7%8C%AB%E7%88%B7%E8%A2%AB%E6%9D%80%E8%AF%B4%E8%B5%B7-%E4%BA%8B%E5%8A%A1%E7%AE%80%E4%BB%8B/</guid><description>&lt;p>第19章 从猫爷被杀说起-事务简介&lt;/p>
&lt;h1 id="事务的起源">事务的起源&lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e7%9a%84%e8%b5%b7%e6%ba%90">#&lt;/a>&lt;/h1>
&lt;p>对于大部分程序员来说，他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个&lt;code>account&lt;/code>表： &lt;code>CREATE TABLE account ( id INT NOT NULL AUTO_INCREMENT COMMENT '自增id', name VARCHAR(100) COMMENT '客户名称', balance INT COMMENT '余额', PRIMARY KEY (id) ) Engine=InnoDB CHARSET=utf8;&lt;/code> 狗哥和猫爷是一对好基友，他们都到银行开一个账户，他们在现实世界中拥有的资产就会体现在数据库世界的&lt;code>account&lt;/code>表中。比如现在狗哥有&lt;code>11&lt;/code>元，猫爷只有&lt;code>2&lt;/code>元，那么现实中的这个情况映射到数据库的&lt;code>account&lt;/code>表就是这样： &lt;code>+----+--------+---------+ | id | name | balance | +----+--------+---------+ | 1 | 狗哥 | 11 | | 2 | 猫爷 | 2 | +----+--------+---------+&lt;/code> 在某个特定的时刻，狗哥猫爷这些家伙在银行所拥有的资产是一个特定的值，这些特定的值也可以被描述为账户在这个特定的时刻现实世界的一个状态。随着时间的流逝，狗哥和猫爷可能陆续进行向账户中存钱、取钱或者向别人转账等操作，这样他们账户中的余额就可能发生变动，&lt;strong>每一个操作都相当于现实世界中账户的一次状态转换&lt;/strong>。数据库世界作为现实世界的一个映射，自然也要进行相应的变动。不变不知道，一变吓一跳，现实世界中一些看似很简单的状态转换，映射到数据库世界却不是那么容易的。比方说有一次猫爷在赌场赌博输了钱，急忙打电话给狗哥要借10块钱，不然那些看场子的就会把自己剁了。现实世界中的狗哥走向了ATM机，输入了猫爷的账号以及10元的转账金额，然后按下确认，狗哥就拔卡走人了。对于数据库世界来说，相当于执行了下面这两条语句：&lt;/p>
&lt;p>&lt;code>UPDATE account SET balance = balance - 10 WHERE id = 1; UPDATE account SET balance = balance + 10 WHERE id = 2;&lt;/code>&lt;/p></description></item><item><title>第18章_调节磁盘和CPU的矛盾-InnoDB的BufferPool</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC18%E7%AB%A0_%E8%B0%83%E8%8A%82%E7%A3%81%E7%9B%98%E5%92%8CCPU%E7%9A%84%E7%9F%9B%E7%9B%BE-InnoDB%E7%9A%84BufferPool/</link><pubDate>Sat, 18 Jan 2025 22:30:01 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC18%E7%AB%A0_%E8%B0%83%E8%8A%82%E7%A3%81%E7%9B%98%E5%92%8CCPU%E7%9A%84%E7%9F%9B%E7%9B%BE-InnoDB%E7%9A%84BufferPool/</guid><description>&lt;p>第18章 调节磁盘和CPU的矛盾-InnoDB的Buffer Pool&lt;/p>
&lt;h1 id="缓存的重要性">缓存的重要性&lt;a class="anchor" href="#%e7%bc%93%e5%ad%98%e7%9a%84%e9%87%8d%e8%a6%81%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;p>通过前面的介绍我们知道，对于使用&lt;code>InnoDB&lt;/code>作为存储引擎的表来说，不管是用于存储用户数据的索引（包括聚簇索引和二级索引），还是各种系统数据，都是以&lt;code>页&lt;/code>的形式存放在&lt;code>表空间&lt;/code>中的，而所谓的&lt;code>表空间&lt;/code>只不过是&lt;code>InnoDB&lt;/code>对文件系统上一个或几个实际文件的抽象，也就是说我们的数据说到底还是存储在磁盘上的。但是各位也都知道，磁盘的速度慢的跟乌龟一样，怎么能配得上“快如风，疾如电”的&lt;code>CPU&lt;/code>呢？所以&lt;code>InnoDB&lt;/code>存储引擎在处理客户端的请求时，当需要访问某个页的数据时，就会把完整的页的数据全部加载到内存中，也就是说&lt;strong>即使我们只需要访问一个页的一条记录，那也需要先把整个页的数据加载到内存中&lt;/strong>。将整个页加载到内存中后就可以进行读写访问了，在进行完读写访问之后并不着急把该页对应的内存空间释放掉，而是将其&lt;code>缓存&lt;/code>起来，这样将来有请求再次访问该页面时，就可以省去磁盘&lt;code>IO&lt;/code>的开销了。&lt;/p>
&lt;h1 id="innodb的buffer-pool">InnoDB的Buffer Pool&lt;a class="anchor" href="#innodb%e7%9a%84buffer-pool">#&lt;/a>&lt;/h1>
&lt;h2 id="什么是个buffer-pool">什么是个Buffer Pool&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e4%b8%aabuffer-pool">#&lt;/a>&lt;/h2>
&lt;p>设计&lt;code>InnoDB&lt;/code>的大佬为了缓存磁盘中的页，在&lt;code>MySQL&lt;/code>服务器启动的时候就向操作系统申请了一片连续的内存，他们给这片内存起了个名，叫做&lt;code>Buffer Pool&lt;/code>（中文名是&lt;code>缓冲池&lt;/code>）。那它有多大呢？这个其实看我们机器的配置，如果你是土豪，你有&lt;code>512G&lt;/code>内存，你分配个几百G作为&lt;code>Buffer Pool&lt;/code>也可以啊，当然你要是没那么有钱，设置小点也行呀～ 默认情况下&lt;code>Buffer Pool&lt;/code>只有&lt;code>128M&lt;/code>大小。当然如果你嫌弃这个&lt;code>128M&lt;/code>太大或者太小，可以在启动服务器的时候配置&lt;code>innodb_buffer_pool_size&lt;/code>参数的值，它表示&lt;code>Buffer Pool&lt;/code>的大小，就像这样： &lt;code>[server] innodb_buffer_pool_size = 268435456&lt;/code> 其中，&lt;code>268435456&lt;/code>的单位是字节，也就是我指定&lt;code>Buffer Pool&lt;/code>的大小为&lt;code>256M&lt;/code>。需要注意的是，&lt;code>Buffer Pool&lt;/code>也不能太小，最小值为&lt;code>5M&lt;/code>(当小于该值时会自动设置成&lt;code>5M&lt;/code>)。&lt;/p>
&lt;h2 id="buffer-pool内部组成">Buffer Pool内部组成&lt;a class="anchor" href="#buffer-pool%e5%86%85%e9%83%a8%e7%bb%84%e6%88%90">#&lt;/a>&lt;/h2>
&lt;p>&lt;code>Buffer Pool&lt;/code>中默认的缓存页大小和在磁盘上默认的页大小是一样的，都是&lt;code>16KB&lt;/code>。为了更好的管理这些在&lt;code>Buffer Pool&lt;/code>中的缓存页，设计&lt;code>InnoDB&lt;/code>的大佬为每一个缓存页都创建了一些所谓的&lt;code>控制信息&lt;/code>，这些控制信息包括该页所属的表空间编号、页号、缓存页在&lt;code>Buffer Pool&lt;/code>中的地址、链表节点信息、一些锁信息以及&lt;code>LSN&lt;/code>信息（锁和&lt;code>LSN&lt;/code>我们之后会具体介绍，现在可以先忽略），当然还有一些别的控制信息，我们这就不全介绍一遍了，挑重要的说嘛～&lt;/p>
&lt;p>每个缓存页对应的控制信息占用的内存大小是相同的，我们就把每个页对应的控制信息占用的一块内存称为一个&lt;code>控制块&lt;/code>吧，&lt;strong>控制块和缓存页是一一对应的，它们都被存放到 Buffer Pool 中，其中控制块被存放到 Buffer Pool 的前面，缓存页被存放到 Buffer Pool 后边&lt;/strong>，所以整个&lt;code>Buffer Pool&lt;/code>对应的内存空间看起来就是这样的：&lt;/p>
&lt;p>&lt;img src="img/18-01.png" alt="" />&lt;/p>
&lt;p>咦？控制块和缓存页之间的那个&lt;code>碎片&lt;/code>是个什么玩意儿？你想想啊，每一个控制块都对应一个缓存页，那在分配足够多的控制块和缓存页后，可能剩余的那点儿空间不够一对控制块和缓存页的大小，自然就用不到喽，这个用不到的那点儿内存空间就被称为&lt;code>碎片&lt;/code>了。当然，如果你把&lt;code>Buffer Pool&lt;/code>的大小设置的刚刚好的话，也可能不会产生&lt;code>碎片&lt;/code>～ &lt;code>小贴士：每个控制块大约占用缓存页大小的5%，在MySQL5.7.21这个版本中，每个控制块占用的大小是808字节。而我们设置的innodb_buffer_pool_size并不包含这部分控制块占用的内存空间大小，也就是说InnoDB在为Buffer Pool向操作系统申请连续的内存空间时，这片连续的内存空间一般会比innodb_buffer_pool_size的值大5%左右。&lt;/code>&lt;/p>
&lt;h2 id="free链表的管理">free链表的管理&lt;a class="anchor" href="#free%e9%93%be%e8%a1%a8%e7%9a%84%e7%ae%a1%e7%90%86">#&lt;/a>&lt;/h2>
&lt;p>当我们最初启动&lt;code>MySQL&lt;/code>服务器的时候，需要完成对&lt;code>Buffer Pool&lt;/code>的初始化过程，就是先向操作系统申请&lt;code>Buffer Pool&lt;/code>的内存空间，然后把它划分成若干对控制块和缓存页。但是此时并没有真实的磁盘页被缓存到&lt;code>Buffer Pool&lt;/code>中（因为还没有用到），之后随着程序的运行，会不断的有磁盘上的页被缓存到&lt;code>Buffer Pool&lt;/code>中。那么问题来了，从磁盘上读取一个页到&lt;code>Buffer Pool&lt;/code>中的时候该放到哪个缓存页的位置呢？或者说怎么区分&lt;code>Buffer Pool&lt;/code>中哪些缓存页是空闲的，哪些已经被使用了呢？&lt;strong>我们最好在某个地方记录一下Buffer Pool中哪些缓存页是可用的&lt;/strong>，这个时候缓存页对应的&lt;code>控制块&lt;/code>就派上大用场了，我们可以&lt;strong>把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中&lt;/strong>，这个链表也可以被称作&lt;code>free链表&lt;/code>（或者说空闲链表）。刚刚完成初始化的&lt;code>Buffer Pool&lt;/code>中所有的缓存页都是空闲的，所以每一个缓存页对应的控制块都会被加入到&lt;code>free链表&lt;/code>中，假设该&lt;code>Buffer Pool&lt;/code>中可容纳的缓存页数量为&lt;code>n&lt;/code>，那增加了&lt;code>free链表&lt;/code>的效果图就是这样的：&lt;/p>
&lt;p>&lt;img src="img/18-02.png" alt="" />&lt;/p>
&lt;p>从图中可以看出，我们为了管理好这个&lt;code>free链表&lt;/code>，特意为这个链表定义了一个&lt;code>基节点&lt;/code>，里边儿包含着链表的头节点地址，尾节点地址，以及当前链表中节点的数量等信息。这里需要注意的是，链表的基节点占用的内存空间并不包含在为&lt;code>Buffer Pool&lt;/code>申请的一大片连续内存空间之内，而是单独申请的一块内存空间。 &lt;code>小贴士：链表基节点占用的内存空间并不大，在MySQL5.7.21这个版本里，每个基节点只占用40字节大小。后边我们即将介绍许多不同的链表，它们的基节点和free链表的基节点的内存分配方式是一样一样的，都是单独申请的一块40字节大小的内存空间，并不包含在为Buffer Pool申请的一大片连续内存空间之内。&lt;/code>&lt;/p>
&lt;p>有了这个&lt;code>free链表&lt;/code>之后事儿就好办了，每当需要从磁盘中加载一个页到&lt;code>Buffer Pool&lt;/code>中时，就从&lt;code>free链表&lt;/code>中取一个空闲的缓存页，并且把该缓存页对应的&lt;code>控制块&lt;/code>的信息填上（就是该页所在的表空间、页号之类的信息），然后把该缓存页对应的&lt;code>free链表&lt;/code>节点从链表中移除，表示该缓存页已经被使用了～&lt;/p>
&lt;h2 id="缓存页的哈希处理">缓存页的哈希处理&lt;a class="anchor" href="#%e7%bc%93%e5%ad%98%e9%a1%b5%e7%9a%84%e5%93%88%e5%b8%8c%e5%a4%84%e7%90%86">#&lt;/a>&lt;/h2>
&lt;p>我们前面说过，当我们需要访问某个页中的数据时，就会把该页从磁盘加载到&lt;code>Buffer Pool&lt;/code>中，如果该页已经在&lt;code>Buffer Pool&lt;/code>中的话直接使用就可以了。那么问题也就来了，我们怎么知道该页在不在&lt;code>Buffer Pool&lt;/code>中呢？难不成需要依次遍历&lt;code>Buffer Pool&lt;/code>中各个缓存页么？一个&lt;code>Buffer Pool&lt;/code>中的缓存页这么多都遍历完岂不是要累死？&lt;/p></description></item><item><title>第17章_神兵利器-optimizer_trace表的神器功效</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC17%E7%AB%A0_%E7%A5%9E%E5%85%B5%E5%88%A9%E5%99%A8-optimizer_trace%E8%A1%A8%E7%9A%84%E7%A5%9E%E5%99%A8%E5%8A%9F%E6%95%88/</link><pubDate>Sat, 18 Jan 2025 22:30:00 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC17%E7%AB%A0_%E7%A5%9E%E5%85%B5%E5%88%A9%E5%99%A8-optimizer_trace%E8%A1%A8%E7%9A%84%E7%A5%9E%E5%99%A8%E5%8A%9F%E6%95%88/</guid><description>&lt;p>第17章 神兵利器-optimizer trace表的神器功效&lt;/p>
&lt;p>对于&lt;code>MySQL 5.6&lt;/code>以及之前的版本来说，查询优化器就像是一个黑盒子一样，你只能通过&lt;code>EXPLAIN&lt;/code>语句查看到最后优化器决定使用的执行计划，却无法知道它为什么做这个决策。这对于一部分喜欢刨根问底的小伙伴来说简直是灾难：“我就觉得使用其他的执行方案比&lt;code>EXPLAIN&lt;/code>输出的这种方案强，凭什么优化器做的决定和我想的不一样呢？”&lt;/p>
&lt;p>在&lt;code>MySQL 5.6&lt;/code>以及之后的版本中，设计&lt;code>MySQL&lt;/code>的大佬贴心的为这部分小伙伴提出了一个&lt;code>optimizer trace&lt;/code>的功能，这个功能可以让我们方便的查看优化器生成执行计划的整个过程，这个功能的开启与关闭由系统变量&lt;code>optimizer_trace&lt;/code>决定，我们看一下： &lt;code>mysql&amp;gt; SHOW VARIABLES LIKE 'optimizer_trace'; +-----------------+--------------------------+ | Variable_name | Value | +-----------------+--------------------------+ | optimizer_trace | enabled=off,one_line=off | +-----------------+--------------------------+ 1 row in set (0.02 sec)&lt;/code> 可以看到&lt;code>enabled&lt;/code>值为&lt;code>off&lt;/code>，表明这个功能默认是关闭的。 &lt;code>小贴士：one_line的值是控制输出格式的，如果为on那么所有输出都将在一行中展示，不适合人阅读，所以我们就保持其默认值为off吧。&lt;/code> 如果想打开这个功能，必须首先把&lt;code>enabled&lt;/code>的值改为&lt;code>on&lt;/code>，就像这样： &lt;code>mysql&amp;gt; SET optimizer_trace=&amp;quot;enabled=on&amp;quot;; Query OK, 0 rows affected (0.00 sec)&lt;/code> 然后我们就可以输入我们想要查看优化过程的查询语句，当该查询语句执行完成后，就可以到&lt;code>information_schema&lt;/code>数据库下的&lt;code>OPTIMIZER_TRACE&lt;/code>表中查看完整的优化过程。这个&lt;code>OPTIMIZER_TRACE&lt;/code>表有4个列，分别是： - &lt;code>QUERY&lt;/code>：表示我们的查询语句。 - &lt;code>TRACE&lt;/code>：表示优化过程的JSON格式文本。 - &lt;code>MISSING_BYTES_BEYOND_MAX_MEM_SIZE&lt;/code>：由于优化过程可能会输出很多，如果超过某个限制时，多余的文本将不会被显示，这个字段展示了被忽略的文本字节数。 - &lt;code>INSUFFICIENT_PRIVILEGES&lt;/code>：表示是否没有权限查看优化过程，默认值是0，只有某些特殊情况下才会是&lt;code>1&lt;/code>，我们暂时不关心这个字段的值。&lt;/p>
&lt;p>完整的使用&lt;code>optimizer trace&lt;/code>功能的步骤总结如下： ```&lt;/p>
&lt;ol>
&lt;li>打开optimizer trace功能 (默认情况下它是关闭的):&lt;/li>
&lt;/ol>
&lt;p>SET optimizer_trace=&amp;ldquo;enabled=on&amp;rdquo;;&lt;/p>
&lt;ol start="2">
&lt;li>这里输入你自己的查询语句&lt;/li>
&lt;/ol>
&lt;p>SELECT &amp;hellip;;&lt;/p>
&lt;ol start="3">
&lt;li>从OPTIMIZER_TRACE表中查看上一个查询的优化过程&lt;/li>
&lt;/ol>
&lt;p>SELECT * FROM information_schema.OPTIMIZER_TRACE;&lt;/p></description></item><item><title>第16章_查询优化的百科全书-Explain详解(下)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC16%E7%AB%A0_%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E7%9A%84%E7%99%BE%E7%A7%91%E5%85%A8%E4%B9%A6-Explain%E8%AF%A6%E8%A7%A3%E4%B8%8B/</link><pubDate>Sat, 18 Jan 2025 22:29:59 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC16%E7%AB%A0_%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E7%9A%84%E7%99%BE%E7%A7%91%E5%85%A8%E4%B9%A6-Explain%E8%AF%A6%E8%A7%A3%E4%B8%8B/</guid><description>&lt;p>第16章 查询优化的百科全书-Explain详解（下）&lt;/p>
&lt;h1 id="执行计划输出中各列详解">执行计划输出中各列详解&lt;a class="anchor" href="#%e6%89%a7%e8%a1%8c%e8%ae%a1%e5%88%92%e8%be%93%e5%87%ba%e4%b8%ad%e5%90%84%e5%88%97%e8%af%a6%e8%a7%a3">#&lt;/a>&lt;/h1>
&lt;p>本章紧接着上一节的内容，继续介绍&lt;code>EXPLAIN&lt;/code>语句输出的各个列的意思。&lt;/p>
&lt;h2 id="extra">Extra&lt;a class="anchor" href="#extra">#&lt;/a>&lt;/h2>
&lt;p>顾名思义，&lt;code>Extra&lt;/code>列是用来说明一些额外信息的，我们可以通过这些额外信息来更准确的理解&lt;code>MySQL&lt;/code>到底将如何执行给定的查询语句。&lt;code>MySQL&lt;/code>提供的额外信息有好几十个，我们就不一个一个介绍了（都介绍了感觉我们的文章就跟文档差不多了～），所以我们只挑一些平时常见的或者比较重要的额外信息介绍给大家。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>No tables used&lt;/code>&lt;/p>
&lt;p>当查询语句的没有&lt;code>FROM&lt;/code>子句时将会提示该额外信息，比如： &lt;code>mysql&amp;gt; EXPLAIN SELECT 1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set, 1 warning (0.00 sec)&lt;/code>&lt;/p></description></item><item><title>第15章_查询优化的百科全书-Explain详解(上)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC15%E7%AB%A0_%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E7%9A%84%E7%99%BE%E7%A7%91%E5%85%A8%E4%B9%A6-Explain%E8%AF%A6%E8%A7%A3%E4%B8%8A/</link><pubDate>Sat, 18 Jan 2025 22:29:57 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC15%E7%AB%A0_%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E7%9A%84%E7%99%BE%E7%A7%91%E5%85%A8%E4%B9%A6-Explain%E8%AF%A6%E8%A7%A3%E4%B8%8A/</guid><description>&lt;p>第15章 查询优化的百科全书-Explain详解（上）&lt;/p>
&lt;p>一条查询语句在经过&lt;code>MySQL&lt;/code>查询优化器的各种基于成本和规则的优化会后生成一个所谓的&lt;code>执行计划&lt;/code>，这个执行计划展示了接下来具体执行查询的方式，比如多表连接的顺序是什么，对于每个表采用什么访问方法来具体执行查询等等。设计&lt;code>MySQL&lt;/code>的大佬贴心的为我们提供了&lt;code>EXPLAIN&lt;/code>语句来帮助我们查看某个查询语句的具体执行计划，本章的内容就是为了帮助大家看懂&lt;code>EXPLAIN&lt;/code>语句的各个输出项都是干嘛使的，从而可以有针对性的提升我们查询语句的性能。&lt;/p>
&lt;p>如果我们想看看某个查询的执行计划的话，可以在具体的查询语句前面加一个&lt;code>EXPLAIN&lt;/code>，就像这样：&lt;/p>
&lt;p>&lt;code>mysql&amp;gt; EXPLAIN SELECT 1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set, 1 warning (0.01 sec)&lt;/code> 然后这输出的一大坨东西就是所谓的&lt;code>执行计划&lt;/code>，我的任务就是带领大家看懂这一大坨东西里边的每个列都是干什么用的，以及在这个&lt;code>执行计划&lt;/code>的辅助下，我们应该怎样改进自己的查询语句以使查询执行起来更高效。其实除了以&lt;code>SELECT&lt;/code>开头的查询语句，其余的&lt;code>DELETE&lt;/code>、&lt;code>INSERT&lt;/code>、&lt;code>REPLACE&lt;/code>以及&lt;code>UPDATE&lt;/code>语句前面都可以加上&lt;code>EXPLAIN&lt;/code>这个词儿，用来查看这些语句的执行计划，不过我们这里对&lt;code>SELECT&lt;/code>语句更感兴趣，所以后边只会以&lt;code>SELECT&lt;/code>语句为例来描述&lt;code>EXPLAIN&lt;/code>语句的用法。为了让大家先有一个感性的认识，我们把&lt;code>EXPLAIN&lt;/code>语句输出的各个列的作用先大致罗列一下：
列名 描述 &lt;code>id&lt;/code> 在一个大的查询语句中每个&lt;code>SELECT&lt;/code>关键字都对应一个唯一的&lt;code>id&lt;/code> &lt;code>select_type&lt;/code> &lt;code>SELECT&lt;/code>关键字对应的那个查询的类型 &lt;code>table&lt;/code> 表名 &lt;code>partitions&lt;/code> 匹配的分区信息 &lt;code>type&lt;/code> 针对单表的访问方法 &lt;code>possible_keys&lt;/code> 可能用到的索引 &lt;code>key&lt;/code> 实际上使用的索引 &lt;code>key_len&lt;/code> 实际使用到的索引长度 &lt;code>ref&lt;/code> 当使用索引列等值查询时，与索引列进行等值匹配的对象信息 &lt;code>rows&lt;/code> 预估的需要读取的记录条数 &lt;code>filtered&lt;/code> 某个表经过搜索条件过滤后剩余记录条数的百分比 &lt;code>Extra&lt;/code> 一些额外的信息 &lt;br>
需要注意的是，&lt;strong>大家如果看不懂上面输出列含义，那是正常的，千万不要纠结～&lt;/strong>。我在这里把它们都列出来只是为了描述一个轮廓，让大家有一个大致的印象，下面会细细道来，等会儿说完了不信你不会～ 为了故事的顺利发展，我们还是要请出我们前面已经用了n遍的&lt;code>single_table&lt;/code>表，为了防止大家忘了，再把它的结构描述一遍： &lt;code>CREATE TABLE single_table ( id INT NOT NULL AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1), UNIQUE KEY idx_key2 (key2), KEY idx_key3 (key3), KEY idx_key_part(key_part1, key_part2, key_part3) ) Engine=InnoDB CHARSET=utf8;&lt;/code> 我们仍然假设有两个和&lt;code>single_table&lt;/code>表构造一模一样的&lt;code>s1&lt;/code>、&lt;code>s2&lt;/code>表，而且这两个表里边儿有10000条记录，除id列外其余的列都插入随机值。为了让大家有比较好的阅读体验，我们下面并不准备严格按照&lt;code>EXPLAIN&lt;/code>输出列的顺序来介绍这些列分别是干嘛的，大家注意一下就好了。&lt;/p></description></item><item><title>第14章_不好看就要多整容-MySQL基于规则的优化(内含关于子查询优化二三事儿)</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC14%E7%AB%A0_%E4%B8%8D%E5%A5%BD%E7%9C%8B%E5%B0%B1%E8%A6%81%E5%A4%9A%E6%95%B4%E5%AE%B9-MySQL%E5%9F%BA%E4%BA%8E%E8%A7%84%E5%88%99%E7%9A%84%E4%BC%98%E5%8C%96%E5%86%85%E5%90%AB%E5%85%B3%E4%BA%8E%E5%AD%90%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E4%BA%8C%E4%B8%89%E4%BA%8B%E5%84%BF/</link><pubDate>Sat, 18 Jan 2025 22:29:56 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC14%E7%AB%A0_%E4%B8%8D%E5%A5%BD%E7%9C%8B%E5%B0%B1%E8%A6%81%E5%A4%9A%E6%95%B4%E5%AE%B9-MySQL%E5%9F%BA%E4%BA%8E%E8%A7%84%E5%88%99%E7%9A%84%E4%BC%98%E5%8C%96%E5%86%85%E5%90%AB%E5%85%B3%E4%BA%8E%E5%AD%90%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E4%BA%8C%E4%B8%89%E4%BA%8B%E5%84%BF/</guid><description>&lt;p>第14章 不好看就要多整容-MySQL基于规则的优化（内含关于子查询优化二三事儿）&lt;/p>
&lt;p>大家别忘了&lt;code>MySQL&lt;/code>本质上是一个软件，设计&lt;code>MySQL&lt;/code>的大佬并不能要求使用这个软件的人个个都是数据库高高手，就像我写这本书的时候并不能要求各位在学之前就会了里边儿的知识。 &lt;code>吐槽一下：都会了的人谁还看呢，难道是为了精神上受感化？&lt;/code> 也就是说我们无法避免某些同学写一些执行起来十分耗费性能的语句。即使是这样，设计&lt;code>MySQL&lt;/code>的大佬还是依据一些规则，竭尽全力的把这个很糟糕的语句转换成某种可以比较高效执行的形式，这个过程也可以被称作&lt;code>查询重写&lt;/code>（就是人家觉得你写的语句不好，自己再重写一遍）。本章详细介绍一下一些比较重要的重写规则。&lt;/p>
&lt;h1 id="条件化简">条件化简&lt;a class="anchor" href="#%e6%9d%a1%e4%bb%b6%e5%8c%96%e7%ae%80">#&lt;/a>&lt;/h1>
&lt;p>我们编写的查询语句的搜索条件本质上是一个表达式，这些表达式可能比较繁杂，或者不能高效的执行，&lt;code>MySQL&lt;/code>的查询优化器会为我们简化这些表达式。为了方便大家理解，我们后边举例子的时候都使用诸如&lt;code>a&lt;/code>、&lt;code>b&lt;/code>、&lt;code>c&lt;/code>之类的简单字母代表某个表的列名。&lt;/p>
&lt;h2 id="移除不必要的括号">移除不必要的括号&lt;a class="anchor" href="#%e7%a7%bb%e9%99%a4%e4%b8%8d%e5%bf%85%e8%a6%81%e7%9a%84%e6%8b%ac%e5%8f%b7">#&lt;/a>&lt;/h2>
&lt;p>有时候表达式里有许多无用的括号，比如这样： &lt;code>((a = 5 AND b = c) OR ((a &amp;gt; c) AND (c &amp;lt; 5)))&lt;/code> 看着就很烦，优化器会把那些用不到的括号给干掉，就是这样： &lt;code>(a = 5 and b = c) OR (a &amp;gt; c AND c &amp;lt; 5)&lt;/code>&lt;/p>
&lt;h2 id="常量传递constant_propagation">常量传递（constant_propagation）&lt;a class="anchor" href="#%e5%b8%b8%e9%87%8f%e4%bc%a0%e9%80%92constant_propagation">#&lt;/a>&lt;/h2>
&lt;p>有时候某个表达式是某个列和某个常量做等值匹配，比如这样： &lt;code>a = 5&lt;/code> 当这个表达式和其他涉及列&lt;code>a&lt;/code>的表达式使用&lt;code>AND&lt;/code>连接起来时，可以将其他表达式中的&lt;code>a&lt;/code>的值替换为&lt;code>5&lt;/code>，比如这样： &lt;code>a = 5 AND b &amp;gt; a&lt;/code> 就可以被转换为： &lt;code>a = 5 AND b &amp;gt; 5&lt;/code> &lt;code>小贴士：为什么用OR连接起来的表达式就不能进行常量传递呢？自己想想～&lt;/code>&lt;/p>
&lt;h2 id="等值传递equality_propagation">等值传递（equality_propagation）&lt;a class="anchor" href="#%e7%ad%89%e5%80%bc%e4%bc%a0%e9%80%92equality_propagation">#&lt;/a>&lt;/h2>
&lt;p>有时候多个列之间存在等值匹配的关系，比如这样： &lt;code>a = b and b = c and c = 5&lt;/code> 这个表达式可以被简化为： &lt;code>a = 5 and b = 5 and c = 5&lt;/code>&lt;/p></description></item><item><title>第13章_兵马未动粮草先行-InnoDB统计数据是如何收集的</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC13%E7%AB%A0_%E5%85%B5%E9%A9%AC%E6%9C%AA%E5%8A%A8%E7%B2%AE%E8%8D%89%E5%85%88%E8%A1%8C-InnoDB%E7%BB%9F%E8%AE%A1%E6%95%B0%E6%8D%AE%E6%98%AF%E5%A6%82%E4%BD%95%E6%94%B6%E9%9B%86%E7%9A%84/</link><pubDate>Sat, 18 Jan 2025 22:29:55 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC13%E7%AB%A0_%E5%85%B5%E9%A9%AC%E6%9C%AA%E5%8A%A8%E7%B2%AE%E8%8D%89%E5%85%88%E8%A1%8C-InnoDB%E7%BB%9F%E8%AE%A1%E6%95%B0%E6%8D%AE%E6%98%AF%E5%A6%82%E4%BD%95%E6%94%B6%E9%9B%86%E7%9A%84/</guid><description>&lt;p>第13章 兵马未动，粮草先行-InnoDB统计数据是如何收集的&lt;/p>
&lt;p>我们前面介绍查询成本的时候经常用到一些统计数据，比如通过&lt;code>SHOW TABLE STATUS&lt;/code>可以看到关于表的统计数据，通过&lt;code>SHOW INDEX&lt;/code>可以看到关于索引的统计数据，那么这些统计数据是怎么来的呢？它们是以什么方式收集的呢？本章将聚焦于&lt;code>InnoDB&lt;/code>存储引擎的统计数据收集策略，看完本章大家就会明白为什么前面老说&lt;code>InnoDB&lt;/code>的统计信息是不精确的估计值了（言下之意就是我们不打算介绍&lt;code>MyISAM&lt;/code>存储引擎统计数据的收集和存储方式，有想了解的同学自己个儿看看文档）。&lt;/p>
&lt;h1 id="两种不同的统计数据存储方式">两种不同的统计数据存储方式&lt;a class="anchor" href="#%e4%b8%a4%e7%a7%8d%e4%b8%8d%e5%90%8c%e7%9a%84%e7%bb%9f%e8%ae%a1%e6%95%b0%e6%8d%ae%e5%ad%98%e5%82%a8%e6%96%b9%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>&lt;code>InnoDB&lt;/code>提供了两种存储统计数据的方式：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>永久性的统计数据&lt;/p>
&lt;p>这种统计数据存储在磁盘上，也就是服务器重启之后这些统计数据还在。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>非永久性的统计数据&lt;/p>
&lt;p>这种统计数据存储在内存中，当服务器关闭时这些这些统计数据就都被清除掉了，等到服务器重启之后，在某些适当的场景下才会重新收集这些统计数据。&lt;/p>
&lt;p>设计&lt;code>MySQL&lt;/code>的大佬们给我们提供了系统变量&lt;code>innodb_stats_persistent&lt;/code>来控制到底采用哪种方式去存储统计数据。在&lt;code>MySQL 5.6.6&lt;/code>之前，&lt;code>innodb_stats_persistent&lt;/code>的值默认是&lt;code>OFF&lt;/code>，也就是说&lt;code>InnoDB&lt;/code>的统计数据默认是存储到内存的，之后的版本中&lt;code>innodb_stats_persistent&lt;/code>的值默认是&lt;code>ON&lt;/code>，也就是统计数据默认被存储到磁盘中。&lt;/p>
&lt;p>不过&lt;code>InnoDB&lt;/code>默认是&lt;strong>以表为单位来收集和存储统计数据的&lt;/strong>，也就是说我们可以把某些表的统计数据（以及该表的索引统计数据）存储在磁盘上，把另一些表的统计数据存储在内存中。怎么做到的呢？我们可以在创建和修改表的时候通过指定&lt;code>STATS_PERSISTENT&lt;/code>属性来指明该表的统计数据存储方式： &lt;code>CREATE TABLE 表名 (...) Engine=InnoDB, STATS_PERSISTENT = (1|0); ALTER TABLE 表名 Engine=InnoDB, STATS_PERSISTENT = (1|0);&lt;/code> 当&lt;code>STATS_PERSISTENT=1&lt;/code>时，表明我们想把该表的统计数据永久的存储到磁盘上，当&lt;code>STATS_PERSISTENT=0&lt;/code>时，表明我们想把该表的统计数据临时的存储到内存中。如果我们在创建表时未指定&lt;code>STATS_PERSISTENT&lt;/code>属性，那默认采用系统变量&lt;code>innodb_stats_persistent&lt;/code>的值作为该属性的值。&lt;/p>
&lt;h1 id="基于磁盘的永久性统计数据">基于磁盘的永久性统计数据&lt;a class="anchor" href="#%e5%9f%ba%e4%ba%8e%e7%a3%81%e7%9b%98%e7%9a%84%e6%b0%b8%e4%b9%85%e6%80%a7%e7%bb%9f%e8%ae%a1%e6%95%b0%e6%8d%ae">#&lt;/a>&lt;/h1>
&lt;p>当我们选择把某个表以及该表索引的统计数据存放到磁盘上时，实际上是把这些统计数据存储到了两个表里：&lt;/p>
&lt;p>&lt;code>mysql&amp;gt; SHOW TABLES FROM mysql LIKE 'innodb%'; +---------------------------+ | Tables_in_mysql (innodb%) | +---------------------------+ | innodb_index_stats | | innodb_table_stats | +---------------------------+ 2 rows in set (0.01 sec)&lt;/code> 可以看到，这两个表都位于&lt;code>mysql&lt;/code>系统数据库下面，其中： - &lt;code>innodb_table_stats&lt;/code>存储了关于表的统计数据，每一条记录对应着一个表的统计数据。 - &lt;code>innodb_index_stats&lt;/code>存储了关于索引的统计数据，每一条记录对应着一个索引的一个统计项的统计数据。&lt;/p>
&lt;p>我们下面的任务就是看一下这两个表里边都有什么以及表里的数据是如何生成的。&lt;/p>
&lt;h2 id="innodb_table_stats">innodb_table_stats&lt;a class="anchor" href="#innodb_table_stats">#&lt;/a>&lt;/h2>
&lt;p>直接看一下这个&lt;code>innodb_table_stats&lt;/code>表中的各个列都是干嘛的：
字段名 描述 &lt;code>database_name&lt;/code> 数据库名 &lt;code>table_name&lt;/code> 表名 &lt;code>last_update&lt;/code> 本条记录最后更新时间 &lt;code>n_rows&lt;/code> 表中记录的条数 &lt;code>clustered_index_size&lt;/code> 表的聚簇索引占用的页面数量 &lt;code>sum_of_other_index_sizes&lt;/code> 表的其他索引占用的页面数量 &lt;br>
注意这个表的主键是&lt;code>(database_name,table_name)&lt;/code>，也就是&lt;strong>innodb_table_stats表的每条记录代表着一个表的统计信息&lt;/strong>。我们直接看一下这个表里的内容： &lt;code>mysql&amp;gt; SELECT * FROM mysql.innodb_table_stats; +---------------+---------------+---------------------+--------+----------------------+--------------------------+ | database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes | +---------------+---------------+---------------------+--------+----------------------+--------------------------+ | mysql | gtid_executed | 2018-07-10 23:51:36 | 0 | 1 | 0 | | sys | sys_config | 2018-07-10 23:51:38 | 5 | 1 | 0 | | xiaohaizi | single_table | 2018-12-10 17:03:13 | 9693 | 97 | 175 | +---------------+---------------+---------------------+--------+----------------------+--------------------------+ 3 rows in set (0.01 sec)&lt;/code> 可以看到我们熟悉的&lt;code>single_table&lt;/code>表的统计信息就对应着&lt;code>mysql.innodb_table_stats&lt;/code>的第三条记录。几个重要统计信息项的值如下： - &lt;code>n_rows&lt;/code>的值是&lt;code>9693&lt;/code>，表明&lt;code>single_table&lt;/code>表中大约有&lt;code>9693&lt;/code>条记录，注意这个数据是估计值。 - &lt;code>clustered_index_size&lt;/code>的值是&lt;code>97&lt;/code>，表明&lt;code>single_table&lt;/code>表的聚簇索引占用97个页面，这个值是也是一个估计值。 - &lt;code>sum_of_other_index_sizes&lt;/code>的值是&lt;code>175&lt;/code>，表明&lt;code>single_table&lt;/code>表的其他索引一共占用175个页面，这个值是也是一个估计值。&lt;/p></description></item><item><title>第12章_谁最便宜就选谁-MySQL基于成本的优化</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC12%E7%AB%A0_%E8%B0%81%E6%9C%80%E4%BE%BF%E5%AE%9C%E5%B0%B1%E9%80%89%E8%B0%81-MySQL%E5%9F%BA%E4%BA%8E%E6%88%90%E6%9C%AC%E7%9A%84%E4%BC%98%E5%8C%96/</link><pubDate>Sat, 18 Jan 2025 22:29:53 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC12%E7%AB%A0_%E8%B0%81%E6%9C%80%E4%BE%BF%E5%AE%9C%E5%B0%B1%E9%80%89%E8%B0%81-MySQL%E5%9F%BA%E4%BA%8E%E6%88%90%E6%9C%AC%E7%9A%84%E4%BC%98%E5%8C%96/</guid><description>&lt;p>第12章 谁最便宜就选谁-MySQL基于成本的优化&lt;/p>
&lt;h1 id="什么是成本">什么是成本&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%88%90%e6%9c%ac">#&lt;/a>&lt;/h1>
&lt;p>我们之前老说&lt;code>MySQL&lt;/code>执行一个查询可以有不同的执行方案，它会选择其中成本最低，或者说代价最低的那种方案去真正的执行查询。不过我们之前对&lt;code>成本&lt;/code>的描述是非常模糊的，其实在&lt;code>MySQL&lt;/code>中一条查询语句的执行成本是由下面这两个方面组成的：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>I/O&lt;/code>成本&lt;/p>
&lt;p>我们的表经常使用的&lt;code>MyISAM&lt;/code>、&lt;code>InnoDB&lt;/code>存储引擎都是将数据和索引都存储到磁盘上的，当我们想查询表中的记录时，需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为&lt;code>I/O&lt;/code>成本。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>CPU&lt;/code>成本&lt;/p>
&lt;p>读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为&lt;code>CPU&lt;/code>成本。&lt;/p>
&lt;p>对于&lt;code>InnoDB&lt;/code>存储引擎来说，页是磁盘和内存之间交互的基本单位，设计&lt;code>MySQL&lt;/code>的大佬规定读取一个页面花费的成本默认是&lt;code>1.0&lt;/code>，读取以及检测一条记录是否符合搜索条件的成本默认是&lt;code>0.2&lt;/code>。&lt;code>1.0&lt;/code>、&lt;code>0.2&lt;/code>这些数字称之为&lt;code>成本常数&lt;/code>，这两个成本常数我们最常用到，其余的成本常数我们后边再说。 &lt;code>小贴士：需要注意的是，不管读取记录时需不需要检测是否满足搜索条件，其成本都算是0.2。&lt;/code>&lt;/p>
&lt;h1 id="单表查询的成本">单表查询的成本&lt;a class="anchor" href="#%e5%8d%95%e8%a1%a8%e6%9f%a5%e8%af%a2%e7%9a%84%e6%88%90%e6%9c%ac">#&lt;/a>&lt;/h1>
&lt;h2 id="准备工作">准备工作&lt;a class="anchor" href="#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c">#&lt;/a>&lt;/h2>
&lt;p>为了故事的顺利发展，我们还得把之前用到的&lt;code>single_table&lt;/code>表搬来，怕大家忘了这个表长什么样，再给大家抄一遍： &lt;code>CREATE TABLE single_table ( id INT NOT NULL AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1), UNIQUE KEY idx_key2 (key2), KEY idx_key3 (key3), KEY idx_key_part(key_part1, key_part2, key_part3) ) Engine=InnoDB CHARSET=utf8;&lt;/code> 还是假设这个表里边儿有10000条记录，除&lt;code>id&lt;/code>列外其余的列都插入随机值。下面正式开始我们的表演。&lt;/p>
&lt;h2 id="基于成本的优化步骤">基于成本的优化步骤&lt;a class="anchor" href="#%e5%9f%ba%e4%ba%8e%e6%88%90%e6%9c%ac%e7%9a%84%e4%bc%98%e5%8c%96%e6%ad%a5%e9%aa%a4">#&lt;/a>&lt;/h2>
&lt;p>在一条单表查询语句真正执行之前，&lt;code>MySQL&lt;/code>的查询优化器会找出执行该语句所有可能使用的方案，对比之后找出成本最低的方案，这个成本最低的方案就是所谓的&lt;code>执行计划&lt;/code>，之后才会调用存储引擎提供的接口真正的执行查询，这个过程总结一下就是这样： 1. 根据搜索条件，找出所有可能使用的索引 2. 计算全表扫描的代价 3. 计算使用不同索引执行查询的代价 4. 对比各种执行方案的代价，找出成本最低的那一个&lt;/p>
&lt;p>下面我们就以一个实例来分析一下这些步骤，单表查询语句如下： &lt;code>SELECT * FROM single_table WHERE key1 IN ('a', 'b', 'c') AND key2 &amp;gt; 10 AND key2 &amp;lt; 1000 AND key3 &amp;gt; key2 AND key_part1 LIKE '%hello%' AND common_field = '123';&lt;/code> 乍看上去有点儿复杂，我们一步一步分析一下。&lt;/p></description></item><item><title>第10章_条条大路通罗马-单表访问方法</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC10%E7%AB%A0_%E6%9D%A1%E6%9D%A1%E5%A4%A7%E8%B7%AF%E9%80%9A%E7%BD%97%E9%A9%AC-%E5%8D%95%E8%A1%A8%E8%AE%BF%E9%97%AE%E6%96%B9%E6%B3%95/</link><pubDate>Sat, 18 Jan 2025 22:29:51 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC10%E7%AB%A0_%E6%9D%A1%E6%9D%A1%E5%A4%A7%E8%B7%AF%E9%80%9A%E7%BD%97%E9%A9%AC-%E5%8D%95%E8%A1%A8%E8%AE%BF%E9%97%AE%E6%96%B9%E6%B3%95/</guid><description>&lt;p>第10章 条条大路通罗马-单表访问方法&lt;/p>
&lt;p>对于我们这些&lt;code>MySQL&lt;/code>的使用者来说，&lt;code>MySQL&lt;/code>其实就是一个软件，平时用的最多的就是查询功能。DBA时不时丢过来一些慢查询语句让优化，我们如果连查询是怎么执行的都不清楚还优化个毛线，所以是时候掌握真正的技术了。我们在第一章的时候就曾说过，&lt;code>MySQL Server&lt;/code>有一个称为&lt;code>查询优化器&lt;/code>的模块，一条查询语句进行语法解析之后就会被交给查询优化器来进行优化，优化的结果就是生成一个所谓的&lt;code>执行计划&lt;/code>，这个执行计划表明了应该使用哪些索引进行查询，表之间的连接顺序是什么样的，最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询，并将查询结果返回给用户。不过查询优化这个主题有点儿大，在学会跑之前还得先学会走，所以本章先来看看&lt;code>MySQL&lt;/code>怎么执行单表查询（就是&lt;code>FROM&lt;/code>子句后边只有一个表，最简单的那种查询～）。不过需要强调的一点是，在学习本章前务必看过前面关于记录结构、数据页结构以及索引的部分，如果你不能保证这些东西已经完全掌握，那么本章不适合你。&lt;/p>
&lt;p>为了故事的顺利发展，我们先得有个表： &lt;code>CREATE TABLE single_table ( id INT NOT NULL AUTO_INCREMENT, key1 VARCHAR(100), key2 INT, key3 VARCHAR(100), key_part1 VARCHAR(100), key_part2 VARCHAR(100), key_part3 VARCHAR(100), common_field VARCHAR(100), PRIMARY KEY (id), KEY idx_key1 (key1), UNIQUE KEY idx_key2 (key2), KEY idx_key3 (key3), KEY idx_key_part(key_part1, key_part2, key_part3) ) Engine=InnoDB CHARSET=utf8;&lt;/code> 我们为这个&lt;code>single_table&lt;/code>表建立了1个聚簇索引和4个二级索引，分别是：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>为&lt;code>id&lt;/code>列建立的聚簇索引。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>为&lt;code>key1&lt;/code>列建立的&lt;code>idx_key1&lt;/code>二级索引。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>为&lt;code>key2&lt;/code>列建立的&lt;code>idx_key2&lt;/code>二级索引，而且该索引是唯一二级索引。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>为&lt;code>key3&lt;/code>列建立的&lt;code>idx_key3&lt;/code>二级索引。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>为&lt;code>key_part1&lt;/code>、&lt;code>key_part2&lt;/code>、&lt;code>key_part3&lt;/code>列建立的&lt;code>idx_key_part&lt;/code>二级索引，这也是一个联合索引。&lt;/p>
&lt;p>然后我们需要为这个表插入10000行记录，除&lt;code>id&lt;/code>列外其余的列都插入随机值就好了，具体的插入语句我就不写了，自己写个程序插入吧（id列是自增主键列，不需要我们手动插入）。&lt;/p>
&lt;h1 id="访问方法access-method的概念">访问方法（access method）的概念&lt;a class="anchor" href="#%e8%ae%bf%e9%97%ae%e6%96%b9%e6%b3%95access-method%e7%9a%84%e6%a6%82%e5%bf%b5">#&lt;/a>&lt;/h1>
&lt;p>想必各位都用过高德地图来查找到某个地方的路线吧（此处没有为高德地图打广告的意思，他们没给我钱，大家用百度地图也可以啊），如果我们搜西安钟楼到大雁塔之间的路线的话，地图软件会给出n种路线供我们选择，如果我们实在闲的没事儿干并且足够有钱的话，还可以用南辕北辙的方式绕地球一圈到达目的地。也就是说，不论采用哪一种方式，我们最终的目标就是到达大雁塔这个地方。回到&lt;code>MySQL&lt;/code>中来，我们平时所写的那些查询语句本质上只是一种声明式的语法，只是告诉&lt;code>MySQL&lt;/code>我们要获取的数据符合哪些规则，至于&lt;code>MySQL&lt;/code>背地里是怎么把查询结果搞出来的那是&lt;code>MySQL&lt;/code>自己的事儿。对于单个表的查询来说，设计MySQL的大佬把查询的执行方式大致分为下面两种：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>使用全表扫描进行查询&lt;/p>
&lt;p>这种执行方式很好理解，就是把表的每一行记录都扫一遍嘛，把符合搜索条件的记录加入到结果集就完了。不管是什么查询都可以使用这种方式执行，当然，这种也是最笨的执行方式。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>使用索引进行查询&lt;/p>
&lt;p>因为直接使用全表扫描的方式执行查询要遍历好多记录，所以代价可能太大了。如果查询语句中的搜索条件可以使用到某个索引，那直接使用索引来执行查询可能会加快查询执行的时间。使用索引来执行查询的方式五花八门，又可以细分为许多种类：&lt;/p>
&lt;pre>&lt;code>+ 
&lt;/code>&lt;/pre>
&lt;p>针对主键或唯一二级索引的等值查询&lt;/p></description></item><item><title>第9章_存放页的大池子-InnoDB的表空间</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC9%E7%AB%A0_%E5%AD%98%E6%94%BE%E9%A1%B5%E7%9A%84%E5%A4%A7%E6%B1%A0%E5%AD%90-InnoDB%E7%9A%84%E8%A1%A8%E7%A9%BA%E9%97%B4/</link><pubDate>Sat, 18 Jan 2025 22:29:49 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC9%E7%AB%A0_%E5%AD%98%E6%94%BE%E9%A1%B5%E7%9A%84%E5%A4%A7%E6%B1%A0%E5%AD%90-InnoDB%E7%9A%84%E8%A1%A8%E7%A9%BA%E9%97%B4/</guid><description>&lt;p>第9章 存放页的大池子-InnoDB的表空间&lt;/p>
&lt;p>通过前面儿的内容大家知道，&lt;code>表空间&lt;/code>是一个抽象的概念，对于系统表空间来说，对应着文件系统中一个或多个实际文件；对于每个独立表空间来说，对应着文件系统中一个名为&lt;code>表名.ibd&lt;/code>的实际文件。大家可以把表空间想象成被切分为许许多多个&lt;code>页&lt;/code>的池子，当我们想为某个表插入一条记录的时候，就从池子中捞出一个对应的页来把数据写进去。本章内容会深入到表空间的各个细节中，带领大家在&lt;code>InnoDB&lt;/code>存储结构的池子中畅游。由于本章中将会涉及比较多的概念，虽然这些概念都不难，但是却相互依赖，所以奉劝大家在看的时候：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>不要跳着看！&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>不要跳着看！&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>不要跳着看！&lt;/strong>&lt;/p>
&lt;h1 id="回忆一些旧知识">回忆一些旧知识&lt;a class="anchor" href="#%e5%9b%9e%e5%bf%86%e4%b8%80%e4%ba%9b%e6%97%a7%e7%9f%a5%e8%af%86">#&lt;/a>&lt;/h1>
&lt;h2 id="页类型">页类型&lt;a class="anchor" href="#%e9%a1%b5%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>再一次强调，InnoDB是以页为单位管理存储空间的，我们的聚簇索引（也就是完整的表数据）和其他的二级索引都是以&lt;code>B+&lt;/code>树的形式保存到表空间的，而&lt;code>B+&lt;/code>树的节点就是数据页。我们前面说过，这个数据页的类型名其实是：&lt;code>FIL_PAGE_INDEX&lt;/code>，除了这种存放索引数据的页类型之外，InnoDB也为了不同的目的设计了若干种不同类型的页，为了唤醒大家的记忆，我们再一次把各种常用的页类型提出来：
类型名称 十六进制 描述 &lt;code>FIL_PAGE_TYPE_ALLOCATED&lt;/code> 0x0000 最新分配，还没使用 &lt;code>FIL_PAGE_UNDO_LOG&lt;/code> 0x0002 Undo日志页 &lt;code>FIL_PAGE_INODE&lt;/code> 0x0003 段信息节点 &lt;code>FIL_PAGE_IBUF_FREE_LIST&lt;/code> 0x0004 Insert Buffer空闲列表 &lt;code>FIL_PAGE_IBUF_BITMAP&lt;/code> 0x0005 Insert Buffer位图 &lt;code>FIL_PAGE_TYPE_SYS&lt;/code> 0x0006 系统页 &lt;code>FIL_PAGE_TYPE_TRX_SYS&lt;/code> 0x0007 事务系统数据 &lt;code>FIL_PAGE_TYPE_FSP_HDR&lt;/code> 0x0008 表空间头部信息 &lt;code>FIL_PAGE_TYPE_XDES&lt;/code> 0x0009 扩展描述页 &lt;code>FIL_PAGE_TYPE_BLOB&lt;/code> 0x000A BLOB页 &lt;code>FIL_PAGE_INDEX&lt;/code> 0x45BF 索引页，也就是我们所说的&lt;code>数据页&lt;/code> &lt;br>
因为页类型前面都有个&lt;code>FIL_PAGE&lt;/code>或者&lt;code>FIL_PAGE_TYPE&lt;/code>的前缀，为简便起见我们后边介绍页类型的时候就把这些前缀省略掉了，比方说&lt;code>FIL_PAGE_TYPE_ALLOCATED&lt;/code>类型称为&lt;code>ALLOCATED&lt;/code>类型，&lt;code>FIL_PAGE_INDEX&lt;/code>类型称为&lt;code>INDEX&lt;/code>类型。&lt;/p>
&lt;h2 id="页通用部分">页通用部分&lt;a class="anchor" href="#%e9%a1%b5%e9%80%9a%e7%94%a8%e9%83%a8%e5%88%86">#&lt;/a>&lt;/h2>
&lt;p>我们前面说过数据页，也就是&lt;code>INDEX&lt;/code>类型的页由7个部分组成，其中的两个部分是所有类型的页都通用的。当然我不能寄希望于你把我说的话都记住，所以在这里重新强调一遍，任何类型的页都有下面这种通用的结构：&lt;/p>
&lt;p>&lt;img src="img/09-01.png" alt="" />&lt;/p>
&lt;p>从上图中可以看出，任何类型的页都会包含这两个部分：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>File Header&lt;/code>：记录页的一些通用信息&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>File Trailer&lt;/code>：校验页是否完整，保证从内存到磁盘刷新时内容的一致性。&lt;/p>
&lt;p>对于&lt;code>File Trailer&lt;/code>我们不再做过多强调，全部忘记了的话可以到将数据页的那一章回顾一下。我们这里再强调一遍&lt;code>File Header&lt;/code>的各个组成部分：
名称 占用空间大小 描述 &lt;code>FIL_PAGE_SPACE_OR_CHKSUM&lt;/code> &lt;code>4&lt;/code>字节 页的校验和（checksum值） &lt;code>FIL_PAGE_OFFSET&lt;/code> &lt;code>4&lt;/code>字节 页号 &lt;code>FIL_PAGE_PREV&lt;/code> &lt;code>4&lt;/code>字节 上一个页的页号 &lt;code>FIL_PAGE_NEXT&lt;/code> &lt;code>4&lt;/code>字节 下一个页的页号 &lt;code>FIL_PAGE_LSN&lt;/code> &lt;code>8&lt;/code>字节 页被最后修改时对应的日志序列位置（英文名是：Log Sequence Number） &lt;code>FIL_PAGE_TYPE&lt;/code> &lt;code>2&lt;/code>字节 该页的类型 &lt;code>FIL_PAGE_FILE_FLUSH_LSN&lt;/code> &lt;code>8&lt;/code>字节 仅在系统表空间的一个页中定义，代表文件至少被刷新到了对应的LSN值 &lt;code>FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID&lt;/code> &lt;code>4&lt;/code>字节 页属于哪个表空间 &lt;br>
现在除了名称里边儿带有&lt;code>LSN&lt;/code>的两个字段大家可能看不懂以外，其他的字段肯定都是倍儿熟了，不过我们仍要强调这么几点：&lt;/p></description></item><item><title>第8章_数据的家-MySQL的数据目录</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC8%E7%AB%A0_%E6%95%B0%E6%8D%AE%E7%9A%84%E5%AE%B6-MySQL%E7%9A%84%E6%95%B0%E6%8D%AE%E7%9B%AE%E5%BD%95/</link><pubDate>Sat, 18 Jan 2025 22:29:48 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC8%E7%AB%A0_%E6%95%B0%E6%8D%AE%E7%9A%84%E5%AE%B6-MySQL%E7%9A%84%E6%95%B0%E6%8D%AE%E7%9B%AE%E5%BD%95/</guid><description>&lt;p>第8章 数据的家-MySQL的数据目录&lt;/p>
&lt;h1 id="数据库和文件系统的关系">数据库和文件系统的关系&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%92%8c%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e5%85%b3%e7%b3%bb">#&lt;/a>&lt;/h1>
&lt;p>我们知道像&lt;code>InnoDB&lt;/code>、&lt;code>MyISAM&lt;/code>这样的存储引擎都是把表存储在磁盘上的，而操作系统用来管理磁盘的那个东东又被称为&lt;code>文件系统&lt;/code>，所以用专业一点的话来表述就是：&lt;strong>像 &lt;em>&lt;strong>InnoDB&lt;/strong>&lt;/em> 、 &lt;em>&lt;strong>MyISAM&lt;/strong>&lt;/em> 这样的存储引擎都是把表存储在文件系统上的&lt;/strong>。当我们想读取数据的时候，这些存储引擎会从文件系统中把数据读出来返回给我们，当我们想写入数据的时候，这些存储引擎会把这些数据又写回文件系统。本章就是要介绍一下&lt;code>InnoDB&lt;/code>和&lt;code>MyISAM&lt;/code>这两个存储引擎的数据如何在文件系统中存储的。&lt;/p>
&lt;h1 id="mysql数据目录">MySQL数据目录&lt;a class="anchor" href="#mysql%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95">#&lt;/a>&lt;/h1>
&lt;p>MySQL服务器程序在启动时会到文件系统的某个目录下加载一些文件，之后在运行过程中产生的数据也都会存储到这个目录下的某些文件中，这个目录就称为&lt;code>数据目录&lt;/code>，我们下面就要详细唠唠这个目录下具体都有哪些重要的东西。&lt;/p>
&lt;h2 id="数据目录和安装目录的区别">数据目录和安装目录的区别&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95%e5%92%8c%e5%ae%89%e8%a3%85%e7%9b%ae%e5%bd%95%e7%9a%84%e5%8c%ba%e5%88%ab">#&lt;/a>&lt;/h2>
&lt;p>我们之前只接触过&lt;code>MySQL&lt;/code>的安装目录（在安装&lt;code>MySQL&lt;/code>的时候我们可以自己指定），我们重点强调过这个&lt;code>安装目录&lt;/code>下非常重要的&lt;code>bin&lt;/code>目录，它里边存储了许多关于控制客户端程序和服务器程序的命令（许多可执行文件，比如&lt;code>mysql&lt;/code>，&lt;code>mysqld&lt;/code>，&lt;code>mysqld_safe&lt;/code>等等等等好几十个）。而&lt;code>数据目录&lt;/code>是用来存储&lt;code>MySQL&lt;/code>在运行过程中产生的数据，一定要和本章要讨论的&lt;code>安装目录&lt;/code>区别开！&lt;strong>一定要区分开&lt;/strong>！&lt;strong>一定要区分开&lt;/strong>！&lt;strong>一定要区分开&lt;/strong>！&lt;/p>
&lt;h2 id="如何确定mysql中的数据目录">如何确定MySQL中的数据目录&lt;a class="anchor" href="#%e5%a6%82%e4%bd%95%e7%a1%ae%e5%ae%9amysql%e4%b8%ad%e7%9a%84%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95">#&lt;/a>&lt;/h2>
&lt;p>那说了半天，到底&lt;code>MySQL&lt;/code>把数据都存到哪个路径下呢？其实&lt;code>数据目录&lt;/code>对应着一个系统变量&lt;code>datadir&lt;/code>，我们在使用客户端与服务器建立连接之后查看这个系统变量的值就可以了： &lt;code>mysql&amp;gt; SHOW VARIABLES LIKE 'datadir'; +---------------+-----------------------+ | Variable_name | Value | +---------------+-----------------------+ | datadir | /usr/local/var/mysql/ | +---------------+-----------------------+ 1 row in set (0.00 sec)&lt;/code> 从结果中可以看出，&lt;strong>在我的计算机上&lt;/strong>&lt;code>MySQL&lt;/code>的数据目录就是&lt;code>/usr/local/var/mysql/&lt;/code>，你用你的计算机试试呗～&lt;/p>
&lt;h1 id="数据目录的结构">数据目录的结构&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95%e7%9a%84%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;p>&lt;code>MySQL&lt;/code>在运行过程中都会产生哪些数据呢？当然会包含我们创建的数据库、表、视图和触发器等等的用户数据，除了这些用户数据，为了程序更好的运行，&lt;code>MySQL&lt;/code>也会创建一些其他的额外数据，我们接下来细细的品味一下这个&lt;code>数据目录&lt;/code>下的内容。&lt;/p>
&lt;h2 id="数据库在文件系统中的表示">数据库在文件系统中的表示&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%9c%a8%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e4%b8%ad%e7%9a%84%e8%a1%a8%e7%a4%ba">#&lt;/a>&lt;/h2>
&lt;p>每当我们使用&lt;code>CREATE DATABASE 数据库名&lt;/code>语句创建一个数据库的时候，在文件系统上实际发生了什么呢？其实很简单，&lt;strong>每个数据库都对应数据目录下的一个子目录，或者说对应一个文件夹&lt;/strong>，我们每当我们新建一个数据库时，&lt;code>MySQL&lt;/code>会帮我们做这两件事儿：&lt;/p>
&lt;ol>
&lt;li>&lt;/li>
&lt;/ol>
&lt;p>在&lt;code>数据目录&lt;/code>下创建一个和数据库名同名的子目录（或者说是文件夹）。&lt;/p>
&lt;ol start="2">
&lt;li>&lt;/li>
&lt;/ol>
&lt;p>在该与数据库名同名的子目录下创建一个名为&lt;code>db.opt&lt;/code>的文件，这个文件中包含了该数据库的各种属性，比方说该数据库的字符集和比较规则是什么。&lt;/p>
&lt;p>比方说我们查看一下&lt;strong>在我的计算机上&lt;/strong>当前有哪些数据库： &lt;code>mysql&amp;gt; SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | charset_demo_db | | dahaizi | | mysql | | performance_schema | | sys | | xiaohaizi | +--------------------+ 7 rows in set (0.00 sec)&lt;/code> 可以看到在我的计算机上当前有7个数据库，其中&lt;code>charset_demo_db&lt;/code>、&lt;code>dahaizi&lt;/code>和&lt;code>xiaohaizi&lt;/code>数据库是我们自定义的，其余4个数据库是属于MySQL自带的系统数据库。我们再看一下&lt;strong>我的计算机上&lt;/strong>的&lt;code>数据目录&lt;/code>下的内容： ``` . ├── auto.cnf ├── ca-key.pem ├── ca.pem ├── charset_demo_db ├── client-cert.pem ├── client-key.pem ├── dahaizi ├── ib_buffer_pool ├── ib_logfile0 ├── ib_logfile1 ├── ibdata1 ├── ibtmp1 ├── mysql ├── performance_schema ├── private_key.pem ├── public_key.pem ├── server-cert.pem ├── server-key.pem ├── sys ├── xiaohaizideMacBook-Pro.local.err ├── xiaohaizideMacBook-Pro.local.pid └── xiaohaizi&lt;/p></description></item><item><title>第7章_好东西也得先学会怎么用-B+树索引的使用</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC7%E7%AB%A0_%E5%A5%BD%E4%B8%9C%E8%A5%BF%E4%B9%9F%E5%BE%97%E5%85%88%E5%AD%A6%E4%BC%9A%E6%80%8E%E4%B9%88%E7%94%A8-B+%E6%A0%91%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BD%BF%E7%94%A8/</link><pubDate>Sat, 18 Jan 2025 22:29:46 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC7%E7%AB%A0_%E5%A5%BD%E4%B8%9C%E8%A5%BF%E4%B9%9F%E5%BE%97%E5%85%88%E5%AD%A6%E4%BC%9A%E6%80%8E%E4%B9%88%E7%94%A8-B+%E6%A0%91%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BD%BF%E7%94%A8/</guid><description>&lt;p>第7章 好东西也得先学会怎么用-B+树索引的使用&lt;/p>
&lt;p>我们前面详细、详细又详细的介绍了&lt;code>InnoDB&lt;/code>存储引擎的&lt;code>B+&lt;/code>树索引，我们必须熟悉下面这些结论：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>每个索引都对应一棵&lt;code>B+&lt;/code>树，&lt;code>B+&lt;/code>树分为好多层，最下面一层是叶子节点，其余的是内节点。所有&lt;code>用户记录&lt;/code>都存储在&lt;code>B+&lt;/code>树的叶子节点，所有&lt;code>目录项记录&lt;/code>都存储在内节点。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>InnoDB&lt;/code>存储引擎会自动为主键（如果没有它会自动帮我们添加）建立&lt;code>聚簇索引&lt;/code>，聚簇索引的叶子节点包含完整的用户记录。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>我们可以为自己感兴趣的列建立&lt;code>二级索引&lt;/code>，&lt;code>二级索引&lt;/code>的叶子节点包含的用户记录由&lt;code>索引列 + 主键&lt;/code>组成，所以如果想通过&lt;code>二级索引&lt;/code>来查找完整的用户记录的话，需要通过&lt;code>回表&lt;/code>操作，也就是在通过&lt;code>二级索引&lt;/code>找到主键值之后再到&lt;code>聚簇索引&lt;/code>中查找完整的用户记录。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>B+&lt;/code>树中每层节点都是按照索引列值从小到大的顺序排序而组成了双向链表，而且每个页内的记录（不论是用户记录还是目录项记录）都是按照索引列的值从小到大的顺序而形成了一个单链表。如果是&lt;code>联合索引&lt;/code>的话，则页面和记录先按照&lt;code>联合索引&lt;/code>前面的列排序，如果该列值相同，再按照&lt;code>联合索引&lt;/code>后边的列排序。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>通过索引查找记录是从&lt;code>B+&lt;/code>树的根节点开始，一层一层向下搜索。由于每个页面都按照索引列的值建立了&lt;code>Page Directory&lt;/code>（页目录），所以在这些页面中的查找非常快。&lt;/p>
&lt;p>如果你读上面的几点结论有些任何一点点疑惑的话，那下面的内容不适合你，回过头先去看前面的内容去。&lt;/p>
&lt;h1 id="索引的代价">索引的代价&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95%e7%9a%84%e4%bb%a3%e4%bb%b7">#&lt;/a>&lt;/h1>
&lt;p>在熟悉了&lt;code>B+&lt;/code>树索引原理之后，本篇文章的主题是介绍如何更好的使用索引，虽然索引是个好东西，可不能乱建，在介绍如何更好的使用索引之前先要了解一下使用这玩意儿的代价，它在空间和时间上都会拖后腿：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>空间上的代价&lt;/p>
&lt;p>这个是显而易见的，每建立一个索引都要为它建立一棵&lt;code>B+&lt;/code>树，每一棵&lt;code>B+&lt;/code>树的每一个节点都是一个数据页，一个页默认会占用&lt;code>16KB&lt;/code>的存储空间，一棵很大的&lt;code>B+&lt;/code>树由许多数据页组成，那可是很大的一片存储空间呢。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>时间上的代价&lt;/p>
&lt;p>每次对表中的数据进行增、删、改操作时，都需要去修改各个&lt;code>B+&lt;/code>树索引。而且我们讲过，&lt;code>B+&lt;/code>树每层节点都是按照索引列的值从小到大的顺序排序而组成了双向链表。不论是叶子节点中的记录，还是内节点中的记录（也就是不论是用户记录还是目录项记录）都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏，所以存储引擎需要额外的时间进行一些记录移位，页面分裂、页面回收什么的操作来维护好节点和记录的排序。如果我们建了许多索引，每个索引对应的&lt;code>B+&lt;/code>树都要进行相关的维护操作，这还能不给性能拖后腿么？&lt;/p>
&lt;p>所以说，一个表上索引建的越多，就会占用越多的存储空间，在增删改记录的时候性能就越差。为了能建立又好又少的索引，我们先得学学这些索引在哪些条件下起作用的。&lt;/p>
&lt;h1 id="b树索引适用的条件">B+树索引适用的条件&lt;a class="anchor" href="#b%e6%a0%91%e7%b4%a2%e5%bc%95%e9%80%82%e7%94%a8%e7%9a%84%e6%9d%a1%e4%bb%b6">#&lt;/a>&lt;/h1>
&lt;p>下面我们将介绍许多种让&lt;code>B+&lt;/code>树索引发挥最大效能的技巧和注意事项，不过大家要清楚，所有的技巧都是源自你对&lt;code>B+&lt;/code>树索引本质的理解，所以如果你还不能保证对&lt;code>B+&lt;/code>树索引充分的理解，那么再次建议回过头把前面的内容看完了再来，要不然读文章对你来说是一种折磨。首先，&lt;code>B+&lt;/code>树索引并不是万能的，并不是所有的查询语句都能用到我们建立的索引。下面介绍几个我们可能使用&lt;code>B+&lt;/code>树索引来进行查询的情况。为了故事的顺利发展，我们需要先创建一个表，这个表是用来存储人的一些基本信息的： &lt;code>CREATE TABLE person_info( id INT NOT NULL auto_increment, name VARCHAR(100) NOT NULL, birthday DATE NOT NULL, phone_number CHAR(11) NOT NULL, country varchar(100) NOT NULL, PRIMARY KEY (id), KEY idx_name_birthday_phone_number (name, birthday, phone_number) );&lt;/code> 对于这个&lt;code>person_info&lt;/code>表我们需要注意两点：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>表中的主键是&lt;code>id&lt;/code>列，它存储一个自动递增的整数。所以&lt;code>InnoDB&lt;/code>存储引擎会自动为&lt;code>id&lt;/code>列建立聚簇索引。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>我们额外定义了一个二级索引&lt;code>idx_name_birthday_phone_number&lt;/code>，它是由3个列组成的联合索引。所以在这个索引对应的&lt;code>B+&lt;/code>树的叶子节点处存储的用户记录只保留&lt;code>name&lt;/code>、&lt;code>birthday&lt;/code>、&lt;code>phone_number&lt;/code>这三个列的值以及主键&lt;code>id&lt;/code>的值，并不会保存&lt;code>country&lt;/code>列的值。&lt;/p>
&lt;p>从这两点注意中我们可以再次看到，一个表中有多少索引就会建立多少棵&lt;code>B+&lt;/code>树，&lt;code>person_info&lt;/code>表会为聚簇索引和&lt;code>idx_name_birthday_phone_number&lt;/code>索引建立2棵&lt;code>B+&lt;/code>树。下面我们画一下索引&lt;code>idx_name_birthday_phone_number&lt;/code>的示意图，不过既然我们已经掌握了&lt;code>InnoDB&lt;/code>的&lt;code>B+&lt;/code>树索引原理，那我们在画图的时候为了让图更加清晰，所以在省略一些不必要的部分，比如记录的额外信息，各页面的页号等等，其中内节点中目录项记录的页号信息我们用箭头来代替，在记录结构中只保留&lt;code>name&lt;/code>、&lt;code>birthday&lt;/code>、&lt;code>phone_number&lt;/code>、&lt;code>id&lt;/code>这四个列的真实数据值，所以示意图就长这样（留心的同学看出来了，这其实和《高性能MySQL》里举的例子的图差不多，我觉得这个例子特别好，所以就借鉴了一下）：&lt;/p>
&lt;p>&lt;img src="img/07-01.png" alt="" />&lt;/p>
&lt;p>为了方便大家理解，我们特意标明了哪些是内节点，哪些是叶子节点。再次强调一下，内节点中存储的是&lt;code>目录项记录&lt;/code>，叶子节点中存储的是&lt;code>用户记录&lt;/code>（由于不是聚簇索引，所以用户记录是不完整的，缺少&lt;code>country&lt;/code>列的值）。从图中可以看出，这个&lt;code>idx_name_birthday_phone_number&lt;/code>索引对应的&lt;code>B+&lt;/code>树中页面和记录的排序方式就是这样的：&lt;/p></description></item><item><title>第6章_快速查询的秘籍-B+树索引</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC6%E7%AB%A0_%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2%E7%9A%84%E7%A7%98%E7%B1%8D-B+%E6%A0%91%E7%B4%A2%E5%BC%95/</link><pubDate>Sat, 18 Jan 2025 22:29:45 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC6%E7%AB%A0_%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2%E7%9A%84%E7%A7%98%E7%B1%8D-B+%E6%A0%91%E7%B4%A2%E5%BC%95/</guid><description>&lt;p>第6章 快速查询的秘籍-B+树索引&lt;/p>
&lt;p>前面我们详细介绍了&lt;code>InnoDB&lt;/code>数据页的7个组成部分，知道了各个数据页可以组成一个&lt;code>双向链表&lt;/code>，而每个数据页中的记录会按照主键值从小到大的顺序组成一个&lt;code>单向链表&lt;/code>，每个数据页都会为存储在它里边儿的记录生成一个&lt;code>页目录&lt;/code>，在通过主键查找某条记录的时候可以在&lt;code>页目录&lt;/code>中使用二分法快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录（如果你对这段话有一丁点儿疑惑，那么接下来的部分不适合你，返回去看一下数据页结构吧）。页和记录的关系示意图如下：&lt;/p>
&lt;p>&lt;img src="img/06-01.png" alt="" />&lt;/p>
&lt;p>其中页a、页b、页c &amp;hellip; 页n 这些页可以不在物理结构上相连，只要通过双向链表相关联即可。&lt;/p>
&lt;h1 id="没有索引的查找">没有索引的查找&lt;a class="anchor" href="#%e6%b2%a1%e6%9c%89%e7%b4%a2%e5%bc%95%e7%9a%84%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h1>
&lt;p>本集的主题是&lt;code>索引&lt;/code>，在正式介绍&lt;code>索引&lt;/code>之前，我们需要了解一下没有索引的时候是怎么查找记录的。为了方便大家理解，我们下面先只介绍搜索条件为对某个列精确匹配的情况，所谓精确匹配，就是搜索条件中用等于&lt;code>=&lt;/code>连接起的表达式，比如这样： &lt;code>SELECT [列名列表] FROM 表名 WHERE 列名 = xxx;&lt;/code>&lt;/p>
&lt;h2 id="在一个页中的查找">在一个页中的查找&lt;a class="anchor" href="#%e5%9c%a8%e4%b8%80%e4%b8%aa%e9%a1%b5%e4%b8%ad%e7%9a%84%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h2>
&lt;p>假设目前表中的记录比较少，所有的记录都可以被存放到一个页中，在查找记录的时候可以根据搜索条件的不同分为两种情况：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>以主键为搜索条件&lt;/p>
&lt;p>这个查找过程我们已经很熟悉了，可以在&lt;code>页目录&lt;/code>中使用二分法快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>以其他列作为搜索条件&lt;/p>
&lt;p>对非主键列的查找的过程可就不这么幸运了，因为在数据页中并没有对非主键列建立所谓的&lt;code>页目录&lt;/code>，所以我们无法通过二分法快速定位相应的&lt;code>槽&lt;/code>。这种情况下只能从&lt;code>最小记录&lt;/code>开始依次遍历单链表中的每条记录，然后对比每条记录是不是符合搜索条件。很显然，这种查找的效率是非常低的。&lt;/p>
&lt;h2 id="在很多页中查找">在很多页中查找&lt;a class="anchor" href="#%e5%9c%a8%e5%be%88%e5%a4%9a%e9%a1%b5%e4%b8%ad%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h2>
&lt;p>大部分情况下我们表中存放的记录都是非常多的，需要好多的数据页来存储这些记录。在很多页中查找记录的话可以分为两个步骤：&lt;/p>
&lt;ol>
&lt;li>定位到记录所在的页。&lt;/li>
&lt;li>从所在的页内中查找相应的记录。&lt;/li>
&lt;/ol>
&lt;p>在没有索引的情况下，不论是根据主键列或者其他列的值进行查找，&lt;strong>由于我们并不能快速的定位到记录所在的页，所以只能从第一个页沿着双向链表一直往下找，在每一个页中根据我们刚刚介绍过的查找方式去查找指定的记录&lt;/strong>。因为要遍历所有的数据页，所以这种方式显然是超级耗时的，如果一个表有一亿条记录，使用这种方式去查找记录那要等到猴年马月才能等到查找结果。所以祖国和人民都在期盼一种能高效完成搜索的方法，&lt;code>索引&lt;/code>同志就要亮相登台了。&lt;/p>
&lt;h1 id="索引">索引&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95">#&lt;/a>&lt;/h1>
&lt;p>为了故事的顺利发展，我们先建一个表： &lt;code>mysql&amp;gt; CREATE TABLE index_demo( -&amp;gt; c1 INT, -&amp;gt; c2 INT, -&amp;gt; c3 CHAR(1), -&amp;gt; PRIMARY KEY(c1) -&amp;gt; ) ROW_FORMAT = Compact; Query OK, 0 rows affected (0.03 sec)&lt;/code> 这个新建的&lt;code>index_demo&lt;/code>表中有2个&lt;code>INT&lt;/code>类型的列，1个&lt;code>CHAR(1)&lt;/code>类型的列，而且我们规定了&lt;code>c1&lt;/code>列为主键，这个表使用&lt;code>Compact&lt;/code>行格式来实际存储记录的。为了我们理解上的方便，我们简化了一下&lt;code>index_demo&lt;/code>表的行格式示意图：&lt;/p>
&lt;p>&lt;img src="img/06-02.png" alt="" />&lt;/p>
&lt;p>我们只在示意图里展示记录的这几个部分：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>record_type&lt;/code>：记录头信息的一项属性，表示记录的类型，&lt;code>0&lt;/code>表示普通记录、&lt;code>2&lt;/code>表示最小记录、&lt;code>3&lt;/code>表示最大记录、&lt;code>1&lt;/code>我们还没用过，等会再说～&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>next_record&lt;/code>：记录头信息的一项属性，表示下一条地址相对于本条记录的地址偏移量，为了方便大家理解，我们都会用箭头来表明下一条记录是谁。&lt;/p></description></item><item><title>第5章 盛放记录的大盒子-InnoDB数据页结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC5%E7%AB%A0-%E7%9B%9B%E6%94%BE%E8%AE%B0%E5%BD%95%E7%9A%84%E5%A4%A7%E7%9B%92%E5%AD%90-InnoDB%E6%95%B0%E6%8D%AE%E9%A1%B5%E7%BB%93%E6%9E%84/</link><pubDate>Sat, 18 Jan 2025 22:29:43 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC5%E7%AB%A0-%E7%9B%9B%E6%94%BE%E8%AE%B0%E5%BD%95%E7%9A%84%E5%A4%A7%E7%9B%92%E5%AD%90-InnoDB%E6%95%B0%E6%8D%AE%E9%A1%B5%E7%BB%93%E6%9E%84/</guid><description>&lt;p>第5章 盛放记录的大盒子-InnoDB数据页结构&lt;/p>
&lt;h1 id="不同类型的页简介">不同类型的页简介&lt;a class="anchor" href="#%e4%b8%8d%e5%90%8c%e7%b1%bb%e5%9e%8b%e7%9a%84%e9%a1%b5%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;p>前面我们简单提了一下&lt;code>页&lt;/code>的概念，它是&lt;code>InnoDB&lt;/code>管理存储空间的基本单位，一个页的大小一般是&lt;code>16KB&lt;/code>。&lt;code>InnoDB&lt;/code>为了不同的目的而设计了许多种不同类型的&lt;code>页&lt;/code>，比如存放表空间头部信息的页，存放&lt;code>Insert Buffer&lt;/code>信息的页，存放&lt;code>INODE&lt;/code>信息的页，存放&lt;code>undo&lt;/code>日志信息的页等等等等。当然了，如果我说的这些名词你一个都没有听过，就当我放了个屁吧～ 不过这没有一毛钱关系，我们今儿个也不准备说这些类型的页，我们聚焦的是那些存放我们表中记录的那种类型的页，官方称这种存放记录的页为索引（&lt;code>INDEX&lt;/code>）页，鉴于我们还没有了解过索引是个什么东西，而这些表中的记录就是我们日常口中所称的&lt;code>数据&lt;/code>，所以目前还是叫这种存放记录的页为&lt;code>数据页&lt;/code>吧。&lt;/p>
&lt;h1 id="数据页结构的快速浏览">数据页结构的快速浏览&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e9%a1%b5%e7%bb%93%e6%9e%84%e7%9a%84%e5%bf%ab%e9%80%9f%e6%b5%8f%e8%a7%88">#&lt;/a>&lt;/h1>
&lt;p>数据页代表的这块&lt;code>16KB&lt;/code>大小的存储空间可以被划分为多个部分，不同部分有不同的功能，各个部分如图所示：&lt;/p>
&lt;p>&lt;img src="img/05-01.png" alt="" />&lt;/p>
&lt;p>从图中可以看出，一个&lt;code>InnoDB&lt;/code>数据页的存储空间大致被划分成了&lt;code>7&lt;/code>个部分，有的部分占用的字节数是确定的，有的部分占用的字节数是不确定的。下面我们用表格的方式来大致描述一下这7个部分都存储一些什么内容（快速的瞅一眼就行了，后边会详细介绍的）：
名称 中文名 占用空间大小 简单描述 &lt;code>File Header&lt;/code> 文件头部 &lt;code>38&lt;/code>字节 页的一些通用信息 &lt;code>Page Header&lt;/code> 页面头部 &lt;code>56&lt;/code>字节 数据页专有的一些信息 &lt;code>Infimum + Supremum&lt;/code> 最小记录和最大记录 &lt;code>26&lt;/code>字节 两个虚拟的行记录 &lt;code>User Records&lt;/code> 用户记录 不确定 实际存储的行记录内容 &lt;code>Free Space&lt;/code> 空闲空间 不确定 页中尚未使用的空间 &lt;code>Page Directory&lt;/code> 页面目录 不确定 页中的某些记录的相对位置 &lt;code>File Trailer&lt;/code> 文件尾部 &lt;code>8&lt;/code>字节 校验页是否完整 &lt;br>
&lt;code>小贴士：我们接下来并不打算按照页中各个部分的出现顺序来依次介绍它们，因为各个部分中会出现很多大家目前不理解的概念，这会打击各位读文章的信心与兴趣，希望各位能接受这种拍摄手法～&lt;/code>&lt;/p>
&lt;h1 id="记录在页中的存储">记录在页中的存储&lt;a class="anchor" href="#%e8%ae%b0%e5%bd%95%e5%9c%a8%e9%a1%b5%e4%b8%ad%e7%9a%84%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h1>
&lt;p>在页的7个组成部分中，我们自己存储的记录会按照我们指定的&lt;code>行格式&lt;/code>存储到&lt;code>User Records&lt;/code>部分。但是在一开始生成页的时候，其实并没有&lt;code>User Records&lt;/code>这个部分，每当我们插入一条记录，都会从&lt;code>Free Space&lt;/code>部分，也就是尚未使用的存储空间中申请一个记录大小的空间划分到&lt;code>User Records&lt;/code>部分，当&lt;code>Free Space&lt;/code>部分的空间全部被&lt;code>User Records&lt;/code>部分替代掉之后，也就意味着这个页使用完了，如果还有新的记录插入的话，就需要去申请新的页了，这个过程的图示如下：&lt;/p>
&lt;p>&lt;img src="img/05-02.png" alt="" />&lt;/p>
&lt;p>为了更好的管理在&lt;code>User Records&lt;/code>中的这些记录，&lt;code>InnoDB&lt;/code>可费了一番力气呢，在哪费力气了呢？不就是把记录按照指定的行格式一条一条摆在&lt;code>User Records&lt;/code>部分么？其实这话还得从记录行格式的&lt;code>记录头信息&lt;/code>中说起。&lt;/p>
&lt;h2 id="记录头信息的秘密">记录头信息的秘密&lt;a class="anchor" href="#%e8%ae%b0%e5%bd%95%e5%a4%b4%e4%bf%a1%e6%81%af%e7%9a%84%e7%a7%98%e5%af%86">#&lt;/a>&lt;/h2>
&lt;p>为了故事的顺利发展，我们先创建一个表： &lt;code>mysql&amp;gt; CREATE TABLE page_demo( -&amp;gt; c1 INT, -&amp;gt; c2 INT, -&amp;gt; c3 VARCHAR(10000), -&amp;gt; PRIMARY KEY (c1) -&amp;gt; ) CHARSET=ascii ROW_FORMAT=Compact; Query OK, 0 rows affected (0.03 sec)&lt;/code> 这个新创建的&lt;code>page_demo&lt;/code>表有3个列，其中&lt;code>c1&lt;/code>和&lt;code>c2&lt;/code>列是用来存储整数的，&lt;code>c3&lt;/code>列是用来存储字符串的。需要注意的是，&lt;strong>我们把 &lt;em>&lt;strong>c1&lt;/strong>&lt;/em> 列指定为主键，所以在具体的行格式中InnoDB就没必要为我们去创建那个所谓的 &lt;em>&lt;strong>row_id&lt;/strong>&lt;/em> 隐藏列了&lt;/strong>。而且我们为这个表指定了&lt;code>ascii&lt;/code>字符集以及&lt;code>Compact&lt;/code>的行格式。所以这个表中记录的行格式示意图就是这样的：&lt;/p></description></item><item><title>第4章_从一条记录说起-InnoDB记录结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC4%E7%AB%A0_%E4%BB%8E%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E8%AF%B4%E8%B5%B7-InnoDB%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%84/</link><pubDate>Sat, 18 Jan 2025 22:29:42 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC4%E7%AB%A0_%E4%BB%8E%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E8%AF%B4%E8%B5%B7-InnoDB%E8%AE%B0%E5%BD%95%E7%BB%93%E6%9E%84/</guid><description>&lt;p>第4章 从一条记录说起-InnoDB记录结构&lt;/p>
&lt;h1 id="准备工作">准备工作&lt;a class="anchor" href="#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c">#&lt;/a>&lt;/h1>
&lt;p>到现在为止，&lt;code>MySQL&lt;/code>对于我们来说还是一个黑盒，我们只负责使用客户端发送请求并等待服务器返回结果，表中的数据到底存到了哪里？以什么格式存放的？&lt;code>MySQL&lt;/code>是以什么方式来访问的这些数据？这些问题我们统统不知道，对于未知领域的探索向来就是社会主义核心价值观中的一部分，作为新一代社会主义接班人，不把它们搞懂怎么支援祖国建设呢？&lt;/p>
&lt;p>我们前面介绍请求处理过程的时候提到过，&lt;code>MySQL&lt;/code>服务器上负责对表中数据的读取和写入工作的部分是&lt;code>存储引擎&lt;/code>，而服务器又支持不同类型的存储引擎，比如&lt;code>InnoDB&lt;/code>、&lt;code>MyISAM&lt;/code>、&lt;code>Memory&lt;/code>什么的，不同的存储引擎一般是由不同的人为实现不同的特性而开发的，&lt;strong>真实数据在不同存储引擎中存放的格式一般是不同的&lt;/strong>，甚至有的存储引擎比如&lt;code>Memory&lt;/code>都不用磁盘来存储数据，也就是说关闭服务器后表中的数据就消失了。由于&lt;code>InnoDB&lt;/code>是&lt;code>MySQL&lt;/code>默认的存储引擎，也是我们最常用到的存储引擎，我们也没有那么多时间去把各个存储引擎的内部实现都看一遍，所以本集要介绍的是使用&lt;code>InnoDB&lt;/code>作为存储引擎的数据存储结构，了解了一个存储引擎的数据存储结构之后，其他的存储引擎都是依葫芦画瓢，等我们用到了再说。&lt;/p>
&lt;h1 id="innodb页简介">InnoDB页简介&lt;a class="anchor" href="#innodb%e9%a1%b5%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;p>&lt;code>InnoDB&lt;/code>是一个将表中的数据存储到磁盘上的存储引擎，所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的，所以需要把磁盘中的数据加载到内存中，如果是处理写入或修改请求的话，还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢，和内存读写差了几个数量级，所以当我们想从表中获取某些记录时，&lt;code>InnoDB&lt;/code>存储引擎需要一条一条的把记录从磁盘上读出来么？不，那样会慢死，&lt;code>InnoDB&lt;/code>采取的方式是：&lt;strong>将数据划分为若干个页，以页作为磁盘和内存之间交互的基本单位，InnoDB中页的大小一般为 &lt;em>&lt;strong>16&lt;/strong>&lt;/em> KB&lt;/strong>。也就是在一般情况下，一次最少从磁盘中读取16KB的内容到内存中，一次最少把内存中的16KB内容刷新到磁盘中。&lt;/p>
&lt;h1 id="innodb行格式">InnoDB行格式&lt;a class="anchor" href="#innodb%e8%a1%8c%e6%a0%bc%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>我们平时是以记录为单位来向表中插入数据的，这些记录在磁盘上的存放方式也被称为&lt;code>行格式&lt;/code>或者&lt;code>记录格式&lt;/code>。设计&lt;code>InnoDB&lt;/code>存储引擎的大佬们到现在为止设计了4种不同类型的&lt;code>行格式&lt;/code>，分别是&lt;code>Compact&lt;/code>、&lt;code>Redundant&lt;/code>、&lt;code>Dynamic&lt;/code>和&lt;code>Compressed&lt;/code>行格式，随着时间的推移，他们可能会设计出更多的行格式，但是不管怎么变，在原理上大体都是相同的。&lt;/p>
&lt;h2 id="指定行格式的语法">指定行格式的语法&lt;a class="anchor" href="#%e6%8c%87%e5%ae%9a%e8%a1%8c%e6%a0%bc%e5%bc%8f%e7%9a%84%e8%af%ad%e6%b3%95">#&lt;/a>&lt;/h2>
&lt;p>我们可以在创建或修改表的语句中指定&lt;code>行格式&lt;/code>： ``` CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称&lt;/p>
&lt;p>ALTER TABLE 表名 ROW_FORMAT=行格式名称 &lt;code>比如我们在&lt;/code>xiaohaizi&lt;code>数据库里创建一个演示用的表&lt;/code>record_format_demo&lt;code>，可以这样指定它的&lt;/code>行格式&lt;code>：&lt;/code> mysql&amp;gt; USE xiaohaizi; Database changed&lt;/p>
&lt;p>mysql&amp;gt; CREATE TABLE record_format_demo ( -&amp;gt; c1 VARCHAR(10), -&amp;gt; c2 VARCHAR(10) NOT NULL, -&amp;gt; c3 CHAR(10), -&amp;gt; c4 VARCHAR(10) -&amp;gt; ) CHARSET=ascii ROW_FORMAT=COMPACT; Query OK, 0 rows affected (0.03 sec) &lt;code>可以看到我们刚刚创建的这个表的&lt;/code>行格式&lt;code>就是&lt;/code>Compact&lt;code>，另外，我们还显式指定了这个表的字符集为&lt;/code>ascii&lt;code>，因为&lt;/code>ascii&lt;code>字符集只包括空格、标点符号、数字、大小写字母和一些不可见字符，所以我们的汉字是不能存到这个表里的。我们现在向这个表中插入两条记录：&lt;/code> mysql&amp;gt; INSERT INTO record_format_demo(c1, c2, c3, c4) VALUES(&amp;lsquo;aaaa&amp;rsquo;, &amp;lsquo;bbb&amp;rsquo;, &amp;lsquo;cc&amp;rsquo;, &amp;rsquo;d&amp;rsquo;), (&amp;rsquo;eeee&amp;rsquo;, &amp;lsquo;fff&amp;rsquo;, NULL, NULL); Query OK, 2 rows affected (0.02 sec) Records: 2 Duplicates: 0 Warnings: 0 &lt;code>现在表中的记录就是这个样子的：&lt;/code> mysql&amp;gt; SELECT * FROM record_format_demo; +&amp;mdash;&amp;mdash;+&amp;mdash;&amp;ndash;+&amp;mdash;&amp;mdash;+&amp;mdash;&amp;mdash;+ | c1 | c2 | c3 | c4 | +&amp;mdash;&amp;mdash;+&amp;mdash;&amp;ndash;+&amp;mdash;&amp;mdash;+&amp;mdash;&amp;mdash;+ | aaaa | bbb | cc | d | | eeee | fff | NULL | NULL | +&amp;mdash;&amp;mdash;+&amp;mdash;&amp;ndash;+&amp;mdash;&amp;mdash;+&amp;mdash;&amp;mdash;+ 2 rows in set (0.00 sec)&lt;/p></description></item><item><title>第3章_乱码的前世今生-字符集和比较规则</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC3%E7%AB%A0_%E4%B9%B1%E7%A0%81%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F-%E5%AD%97%E7%AC%A6%E9%9B%86%E5%92%8C%E6%AF%94%E8%BE%83%E8%A7%84%E5%88%99/</link><pubDate>Sat, 18 Jan 2025 22:29:41 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC3%E7%AB%A0_%E4%B9%B1%E7%A0%81%E7%9A%84%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F-%E5%AD%97%E7%AC%A6%E9%9B%86%E5%92%8C%E6%AF%94%E8%BE%83%E8%A7%84%E5%88%99/</guid><description>&lt;p>第3章 乱码的前世今生-字符集和比较规则&lt;/p>
&lt;h1 id="字符集和比较规则简介">字符集和比较规则简介&lt;a class="anchor" href="#%e5%ad%97%e7%ac%a6%e9%9b%86%e5%92%8c%e6%af%94%e8%be%83%e8%a7%84%e5%88%99%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;h2 id="字符集简介">字符集简介&lt;a class="anchor" href="#%e5%ad%97%e7%ac%a6%e9%9b%86%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;p>我们知道在计算机中只能存储二进制数据，那该怎么存储字符串呢？当然是建立字符与二进制数据的映射关系了，建立这个关系最起码要搞清楚两件事儿：&lt;/p>
&lt;ol>
&lt;li>你要把哪些字符映射成二进制数据？ 也就是界定清楚字符范围。&lt;/li>
&lt;li>怎么映射？ 将一个字符映射成一个二进制数据的过程也叫做&lt;code>编码&lt;/code>，将一个二进制数据映射到一个字符的过程叫做&lt;code>解码&lt;/code>。&lt;/li>
&lt;/ol>
&lt;p>人们抽象出一个&lt;code>字符集&lt;/code>的概念来描述某个字符范围的编码规则。比方说我们来自定义一个名称为&lt;code>xiaohaizi&lt;/code>的字符集，它包含的字符范围和编码规则如下：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>包含字符&lt;code>'a'&lt;/code>、&lt;code>'b'&lt;/code>、&lt;code>'A'&lt;/code>、&lt;code>'B'&lt;/code>。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>编码规则如下：&lt;/p>
&lt;p>采用1个字节编码一个字符的形式，字符和字节的映射关系如下： &lt;code>'a' -&amp;gt; 00000001 (十六进制：0x01) 'b' -&amp;gt; 00000010 (十六进制：0x02) 'A' -&amp;gt; 00000011 (十六进制：0x03) 'B' -&amp;gt; 00000100 (十六进制：0x04)&lt;/code>&lt;/p>
&lt;p>有了&lt;code>xiaohaizi&lt;/code>字符集，我们就可以用二进制形式表示一些字符串了，下面是一些字符串用&lt;code>xiaohaizi&lt;/code>字符集编码后的二进制表示： &lt;code>'bA' -&amp;gt; 0000001000000011 (十六进制：0x0203) 'baB' -&amp;gt; 000000100000000100000100 (十六进制：0x020104) 'cd' -&amp;gt; 无法表示，字符集xiaohaizi不包含字符'c'和'd'&lt;/code>&lt;/p>
&lt;h2 id="比较规则简介">比较规则简介&lt;a class="anchor" href="#%e6%af%94%e8%be%83%e8%a7%84%e5%88%99%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h2>
&lt;p>在我们确定了&lt;code>xiaohaizi&lt;/code>字符集表示字符的范围以及编码规则后，怎么比较两个字符的大小呢？最容易想到的就是直接比较这两个字符对应的二进制编码的大小，比方说字符&lt;code>'a'&lt;/code>的编码为&lt;code>0x01&lt;/code>，字符&lt;code>'b'&lt;/code>的编码为&lt;code>0x02&lt;/code>，所以&lt;code>'a'&lt;/code>小于&lt;code>'b'&lt;/code>，这种简单的比较规则也可以被称为二进制比较规则，英文名为&lt;code>binary collation&lt;/code>。&lt;/p>
&lt;p>二进制比较规则是简单，但有时候并不符合现实需求，比如在很多场合对于英文字符我们都是不区分大小写的，也就是说&lt;code>'a'&lt;/code>和&lt;code>'A'&lt;/code>是相等的，在这种场合下就不能简单粗暴的使用二进制比较规则了，这时候我们可以这样指定比较规则：&lt;/p>
&lt;ol>
&lt;li>将两个大小写不同的字符全都转为大写或者小写。&lt;/li>
&lt;li>再比较这两个字符对应的二进制数据。&lt;/li>
&lt;/ol>
&lt;p>这是一种稍微复杂一点点的比较规则，但是实际生活中的字符不止英文字符一种，比如我们的汉字有几万之多，对于某一种字符集来说，比较两个字符大小的规则可以制定出很多种，也就是说&lt;strong>同一种字符集可以有多种比较规则&lt;/strong>，我们稍后就要介绍各种现实生活中用的字符集以及它们的一些比较规则。&lt;/p>
&lt;h2 id="一些重要的字符集">一些重要的字符集&lt;a class="anchor" href="#%e4%b8%80%e4%ba%9b%e9%87%8d%e8%a6%81%e7%9a%84%e5%ad%97%e7%ac%a6%e9%9b%86">#&lt;/a>&lt;/h2>
&lt;p>不幸的是，这个世界太大了，不同的人制定出了好多种&lt;code>字符集&lt;/code>，它们表示的字符范围和用到的编码规则可能都不一样。我们看一下一些常用字符集的情况：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ASCII&lt;/code>字符集&lt;/p>
&lt;p>共收录128个字符，包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符，所以可以使用1个字节来进行编码，我们看一些字符的编码方式： &lt;code>'L' -&amp;gt; 01001100（十六进制：0x4C，十进制：76） 'M' -&amp;gt; 01001101（十六进制：0x4D，十进制：77）&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>ISO 8859-1&lt;/code>字符集&lt;/p>
&lt;p>共收录256个字符，是在&lt;code>ASCII&lt;/code>字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母)，也可以使用1个字节来进行编码。这个字符集也有一个别名&lt;code>latin1&lt;/code>。&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>GB2312&lt;/code>字符集&lt;/p>
&lt;p>收录了汉字以及拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母。其中收录汉字6763个，其他文字符号682个。同时这种字符集又兼容&lt;code>ASCII&lt;/code>字符集，所以在编码方式上显得有些奇怪：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>如果该字符在&lt;code>ASCII&lt;/code>字符集中，则采用1字节编码。&lt;/p></description></item><item><title>第2章_MySQL的调控按钮-启动选项和系统变量</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC2%E7%AB%A0_MySQL%E7%9A%84%E8%B0%83%E6%8E%A7%E6%8C%89%E9%92%AE-%E5%90%AF%E5%8A%A8%E9%80%89%E9%A1%B9%E5%92%8C%E7%B3%BB%E7%BB%9F%E5%8F%98%E9%87%8F/</link><pubDate>Sat, 18 Jan 2025 22:29:39 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC2%E7%AB%A0_MySQL%E7%9A%84%E8%B0%83%E6%8E%A7%E6%8C%89%E9%92%AE-%E5%90%AF%E5%8A%A8%E9%80%89%E9%A1%B9%E5%92%8C%E7%B3%BB%E7%BB%9F%E5%8F%98%E9%87%8F/</guid><description>&lt;p>第2章 MySQL的调控按钮-启动选项和系统变量&lt;/p>
&lt;p>如果你用过手机，你的手机上一定有一个设置的功能，你可以选择设置手机的来电铃声、设置音量大小、设置解锁密码等等。假如没有这些设置功能，我们的生活将置于尴尬的境地，比如在图书馆里无法把手机设置为静音，无法把流量开关关掉以节省流量，在别人得知解锁密码后无法更改密码～ &lt;code>MySQL&lt;/code>的服务器程序和客户端程序也有很多设置项，比如对于&lt;code>MySQL&lt;/code>服务器程序，我们可以指定诸如允许同时连入的客户端数量、客户端和服务器通信方式、表的默认存储引擎、查询缓存的大小等设置项。对于&lt;code>MySQL&lt;/code>客户端程序，我们之前已经见识过了，可以指定需要连接的服务器程序所在主机的主机名或IP地址、用户名及密码等信息。&lt;/p>
&lt;p>这些设置项一般都有各自的默认值，比方说服务器允许同时连入的客户端的默认数量是&lt;code>151&lt;/code>，表的默认存储引擎是&lt;code>InnoDB&lt;/code>，我们可以在程序启动的时候去修改这些默认值，对于这种在程序启动时指定的设置项也称之为启动选项（startup options），这些选项控制着程序启动后的行为。在&lt;code>MySQL&lt;/code>安装目录下的&lt;code>bin&lt;/code>目录中的各种可执行文件，不论是服务器相关的程序（比如&lt;code>mysqld&lt;/code>、&lt;code>mysqld_safe&lt;/code>）还是客户端相关的程序（比如&lt;code>mysql&lt;/code>、&lt;code>mysqladmin&lt;/code>），在启动的时候基本都可以指定启动参数。这些启动参数可以放在命令行中指定，也可以把它们放在配置文件中指定。下面我们以&lt;code>mysqld&lt;/code>为例，来详细介绍指定启动选项的格式。需要注意的一点是，我们现在要介绍的是设置启动选项的方式，下面出现的启动选项不论大家认不认识，先不用去纠结每个选项具体的作用是什么，之后我们会对一些重要的启动选项详细介绍。&lt;/p>
&lt;h1 id="在命令行上使用选项">在命令行上使用选项&lt;a class="anchor" href="#%e5%9c%a8%e5%91%bd%e4%bb%a4%e8%a1%8c%e4%b8%8a%e4%bd%bf%e7%94%a8%e9%80%89%e9%a1%b9">#&lt;/a>&lt;/h1>
&lt;p>如果我们在启动客户端程序时在&lt;code>-h&lt;/code>参数后边紧跟服务器的IP地址，这就意味着客户端和服务器之间需要通过&lt;code>TCP/IP&lt;/code>网络进行通信。因为我的客户端程序和服务器程序都装在一台计算机上，所以在使用客户端程序连接服务器程序时指定的主机名是&lt;code>127.0.0.1&lt;/code>的情况下，客户端进程和服务器进程之间会使用&lt;code>TCP/IP&lt;/code>网络进行通信。如果我们在启动服务器程序的时候就禁止各客户端使用&lt;code>TCP/IP&lt;/code>网络进行通信，可以在启动服务器程序的命令行里添加&lt;code>skip-networking&lt;/code>启动选项，就像这样： &lt;code>mysqld --skip-networking&lt;/code> 可以看到，我们在命令行中指定启动选项时需要在选项名前加上&lt;code>--&lt;/code>前缀。另外，如果选项名是由多个单词构成的，它们之间可以由短划线&lt;code>-&lt;/code>连接起来，也可以使用下划线&lt;code>_&lt;/code>连接起来，也就是说&lt;code>skip-networking&lt;/code>和&lt;code>skip_networking&lt;/code>表示的含义是相同的。所以上面的写法与下面的写法是等价的： &lt;code>mysqld --skip_networking&lt;/code> 在按照上述命令启动服务器程序后，如果我们再使用&lt;code>mysql&lt;/code>来启动客户端程序时，再把服务器主机名指定为&lt;code>127.0.0.1&lt;/code>（IP地址的形式）的话会显示连接失败： ``` mysql -h127.0.0.1 -uroot -p Enter password:&lt;/p>
&lt;p>ERROR 2003 (HY000): Can&amp;rsquo;t connect to MySQL server on &amp;lsquo;127.0.0.1&amp;rsquo; (61) ``` 这就意味着我们指定的启动选项&lt;code>skip-networking&lt;/code>生效了！&lt;/p>
&lt;p>再举一个例子，我们前面说过如果在创建表的语句中没有显式指定表的存储引擎的话，那就会默认使用&lt;code>InnoDB&lt;/code>作为表的存储引擎。如果我们想改变表的默认存储引擎的话，可以这样写启动服务器的命令行： &lt;code>mysqld --default-storage-engine=MyISAM&lt;/code> 我们现在就已经把表的默认存储引擎改为&lt;code>MyISAM&lt;/code>了，在客户端程序连接到服务器程序后试着创建一个表： &lt;code>mysql&amp;gt; CREATE TABLE sys_var_demo( -&amp;gt; i INT -&amp;gt; ); Query OK, 0 rows affected (0.02 sec)&lt;/code> 这个定义语句中我们并没有明确指定表的存储引擎，创建成功后再看一下这个表的结构： &lt;code>mysql&amp;gt; SHOW CREATE TABLE sys_var_demo\G *************************** 1. row *************************** Table: sys_var_demo Create Table: CREATE TABLE &lt;/code>sys_var_demo&lt;code>(&lt;/code>i&lt;code> int(11) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 1 row in set (0.01 sec)&lt;/code> 可以看到该表的存储引擎已经是&lt;code>MyISAM&lt;/code>了，说明启动选项&lt;code>default-storage-engine&lt;/code>生效了。&lt;/p></description></item><item><title>第1章_装作自己是个小白-重新认识MySQL</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC1%E7%AB%A0_%E8%A3%85%E4%BD%9C%E8%87%AA%E5%B7%B1%E6%98%AF%E4%B8%AA%E5%B0%8F%E7%99%BD-%E9%87%8D%E6%96%B0%E8%AE%A4%E8%AF%86MySQL/</link><pubDate>Sat, 18 Jan 2025 22:29:38 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC1%E7%AB%A0_%E8%A3%85%E4%BD%9C%E8%87%AA%E5%B7%B1%E6%98%AF%E4%B8%AA%E5%B0%8F%E7%99%BD-%E9%87%8D%E6%96%B0%E8%AE%A4%E8%AF%86MySQL/</guid><description>&lt;p>&lt;em>第1章 装作自己是个小白-重新认识MySQL&lt;/em>&lt;/p>
&lt;h1 id="mysql的客户端服务器架构">MySQL的客户端／服务器架构&lt;a class="anchor" href="#mysql%e7%9a%84%e5%ae%a2%e6%88%b7%e7%ab%af%e6%9c%8d%e5%8a%a1%e5%99%a8%e6%9e%b6%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;p>以我们平时使用的微信为例，它其实是由两部分组成的，一部分是&lt;mark>客户端&lt;/mark>程序，一部分是&lt;mark>服务器&lt;/mark>程序。客户端可能有很多种形式，比如手机APP，电脑软件或者是网页版微信，每个客户端都有一个唯一的用户名，就是你的微信号，另一方面，腾讯公司在他们的机房里运行着一个服务器软件，我们平时操作微信其实都是用客户端来和这个服务器来打交道。比如狗哥用微信给猫爷发了一条消息的过程其实是这样的：&lt;/p>
&lt;ol>
&lt;li>消息被客户端包装了一下，添加了发送者和接收者信息，然后从狗哥的微信客户端传送给微信服务器；&lt;/li>
&lt;li>微信服务器从消息里获取到它的发送者和接收者，根据消息的接收者信息把这条消息送达到猫爷的微信客户端，猫爷的微信客户端里就显示出狗哥给他发了一条消息。&lt;/li>
&lt;/ol>
&lt;p>&lt;code>MySQL&lt;/code>的使用过程跟这个是一样的，它的服务器程序&lt;strong>直接和我们存储的数据打交道&lt;/strong>，然后可以有好多客户端程序连接到这个服务器程序，发送增删改查的请求，然后服务器就响应这些请求，从而操作它维护的数据。和微信一样，&lt;code>MySQL&lt;/code>的每个客户端都需要提供用户名密码才能登录，登录之后才能给服务器发请求来操作某些数据。我们日常使用&lt;code>MySQL&lt;/code>的情景一般是这样的：&lt;/p>
&lt;ol>
&lt;li>启动&lt;code>MySQL&lt;/code>服务器程序。&lt;/li>
&lt;li>启动&lt;code>MySQL&lt;/code>客户端程序并连接到服务器程序。&lt;/li>
&lt;li>在客户端程序中输入一些命令语句作为请求发送到服务器程序，服务器程序收到这些请求后，会根据请求的内容来操作具体的数据并向客户端返回操作结果。&lt;/li>
&lt;/ol>
&lt;p>我们知道计算机很牛逼，在一台计算机上可以同时运行多个程序，比如微信、QQ、音乐播放器、文本编辑器等，每一个运行着的程序也被称为一个&lt;code>进程&lt;/code>。我们的&lt;code>MySQL&lt;/code>服务器程序和客户端程序本质上都算是计算机上的一个&lt;code>进程&lt;/code>，这个代表着&lt;code>MySQL&lt;/code>服务器程序的进程也被称为&lt;code>MySQL数据库实例&lt;/code>，简称&lt;code>数据库实例&lt;/code>。&lt;/p>
&lt;p>每个进程都有一个唯一的编号，称为&lt;code>进程ID&lt;/code>，英文名叫&lt;code>PID&lt;/code>，这个编号是在我们启动程序的时候由操作系统随机分配的，操作系统会保证在某一时刻同一台机器上的进程号不重复。比如你打开了计算机中的QQ程序，那么操作系统会为它分配一个唯一的进程号，如果你把这个程序关掉了，那操作系统就会把这个进程号回收，之后可能会重新分配给别的进程。当我们下一次再启动 QQ程序的时候分配的就可能是另一个编号。每个进程都有一个名称，这个名称是编写程序的人自己定义的，比如我们启动的&lt;code>MySQL&lt;/code>服务器进程的默认名称为&lt;code>mysqld&lt;/code>， 而我们常用的&lt;code>MySQL&lt;/code>客户端进程的默认名称为&lt;code>mysql&lt;/code>。&lt;/p>
&lt;h1 id="mysql的安装">MySQL的安装&lt;a class="anchor" href="#mysql%e7%9a%84%e5%ae%89%e8%a3%85">#&lt;/a>&lt;/h1>
&lt;p>不论我们通过下载源代码自行编译安装的方式，还是直接使用官方提供的安装包进行安装之后，&lt;code>MySQL&lt;/code>的服务器程序和客户端程序都会被安装到我们的机器上。不论使用上述两者的哪种安装方式，一定一定一定（重要的话说三遍）要记住你把&lt;code>MySQL&lt;/code>安装到哪了，换句话说，一定要记住&lt;code>MySQL&lt;/code>的安装目录。 &lt;code>小贴士：&lt;/code>MySQL&lt;code>的大部分安装包都包含了服务器程序和客户端程序，不过在Linux下使用RPM包时会有单独的服务器RPM包和客户端RPM包，需要分别安装。&lt;/code>&lt;/p>
&lt;p>另外，&lt;code>MySQL&lt;/code>可以运行在各种各样的操作系统上，我们后边会讨论在类&lt;code>UNIX&lt;/code>操作系统和&lt;code>Windows&lt;/code>操作系统上使用的一些差别。为了方便大家理解，我在&lt;code>macOS&lt;/code> 操作系统（苹果电脑使用的操作系统）和&lt;code>Windows&lt;/code>操作系统上都安装了&lt;code>MySQL&lt;/code>，它们的安装目录分别是：&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>macOS&lt;/code>操作系统上的安装目录： &lt;code>/usr/local/mysql/&lt;/code>&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>&lt;code>Windows&lt;/code>操作系统上的安装目录： &lt;code>C:\Program Files\MySQL\MySQL Server 5.7&lt;/code>&lt;/p>
&lt;p>下面我会以这两个安装目录为例来进一步扯出更多的概念，不过一定要注意，&lt;strong>这两个安装目录是我的运行不同操作系统的机器上的安装目录，一定要记着把下面示例中用到安装目录的地方替换为你自己机器上的安装目录&lt;/strong>。 &lt;code>小贴士：类UNIX操作系统非常多，比如FreeBSD、Linux、macOS、Solaris等都属于UNIX操作系统的范畴，我们这里使用macOS操作系统代表类UNIX操作系统来运行MySQL。&lt;/code>&lt;/p>
&lt;h2 id="bin目录下的可执行文件">bin目录下的可执行文件&lt;a class="anchor" href="#bin%e7%9b%ae%e5%bd%95%e4%b8%8b%e7%9a%84%e5%8f%af%e6%89%a7%e8%a1%8c%e6%96%87%e4%bb%b6">#&lt;/a>&lt;/h2>
&lt;p>在&lt;code>MySQL&lt;/code>的安装目录下有一个特别特别重要的&lt;code>bin&lt;/code>目录，这个目录下存放着许多可执行文件，以&lt;code>macOS&lt;/code>系统为例，这个&lt;code>bin&lt;/code>目录的绝对路径就是（在我的机器上）： &lt;code>/usr/local/mysql/bin&lt;/code> 我们列出一些在&lt;code>macOS&lt;/code>中这个&lt;code>bin&lt;/code>目录下的一部分可执行文件来看一下（文件太多，全列出来会刷屏的）： &lt;code>. ├── mysql ├── mysql.server -&amp;gt; ../support-files/mysql.server ├── mysqladmin ├── mysqlbinlog ├── mysqlcheck ├── mysqld ├── mysqld_multi ├── mysqld_safe ├── mysqldump ├── mysqlimport ├── mysqlpump ... (省略其他文件) 0 directories, 40 files&lt;/code> &lt;code>Windows&lt;/code>中的可执行文件与&lt;code>macOS&lt;/code>中的类似，不过都是以&lt;code>.exe&lt;/code>为扩展名的。这些可执行文件都是与服务器程序和客户端程序相关的，后边我们会详细介绍一些比较重要的可执行文件，现在先看看执行这些文件的方式。&lt;/p>
&lt;p>对于有可视化界面的操作系统来说，我们拿着鼠标点点点就可以执行某个可执行文件，不过现在我们更关注在命令行环境下如何执行这些可执行文件，命令行通俗的说就是那些黑框框，这里的指的是类&lt;code>UNIX&lt;/code>系统中的&lt;code>Shell&lt;/code>或者&lt;code>Windows&lt;/code>系统中的&lt;code>cmd.exe&lt;/code>，如果你现在还不知道怎么启动这些命令行工具，网上搜搜吧～ 下面我们以&lt;code>macOS&lt;/code>系统为例来看看如何启动这些可执行文件（&lt;code>Windows&lt;/code>中的操作是类似的，依葫芦画瓢就好了）&lt;/p>
&lt;ul>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>使用可执行文件的相对／绝对路径&lt;br>
假设我们现在所处的工作目录是&lt;code>MySQL&lt;/code>的安装目录，也就是&lt;code>/usr/local/mysql&lt;/code>，我们想启动&lt;code>bin&lt;/code>目录下的&lt;code>mysqld&lt;/code>这个可执行文件，可以使用相对路径来启动： &lt;code>./bin/mysqld&lt;/code> 或者直接输入&lt;code>mysqld&lt;/code>的绝对路径也可以： &lt;code>/usr/local/mysql/bin/mysqld&lt;/code>&lt;/p></description></item><item><title>第0章_万里长征第一步(非常重要)-如何愉快的阅读本小册</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC0%E7%AB%A0_%E4%B8%87%E9%87%8C%E9%95%BF%E5%BE%81%E7%AC%AC%E4%B8%80%E6%AD%A5%E9%9D%9E%E5%B8%B8%E9%87%8D%E8%A6%81-%E5%A6%82%E4%BD%95%E6%84%89%E5%BF%AB%E7%9A%84%E9%98%85%E8%AF%BB%E6%9C%AC%E5%B0%8F%E5%86%8C/</link><pubDate>Sat, 18 Jan 2025 22:29:37 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC0%E7%AB%A0_%E4%B8%87%E9%87%8C%E9%95%BF%E5%BE%81%E7%AC%AC%E4%B8%80%E6%AD%A5%E9%9D%9E%E5%B8%B8%E9%87%8D%E8%A6%81-%E5%A6%82%E4%BD%95%E6%84%89%E5%BF%AB%E7%9A%84%E9%98%85%E8%AF%BB%E6%9C%AC%E5%B0%8F%E5%86%8C/</guid><description>&lt;p>第0章 万里长征第一步（非常重要）-如何愉快的阅读本小册&lt;/p>
&lt;h1 id="购买前警告">购买前警告⚠️&lt;a class="anchor" href="#%e8%b4%ad%e4%b9%b0%e5%89%8d%e8%ad%a6%e5%91%8a">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>此小册并非数据库入门书籍，需要各位知道增删改查是什么意思，并且能用 SQL 语言写出来，当然并不要求各位知道的太多，你甚至可以不知道连接的语法都可以。不过如果你连&lt;code>SELECT&lt;/code>、&lt;code>INSERT&lt;/code>这些单词都没听说过那本小册并不适合你。&lt;/li>
&lt;li>此小册非正经科学专著，亦非十二五国家级规划教材，也没有大段代码和详细论证，有的全是图，喜欢正经论述的同学请避免购买本小册。&lt;/li>
&lt;li>此小册作者乃一无业游民，非专业大佬，没有任何职称，只是单单喜欢把复杂问题讲清楚的那种快感，所以喜欢作者有 Google、Facebook 高级开发工程师，二百年工作经验等 Title 的同学请谨慎购买。&lt;/li>
&lt;li>此小册是用于介绍 MySQL 的工作原理以及对我们程序猿的影响，并不是介绍概念设计、逻辑设计、物理设计、范式化之类的数据库设计方面的知识，希望了解上述这些知识的同学来错地方了。&lt;/li>
&lt;li>文章标题中的“从根儿上理解MySQL”**其实是专门雇了 UC 震惊部小编起的，纯属为了吸引大家眼球。严格意义上说，本书只是介绍&lt;code>MySQL&lt;/code>内核的一些核心概念的小白进阶书籍。大家读完本小册也不会一下子晋升业界大佬，当上 CTO，迎娶白富美，走上人生巅峰。希望本小册能够帮助大家解决一些工作、面试过程中的问题，逐渐成为一个更好的工程师，有兴趣的小伙伴可以再深入研究一下 MySQL，说不定你就是下一个数据库泰斗啦。&lt;/li>
&lt;/ul>
&lt;h1 id="购买并阅读本小册的建议">购买并阅读本小册的建议&lt;a class="anchor" href="#%e8%b4%ad%e4%b9%b0%e5%b9%b6%e9%98%85%e8%af%bb%e6%9c%ac%e5%b0%8f%e5%86%8c%e7%9a%84%e5%bb%ba%e8%ae%ae">#&lt;/a>&lt;/h1>
&lt;p>&lt;img src="img/00-01.png" alt="" />&lt;/p>
&lt;ul>
&lt;li>本小册是一本待出版的纸质书籍，并非一些杂碎文章的集合，是非常有结构和套路的，所以大家阅读时千万不能当作厕所蹲坑、吃饭看手机时的所谓&lt;code>碎片化读物&lt;/code>。碎片化阅读只适合听听矮大紧、罗胖子他们扯扯犊子，开阔一下视野用的。对于专业的技术知识来说，大家必须付出一个完整的时间段进行体系化学习，这样尊重知识，工资才能尊重你。 &lt;code>顺便说一句，我已经好久都不听罗胖子扯犊子了，刚开始办罗辑思维的时候觉得他扯的还可以，越往后越觉得都钻钱眼儿里了，天天在鼓吹焦虑，让大家去买他们的鸡汤课。不过听听矮大紧就挺好啊，不累～&lt;/code>&lt;/li>
&lt;li>本小册是由 Markdown 写成，在电脑端阅读体验十分舒服，当然你非要用小手机看我也不拦着你，但是效果打了折扣是你的损失。&lt;/li>
&lt;li>为了保证最好的阅读体验，不用一个没学过的概念去介绍另一个新概念，本小册的章节有严重的依赖性，比如你在没读&lt;code>InnoDB&lt;/code>数据页结构前千万不要就去读&lt;code>B+&lt;/code>树索引，所以大家最好从前看到尾，&lt;strong>不要跳着看！不要跳着看！不要跳着看！&lt;/strong>，当然，不听劝告我也不能说什么，祝你好运。&lt;/li>
&lt;li>大家可能买过别的小册，有的小册一篇文章可能用5分钟、10分钟读完，不过我的小册子每一篇文章都比较长，因为我把高耦合的部分都集中在一篇文章中了。文章中埋着各种伏笔，所以大家看的时候可能不会觉察出来很突兀的转变，所以在阅读一篇文章的时候千万&lt;strong>不要跳着看！不要跳着看！不要跳着看！&lt;/strong>&lt;/li>
&lt;li>大家在看本小册之前应该断断续续看过一些与本小册内容相关的知识，只是不成体系，细节学习的不够。对于这部分读者来说，希望大家像倚天屠龙记里的张无忌一样，在学张三丰的太极剑法时先忘记之前的武功，忘的越干净，学的越得真传。这样才能跟着我的套路走下去。&lt;/li>
&lt;li>&lt;/li>
&lt;/ul>
&lt;p>如果你真的是个小白的话，那这里头的数字都是假的： &lt;img src="img/00-02.png" alt="" />&lt;/p>
&lt;p>一篇文章能用2个小时左右的时间掌握就很不错了。说句扫大家兴的话，虽然我已经很努力的想让大家的学习效率提升n倍，但是不幸的是想掌握一门核心技术仍然需要大家多看几遍（不然工资那么好涨啊～）。&lt;/p>
&lt;h1 id="关于工具">关于工具&lt;a class="anchor" href="#%e5%85%b3%e4%ba%8e%e5%b7%a5%e5%85%b7">#&lt;/a>&lt;/h1>
&lt;p>本小册中会涉及很多 InnoDB 的存储结构的知识，比如记录结构、页结构、索引结构、表空间结构等等，这些知识是所有后续知识的基础，所以是重中之重，需要大家认真对待。Jeremy Cole 已经使用 Ruby 开发了一个简易的解析这些基础结构的工具，github地址是：
&lt;a target="_blank" href="https://github.com/jeremycole/innodb_ruby">innodb_ruby的github地址&lt;/a>，大家可以按照说明安装上这个工具，可以更好的理解 InnoDB 中的一些存储结构（此工具虽然是针对&lt;code>MySQL 5.6&lt;/code>的，但是幸好&lt;code>MySQL&lt;/code>的基础存储结构基本没多大变化，所以大部分场景下这个&lt;code>innodb_ruby&lt;/code>工具还是可以使用的）。&lt;/p>
&lt;h1 id="关于盗版">关于盗版&lt;a class="anchor" href="#%e5%85%b3%e4%ba%8e%e7%9b%97%e7%89%88">#&lt;/a>&lt;/h1>
&lt;p>在写这本小册之前，我天真的以为只需要找几本参考书，看看 MySQL 的官方文档，遇到不会的地方百度谷歌一下就可以在 3 个月内解决这本书，后来的现实证明我真的想的太美了。不仅花了大量的时间阅读各种书籍和源码，而且有的时候知识耦合太厉害，为了更加模块化的把知识表述清楚，我又花了大量的时间来思考如何写作才能符合用户认知习惯，还花了非常多的时间来画各种图表，总之就是心累啊～ 我希望的是：&lt;strong>各位同学可以用很低的成本来更快速学会一些看起来生涩难懂的知识&lt;/strong>，但是毕竟我不是马云，不能一心一意做公益，希望各位通过正规渠道获得小册，尊重一下版权。 还有各位写博客的同学，引用的少了叫借鉴，引用的多了就，就有点那个了。希望各位不要大段大段的复制粘贴，用自己的话写出来的知识才是自己的东西。 我知道不论我们怎样强调版权意识，总是有一部分小伙伴喜欢不劳而获，总是喜欢想尽各种渠道来弄一份盗版的看，希望这部分同学看完别忘了关注公众号【我们都是小青蛙】，给我填个粉儿也算是赞助一下我（下面是二维码，觉得有帮助的话希望可以打赏一下，毕竟本人很穷。另外，公众号中有若干篇小册的补充文章，包括三篇极其重要的语句加锁分析）：&lt;/p>
&lt;p>&lt;code>小贴士：我一直有个想法，就是如何降低教育成本。现在教育的盈利收费模式都太单一，就是直接跟学生收上课费，导致课程成为一种2C的商品，价格高低其实和内容质量并不是很相关，所以课程提供商会投入更大的精力做他们的渠道营销。所以现在的在线教育市场就是渠道为王，招生为王。我们其实可以换一种思路，在线教育的优势其实是传播费用更低，一个人上课和一千万人上课的费用区别其实就是服务器使用的多少罢了，所以我们可能并不需要那么多语文老师、数学老师，我们用专业的导演、专业的声优、专业的动画制作、专业的后期、专业的剪辑、专业的编剧组成的团队为某个科目制作一个专业的课程就好了嘛（顺便说一句，我就可以转行做课程编剧了）！把课程当作电影、电视剧来卖，只要在课程中植入广告，或者在播放平台上加广告就好了嘛，我们也可以在课程里培养偶像，来做一波粉丝经济。这样课程生产方也赚钱，学生们也省钱，最主要的是可以更大层度上促进教育公平，多好。&lt;/code>&lt;/p>
&lt;h1 id="关于错误">关于错误&lt;a class="anchor" href="#%e5%85%b3%e4%ba%8e%e9%94%99%e8%af%af">#&lt;/a>&lt;/h1>
&lt;h2 id="准确性问题">准确性问题&lt;a class="anchor" href="#%e5%87%86%e7%a1%ae%e6%80%a7%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;p>我不是神，并不是书中的所有内容我都一一对照源码来验证准确性（阅读的大部分源码是关于查询优化和事务处理的），如果各位发现了文中有准确性问题请直接联系我，我会加入 Bug 列表中修正的。&lt;/p>
&lt;h2 id="阅读体验问题">阅读体验问题&lt;a class="anchor" href="#%e9%98%85%e8%af%bb%e4%bd%93%e9%aa%8c%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;p>大家知道大部分人在长大之后就忘记了自己小时候的样子，我写本书的初衷就是有很多资料我看不懂，看的我脑壳疼，之后才决定从小白的角度出发来写一本小白都能看懂的技术书籍。但是由于后来自己学的东西越来越多，可能有些地方我已经忘掉了小白的想法是怎么样的，所以大家在阅读过程中有任何阅读不畅快的地方都可以给我提，我也会加入bug列表中逐一优化。&lt;/p>
&lt;h1 id="关于转发">关于转发&lt;a class="anchor" href="#%e5%85%b3%e4%ba%8e%e8%bd%ac%e5%8f%91">#&lt;/a>&lt;/h1>
&lt;p>如果你从本小册中获取到了自己想要的知识，并且这个过程是比较轻松愉快的，希望各位能帮助转发本小册，解放一下学不懂这些知识的童鞋们，多节省一下他们的学习时间以及让学习过程不再那么痛苦。大家的技术都长进了，咱国家的技术也就慢慢强起来了。&lt;/p>
&lt;h1 id="关于疑惑">关于疑惑&lt;a class="anchor" href="#%e5%85%b3%e4%ba%8e%e7%96%91%e6%83%91">#&lt;/a>&lt;/h1>
&lt;p>虽然我觉得文章写的已经很清晰了，但毕竟只是“我觉得”，不是大家觉得。传道授业解惑，&lt;strong>解惑&lt;/strong>很重要。在学习一门知识时，我们最容易让一些问题绊住脚步，大家在阅读小册时如果发现了任何你觉得让你很困惑的问题，都可以直接加微信 xiaohaizi4919 问我，或者到群里提问题（最好到群里提，这样大家都能看到，也省的重复提问），我在力所能及的范围内尽力帮大家解答。 &lt;img src="img/00-03.jpg" alt="" />&lt;/p></description></item><item><title>第11章_两个表的亲密接触-连接的原理</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC11%E7%AB%A0_%E4%B8%A4%E4%B8%AA%E8%A1%A8%E7%9A%84%E4%BA%B2%E5%AF%86%E6%8E%A5%E8%A7%A6-%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%8E%9F%E7%90%86/</link><pubDate>Sat, 11 Jan 2025 16:40:07 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/MySQL%E6%98%AF%E6%80%8E%E6%A0%B7%E8%BF%90%E8%A1%8C%E7%9A%84/%E7%AC%AC11%E7%AB%A0_%E4%B8%A4%E4%B8%AA%E8%A1%A8%E7%9A%84%E4%BA%B2%E5%AF%86%E6%8E%A5%E8%A7%A6-%E8%BF%9E%E6%8E%A5%E7%9A%84%E5%8E%9F%E7%90%86/</guid><description>&lt;p>&lt;em>第11章 两个表的亲密接触-连接的原理&lt;/em>&lt;/p>
&lt;p>搞数据库一个避不开的概念就是&lt;code>Join&lt;/code>，翻译成中文就是&lt;code>连接&lt;/code>。相信很多小伙伴在初学连接的时候有些一脸懵逼，理解了连接的语义之后又可能不明白各个表中的记录到底是怎么连起来的，以至于在使用的时候常常陷入下面两种误区： - 误区一：业务至上，管他三七二十一，再复杂的查询也用在一个连接语句中搞定。 - 误区二：敬而远之，上次 DBA 那给报过来的慢查询就是因为使用了连接导致的，以后再也不敢用了。&lt;/p>
&lt;p>所以本章就来扒一扒连接的原理。考虑到一部分小伙伴可能忘了连接是什么或者压根儿就不知道，为了节省他们百度或者看其他书的宝贵时间以及为了我的书凑字数，我们先来介绍一下 MySQL 中支持的一些连接语法。&lt;/p>
&lt;h1 id="连接简介">连接简介&lt;a class="anchor" href="#%e8%bf%9e%e6%8e%a5%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;h2 id="连接的本质">连接的本质&lt;a class="anchor" href="#%e8%bf%9e%e6%8e%a5%e7%9a%84%e6%9c%ac%e8%b4%a8">#&lt;/a>&lt;/h2>
&lt;p>为了故事的顺利发展，我们先建立两个简单的表并给它们填充一点数据：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-mysql>" data-lang="mysql>">
mysql&amp;gt; CREATE TABLE t2 (m2 int, n2 char\(1)\); Query OK, 0 rows affected (0.02 sec)

mysql&amp;gt; INSERT INTO t1 VALUES(1, &amp;#39;a&amp;#39;), (2, &amp;#39;b&amp;#39;), (3, &amp;#39;c&amp;#39;); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0

mysql&amp;gt; INSERT INTO t2 VALUES(2, &amp;#39;b&amp;#39;), (3, &amp;#39;c&amp;#39;), (4, &amp;#39;d&amp;#39;); Query OK, 3 rows affected (0.00 sec) Records: 3 Duplicates: 0 Warnings: 0&lt;/code>&lt;/pre>&lt;p>&lt;code>我们成功建立了&lt;/code>t1&lt;code>、&lt;/code>t2&lt;code>两个表，这两个表都有两个列，一个是&lt;/code>INT&lt;code>类型的，一个是&lt;/code>CHAR(1)`类型的，填充好数据的两个表长这样：&lt;/p></description></item><item><title>08数据的家--MySQL的数据目录</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/08/</link><pubDate>Sun, 05 Jan 2025 19:17:49 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/08/</guid><description>&lt;h1 id="heading">#&lt;a class="anchor" href="#heading">#&lt;/a>&lt;/h1>

&lt;h1 id="数据库和文件系统的工具">数据库和文件系统的工具&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e5%ba%93%e5%92%8c%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e7%9a%84%e5%b7%a5%e5%85%b7">#&lt;/a>&lt;/h1>
&lt;h1 id="数据目录的结构">数据目录的结构&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95%e7%9a%84%e7%bb%93%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;h2 id="表在文件系统中的表示">表在文件系统中的表示&lt;a class="anchor" href="#%e8%a1%a8%e5%9c%a8%e6%96%87%e4%bb%b6%e7%b3%bb%e7%bb%9f%e4%b8%ad%e7%9a%84%e8%a1%a8%e7%a4%ba">#&lt;/a>&lt;/h2>
&lt;p>kjskfjksdf&lt;br>
s ksfd&lt;/p>
&lt;h3 id="innodb是如何存储表数据的1">InnoDB是如何存储表数据的1&lt;a class="anchor" href="#innodb%e6%98%af%e5%a6%82%e4%bd%95%e5%ad%98%e5%82%a8%e8%a1%a8%e6%95%b0%e6%8d%ae%e7%9a%841">#&lt;/a>&lt;/h3>
&lt;h4 id="系统表空间1">系统表空间1&lt;a class="anchor" href="#%e7%b3%bb%e7%bb%9f%e8%a1%a8%e7%a9%ba%e9%97%b41">#&lt;/a>&lt;/h4>
&lt;p>撒旦发就&lt;/p>
&lt;h4 id="系统表空间2">系统表空间2&lt;a class="anchor" href="#%e7%b3%bb%e7%bb%9f%e8%a1%a8%e7%a9%ba%e9%97%b42">#&lt;/a>&lt;/h4>
&lt;p>撒旦发就&lt;/p>
&lt;h4 id="系统表空间3">系统表空间3&lt;a class="anchor" href="#%e7%b3%bb%e7%bb%9f%e8%a1%a8%e7%a9%ba%e9%97%b43">#&lt;/a>&lt;/h4>
&lt;p>撒旦发就&lt;/p>
&lt;h3 id="innodb是如何存储表数据的2">InnoDB是如何存储表数据的2&lt;a class="anchor" href="#innodb%e6%98%af%e5%a6%82%e4%bd%95%e5%ad%98%e5%82%a8%e8%a1%a8%e6%95%b0%e6%8d%ae%e7%9a%842">#&lt;/a>&lt;/h3></description></item><item><title>07B+数索引的使用</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/07/</link><pubDate>Mon, 24 Apr 2023 14:32:22 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/07/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="innodb存储引擎的b树索引结论">InnoDB存储引擎的B+树索引：结论&lt;a class="anchor" href="#innodb%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e7%9a%84b%e6%a0%91%e7%b4%a2%e5%bc%95%e7%bb%93%e8%ae%ba">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;strong>每个索引&lt;/strong>对应一颗B+树。B+树有好多层，&lt;strong>最下边一层&lt;/strong>是&lt;strong>叶子节点&lt;/strong>，其余是&lt;strong>内节点&lt;/strong>。所有&lt;strong>用户记录&lt;/strong>都存在B+树的&lt;strong>叶子节点&lt;/strong>，所有&lt;strong>目录项记录&lt;/strong>都存在内节点&lt;/li>
&lt;li>InnoDB 存储引擎会&lt;strong>自动为主键建立聚簇索引&lt;/strong>(如果&lt;strong>没有显式指定主键&lt;/strong>或者&lt;strong>没有声明不允许存储NULL的UNIQUE 键&lt;/strong>，它会自动添加主键) ， &lt;strong>聚簇索引&lt;/strong>的&lt;strong>叶子节点&lt;/strong>包含&lt;strong>完整的用户记录&lt;/strong>&lt;/li>
&lt;li>我们可以为感兴趣的列建立&lt;strong>二级索引&lt;/strong>，二级索引的叶子节点包含的用户记录由&lt;strong>索引列&lt;/strong>
&lt;strong>和主键&lt;/strong>组成。如果想通过二级索引查找完整的用户记录，需要执行&lt;strong>回表&lt;/strong>操作， 也就是在通过二级索引&lt;strong>找到主键值之后&lt;/strong>，再到&lt;strong>聚簇索引&lt;/strong>中查找完整的用户记录&lt;/li>
&lt;li>B+ 树中的&lt;strong>每层节点&lt;/strong>都按照&lt;strong>索引列的值从小到大的顺序排序&lt;/strong>组成了&lt;strong>双向链表&lt;/strong>，而且每个&lt;strong>页内的记录&lt;/strong>(无论是&lt;strong>用户记录&lt;/strong>还是&lt;strong>目录项记录&lt;/strong>)都按照索引列的值从小到大的顺序形成了一个单向链表。如果是联合索引， 则&lt;strong>页面&lt;/strong>和&lt;strong>记录&lt;/strong> &lt;strong>先&lt;/strong>按照索引列中前面的列的值排序：&lt;strong>如果该列的值相同&lt;/strong>，&lt;strong>再按照索引列中后面的列的值排序&lt;/strong>。比如， 我们对列c2 和c3建立了联合索引 &lt;strong>idx_c2_c3(c2， c3)&lt;/strong>，那么该索引中的页面和记录就&lt;strong>先按照c2 列&lt;/strong>的值进行排序；&lt;strong>如果c2 列的值相同&lt;/strong>， &lt;strong>再按照c3&lt;/strong> 列的值排序&lt;/li>
&lt;li>通过索引查找记录时，是&lt;strong>从B+ 树的根节点开始一层一层向下搜索的&lt;/strong>。由于每个页面(无论是&lt;strong>内节点页面&lt;/strong>还是&lt;strong>叶子节点页面&lt;/strong>〉中的记录都划分成了&lt;strong>若干个组&lt;/strong>， 每个组中&lt;strong>索引列值最大的记录&lt;/strong>在&lt;strong>页内的偏移量&lt;/strong>会被当作&lt;strong>槽&lt;/strong>依次存放在&lt;strong>页目录中&lt;/strong>(当然， 规定Supremum 记录比任何用户记录都大) ，因此可以在&lt;strong>页目录中通过二分法&lt;/strong>快速定位到&lt;strong>索引列等于某个值的记录&lt;/strong>&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>如果大家在阅读上述结论时哪怕有点疑惑， 那么下面的内容就不适合你，请回过头去反复阅读前面的章节&lt;/p>&lt;/blockquote>&lt;h1 id="b树索引示意图的简化">B+树索引示意图的简化&lt;a class="anchor" href="#b%e6%a0%91%e7%b4%a2%e5%bc%95%e7%a4%ba%e6%84%8f%e5%9b%be%e7%9a%84%e7%ae%80%e5%8c%96">#&lt;/a>&lt;/h1>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#创建新表&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; CREATE TABLE single_table&lt;span style="color:#f92672">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id INT NOT NULL AUTO_INCREMENT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key1 VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key2 INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key3 VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key_part1 VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key_part2 VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> key_part3 VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> common_field VARCHAR&lt;span style="color:#f92672">(&lt;/span>100&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PRIMARY KEY &lt;span style="color:#f92672">(&lt;/span>id&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> KEY idx_key1&lt;span style="color:#f92672">(&lt;/span>key1&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UNIQUE KEY uk_key2&lt;span style="color:#f92672">(&lt;/span>key2&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> KEY idx_key3&lt;span style="color:#f92672">(&lt;/span>key3&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> KEY idx_key_part&lt;span style="color:#f92672">(&lt;/span>key_part1,key_part2,key_part3&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">)&lt;/span> Engine&lt;span style="color:#f92672">=&lt;/span>InnoDB CHARSET &lt;span style="color:#f92672">=&lt;/span> utf8;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如上，建立了&lt;strong>1个聚簇索引&lt;/strong>，&lt;strong>4个二级索引&lt;/strong>&lt;/p></description></item><item><title>06B+树索引</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/06/</link><pubDate>Sat, 22 Apr 2023 15:04:46 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/06/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="概述">概述&lt;a class="anchor" href="#%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;p>数据页由&lt;strong>7个&lt;/strong>组成部分，各个数据页可以组成一个&lt;strong>双向链表&lt;/strong>，每个数据页中的记录会按照&lt;strong>主键值从小到大的顺序&lt;/strong>组成一个&lt;strong>单向链表&lt;/strong>。每个数据页都会为&lt;strong>它里面的记录&lt;/strong>生成一个&lt;strong>页目录&lt;/strong>，在通过主键查找某条记录的时候可以在&lt;strong>页目录&lt;/strong>中使用二分法快速定位到&lt;strong>对应的槽&lt;/strong>，然后再&lt;strong>遍历该槽&lt;/strong>对应&lt;strong>分组中的记录&lt;/strong>即可快速找到&lt;strong>指定&lt;/strong>的记录。&lt;strong>页和记录&lt;/strong>的关系&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>页a，页b 可以&lt;strong>不在物理结构上相连&lt;/strong>，只要通过&lt;strong>双向链表相关联&lt;/strong>即可&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212142156258.png" alt="ly-20241212142156258" />&lt;/p>
&lt;h1 id="没有索引时进行查找">没有索引时进行查找&lt;a class="anchor" href="#%e6%b2%a1%e6%9c%89%e7%b4%a2%e5%bc%95%e6%97%b6%e8%bf%9b%e8%a1%8c%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h1>
&lt;p>假设我们要搜索&lt;strong>某个列&lt;/strong>等于&lt;strong>某个常数&lt;/strong>的情况：&lt;br>
&lt;code>SELECT [查询列表] FROM 表名 WHERE 列名 = xxx&lt;/code>&lt;/p>
&lt;h2 id="在一个页中查找">在一个页中查找&lt;a class="anchor" href="#%e5%9c%a8%e4%b8%80%e4%b8%aa%e9%a1%b5%e4%b8%ad%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h2>
&lt;p>假设记录极少，所有记录可以&lt;strong>存放到一个页中&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>以&lt;strong>主键&lt;/strong>位搜索条件：页目录中使用&lt;strong>二分法&lt;/strong>快速定位到对应的&lt;strong>槽&lt;/strong>，然后在&lt;strong>遍历槽对应分组中的记录&lt;/strong>，即可快速找到指定记录&lt;/li>
&lt;li>其他列作为搜索条件：对于&lt;strong>非主键&lt;/strong>，数据页没有为&lt;strong>非主键列&lt;/strong>建立所谓的&lt;strong>页目录&lt;/strong>，所以&lt;strong>无法通过二分法&lt;/strong>快速定位相应的槽。只能&lt;strong>从Infimum依次遍历&lt;/strong>单向链表中的每条记录，然后对比，效率极低&lt;/li>
&lt;/ul>
&lt;h2 id="在很多页中查找">在很多页中查找&lt;a class="anchor" href="#%e5%9c%a8%e5%be%88%e5%a4%9a%e9%a1%b5%e4%b8%ad%e6%9f%a5%e6%89%be">#&lt;/a>&lt;/h2>
&lt;p>两个步骤：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>定位到记录所在的页&lt;/strong>&lt;/li>
&lt;li>从&lt;strong>所在页内&lt;/strong>查找相应的记录&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>没有索引&lt;/strong>情况下，不能快速定位到所在页，只能从&lt;strong>第一页&lt;/strong>沿着&lt;strong>双向链表&lt;/strong>一直往下找，而如果是主键，每一页则可以在&lt;strong>页目录&lt;/strong>二分查找。&lt;br>
不过由于要&lt;strong>遍历所有页&lt;/strong>，所以超级&lt;strong>耗时&lt;/strong>&lt;/p>
&lt;h1 id="索引">索引&lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95">#&lt;/a>&lt;/h1>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#例子&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; CREATE TABLE index_demo&lt;span style="color:#f92672">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c1 INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c2 INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c3 CHAR&lt;span style="color:#f92672">(&lt;/span>1&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PRIMARY KEY&lt;span style="color:#f92672">(&lt;/span>c1&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">)&lt;/span> ROW_FORMAT&lt;span style="color:#f92672">=&lt;/span>COMPACT;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote class='book-hint '>
&lt;p>完整的行格式&lt;/p>&lt;/blockquote>&lt;p>&lt;img src="img/ly-20241212142156429.png" alt="ly-20241212142156429" />&lt;/p>
&lt;p>简化的行格式&lt;br>
&lt;img src="img/ly-20241212142156467.png" alt="ly-20241212142156467" />&lt;/p>
&lt;ul>
&lt;li>record_type：&lt;strong>记录头信息&lt;/strong>的一项属性，表示&lt;strong>记录的类型&lt;/strong>。0：普通记录，2：Infimum记录，3：Supremum记录，1还没用过等会再说&lt;/li>
&lt;li>next_record：&lt;strong>记录头信息&lt;/strong>的一项属性，表示从当前记录的&lt;strong>真实数据&lt;/strong>到&lt;strong>下一条记录真实数据&lt;/strong>的距离&lt;/li>
&lt;li>各个列的值：这里只展示在index_demo表中的&lt;strong>3个列&lt;/strong>，分别是c1、c2、c3&lt;/li>
&lt;li>其他信息：包括&lt;strong>隐藏列&lt;/strong>及&lt;strong>记录的额外信息&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>改为竖着查看：&lt;br>
&lt;img src="img/ly-20241212142156521.png" alt="ly-20241212142156521" />&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>上面图6-4的箭头其实有一点点出入，应该是指向z真实数据第1列那个位置，如下
&lt;img src="img/ly-20241212142156561.png" alt="ly-20241212142156561" />&lt;/p>&lt;/blockquote>&lt;h2 id="一个简单的索引方案">一个简单的索引方案&lt;a class="anchor" href="#%e4%b8%80%e4%b8%aa%e7%ae%80%e5%8d%95%e7%9a%84%e7%b4%a2%e5%bc%95%e6%96%b9%e6%a1%88">#&lt;/a>&lt;/h2>
&lt;p>思考：在&lt;strong>根据某个条件&lt;/strong>查找一些记录，为什么要&lt;strong>遍历所有&lt;/strong>的&lt;strong>数据页&lt;/strong>呢？因为各个&lt;strong>页&lt;/strong>中的记录没有规律，不知道&lt;strong>搜索条件会匹配哪些页&lt;/strong>&lt;br>
思路：为&lt;strong>快速定位&lt;/strong>记录所在的&lt;strong>数据页&lt;/strong>而建立一个别的目录&lt;/p>
&lt;h3 id="有序">有序&lt;a class="anchor" href="#%e6%9c%89%e5%ba%8f">#&lt;/a>&lt;/h3>
&lt;p>下一个数据页中&lt;strong>用户记录&lt;/strong>的&lt;strong>主键值&lt;/strong>必须大于上一页&lt;strong>用户记录&lt;/strong>的&lt;strong>主键值&lt;/strong>&lt;br>
假设一页只能存放3条记录&lt;/p></description></item><item><title>05InnoDB数据页结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/05/</link><pubDate>Fri, 21 Apr 2023 10:35:29 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/05/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="不同类型的页简介">不同类型的页简介&lt;a class="anchor" href="#%e4%b8%8d%e5%90%8c%e7%b1%bb%e5%9e%8b%e7%9a%84%e9%a1%b5%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;p>页是&lt;strong>InnoDB管理存储空间&lt;/strong>的基本单位，1个页的大小一般是16KB&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>InnoDB为了不同目的设计多种不同类型的页，包括&lt;strong>存放表空间头部信息&lt;/strong>
的页、&lt;strong>存放Change Buffer 信息&lt;/strong>的页、&lt;strong>存放INODE信息&lt;/strong>的页、&lt;strong>存放undo 日志信息&lt;/strong>的页&lt;/p>&lt;/blockquote>&lt;p>这里说的是&lt;strong>存放表中记录&lt;/strong>的那种类型的页，这种&lt;strong>存放记录&lt;/strong>的页称为&lt;strong>索引页&lt;/strong>（INDEX页）&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>暂时称之为&lt;strong>数据页&lt;/strong>&lt;/p>&lt;/blockquote>&lt;h1 id="数据页结构快览">数据页结构快览&lt;a class="anchor" href="#%e6%95%b0%e6%8d%ae%e9%a1%b5%e7%bb%93%e6%9e%84%e5%bf%ab%e8%a7%88">#&lt;/a>&lt;/h1>
&lt;p>1个页有16KB，这部分存储空间被划分为了多个部分（7部分），不同部分有不同的功能&lt;br>
&lt;img src="img/ly-20241212142155323.png" alt="ly-20241212142155323" />&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>名称&lt;/th>
 &lt;th>中文名&lt;/th>
 &lt;th>占用空间&lt;/th>
 &lt;th>大小&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>File Header&lt;/td>
 &lt;td>文件头部&lt;/td>
 &lt;td>38 字节&lt;/td>
 &lt;td>页的一些通用信息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Page Header&lt;/td>
 &lt;td>页面头部&lt;/td>
 &lt;td>56 字节&lt;/td>
 &lt;td>数据页专有的一些信息&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Infimum + Supremum&lt;/td>
 &lt;td>页面中的最小记录和最大记录&lt;/td>
 &lt;td>26 字节&lt;/td>
 &lt;td>两个虚拟的记录&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>User Records&lt;/td>
 &lt;td>用户记录&lt;/td>
 &lt;td>不确定&lt;/td>
 &lt;td>用户存储的记录内容&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Free Space&lt;/td>
 &lt;td>空闲空间&lt;/td>
 &lt;td>不确定&lt;/td>
 &lt;td>页中尚未使用的空间&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>Page Directory&lt;/td>
 &lt;td>页目录&lt;/td>
 &lt;td>不确定&lt;/td>
 &lt;td>某些记录的相对位置&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>File Trailer&lt;/td>
 &lt;td>文件尾部&lt;/td>
 &lt;td>8 字节&lt;/td>
 &lt;td>校验页是否完整&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h1 id="记录在页中的存储">记录在页中的存储&lt;a class="anchor" href="#%e8%ae%b0%e5%bd%95%e5%9c%a8%e9%a1%b5%e4%b8%ad%e7%9a%84%e5%ad%98%e5%82%a8">#&lt;/a>&lt;/h1>
&lt;p>每插入一条记录，从&lt;strong>Free Space&lt;/strong>申请一个记录大小的空间，并&lt;strong>将这个空间划分到UserRecords&lt;/strong>部分。当FreeSpace部分的空间全部被UserRecords部分替代掉后，意味着该页用完。如果再插入，就需要申请新的页&lt;/p>
&lt;p>&lt;img src="img/ly-20241212142155485.png" alt="ly-20241212142155485" />&lt;/p>
&lt;h2 id="记录头信息的秘密">记录头信息的秘密&lt;a class="anchor" href="#%e8%ae%b0%e5%bd%95%e5%a4%b4%e4%bf%a1%e6%81%af%e7%9a%84%e7%a7%98%e5%af%86">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>mysql&amp;gt; CREATE TABLE page_demo&lt;span style="color:#f92672">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c1 INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c2 INT,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c3 VARCHAR&lt;span style="color:#f92672">(&lt;/span>10000&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PRIMARY KEY&lt;span style="color:#f92672">(&lt;/span>c1&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">)&lt;/span> CHARSET&lt;span style="color:#f92672">=&lt;/span>ascii ROW_FORMAT&lt;span style="color:#f92672">=&lt;/span>COMPACT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Query OK, &lt;span style="color:#ae81ff">0&lt;/span> rows affected &lt;span style="color:#f92672">(&lt;/span>0.03 sec&lt;span style="color:#f92672">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="img/ly-20241212142155528.png" alt="ly-20241212142155528" />&lt;/p></description></item><item><title>04InnoDB记录存储结构</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/04/</link><pubDate>Tue, 18 Apr 2023 20:00:03 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/04/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="问题">问题&lt;a class="anchor" href="#%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h1>
&lt;p>表数据存在哪，以什么格式存放，MySQL以什么方式来访问&lt;br>
存储引擎：对表中数据进行&lt;strong>存储&lt;/strong>和&lt;strong>写入&lt;/strong>&lt;br>
InnoDB是MySQL默认的存储引擎，这章主要讲&lt;strong>InnoDB存储引擎的记录存储结构&lt;/strong>&lt;/p>
&lt;h1 id="innodb页简介">InnoDB页简介&lt;a class="anchor" href="#innodb%e9%a1%b5%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;p>&lt;strong>注意，是简介&lt;/strong>&lt;br>
InnoDB：将表中的数据&lt;strong>存储到磁盘上&lt;/strong>&lt;br>
真正处理数据的过程：内存中。所以需要&lt;strong>把磁盘中数据加载到内存中&lt;/strong>，如果是&lt;strong>写入&lt;/strong>或&lt;strong>修改&lt;/strong>请求，还需要把&lt;strong>内存&lt;/strong>中的内容&lt;strong>刷新到磁盘&lt;/strong>上&lt;br>
获取记录：不是一条条从磁盘读，InnoDB将数据划分为若干个页，以&lt;strong>页&lt;/strong>作为&lt;strong>磁盘&lt;/strong>和&lt;strong>内存&lt;/strong>之间交互的基本单位。页大小-&amp;gt; 一般是16KB&lt;br>
一般情况：一次最少从磁盘读取16KB的内容&lt;strong>到内存中&lt;/strong>，一次最少把内存中的16KB内容&lt;strong>刷新到磁盘&lt;/strong>中&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>mysql&amp;gt; SHOW VARIABLES LIKE &lt;span style="color:#e6db74">&amp;#39;innodb_page_size&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+------------------+-------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| Variable_name | Value |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+------------------+-------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| innodb_page_size | &lt;span style="color:#ae81ff">16384&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+------------------+-------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">1&lt;/span> row in set &lt;span style="color:#f92672">(&lt;/span>0.00 sec&lt;span style="color:#f92672">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>只能在第一次初始化MySQL数据目录时指定，之后&lt;strong>再也不能更改&lt;/strong>（通过mysqld &amp;ndash;initialize初始化数据目录[旧版本]）&lt;/p>
&lt;h1 id="innodb行格式">InnoDB行格式&lt;a class="anchor" href="#innodb%e8%a1%8c%e6%a0%bc%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>以记录为单位向表中插入数据，而这些记录在&lt;strong>磁盘上的存放形式&lt;/strong>也被称为&lt;strong>行格式&lt;/strong>或者&lt;strong>记录格式&lt;/strong>&lt;br>
目前有4中&lt;strong>不同类型的行格式&lt;/strong>：COMPACT、REDUNDANT、DYNAMIC和COMPRESSED&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>compact [kəmˈpækt]契约&lt;br>
redundant[rɪˈdʌndənt] 冗余的&lt;br>
dynamic[daɪˈnæmɪk]动态的&lt;br>
compressed [kəmˈprest] 压缩的&lt;/p>&lt;/blockquote>&lt;h2 id="指定行格式的语法">指定行格式的语法&lt;a class="anchor" href="#%e6%8c%87%e5%ae%9a%e8%a1%8c%e6%a0%bc%e5%bc%8f%e7%9a%84%e8%af%ad%e6%b3%95">#&lt;/a>&lt;/h2>
&lt;p>CREATE TABLE 表名(列的信息) ROW_FORMAT=行格式名称&lt;br>
ALTER TABLE 表名 ROW_FORMATE=行格式名称&lt;br>
如下，在数据库xiaohaizi下创建一个表&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>CREATE TABLE record_format_demo&lt;span style="color:#f92672">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c1 VARCHAR&lt;span style="color:#f92672">(&lt;/span>10&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c2 VARCHAR&lt;span style="color:#f92672">(&lt;/span>10&lt;span style="color:#f92672">)&lt;/span> NOT NULL,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c3 CHAR&lt;span style="color:#f92672">(&lt;/span>10&lt;span style="color:#f92672">)&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> c4 VARCHAR&lt;span style="color:#f92672">(&lt;/span>10&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">)&lt;/span> CHARSET&lt;span style="color:#f92672">=&lt;/span>ascii ROW_FORMAT&lt;span style="color:#f92672">=&lt;/span>COMPACT; 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#回顾：ascii每个字符1字节即可表示，且只有空格标点数字字母不可见字符&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#插入两条数据&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>INSERT INTO record_format_demo&lt;span style="color:#f92672">(&lt;/span>c1,c2,c3,c4&lt;span style="color:#f92672">)&lt;/span> VALUES&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#e6db74">&amp;#39;aaaa&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;bbb&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;cc&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;d&amp;#39;&lt;/span>&lt;span style="color:#f92672">)&lt;/span>,&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#e6db74">&amp;#39;eeee&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;fff&amp;#39;&lt;/span>,NULL,NULL&lt;span style="color:#f92672">)&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>查询&lt;/p></description></item><item><title>03字符集和比较规则</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/03/</link><pubDate>Mon, 17 Apr 2023 22:15:01 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/03/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="字符集">字符集&lt;a class="anchor" href="#%e5%ad%97%e7%ac%a6%e9%9b%86">#&lt;/a>&lt;/h1>
&lt;p>把哪些字符映射成二进制数据：&lt;strong>字符范围&lt;/strong>&lt;br>
怎么映射：字符-&amp;gt;二进制数据，编码；二进制-&amp;gt;字符，解码&lt;br>
字符集：&lt;strong>某个字符范围的编码规则&lt;/strong>&lt;br>
同一种字符集可以有多种比较规则&lt;/p>
&lt;h2 id="重要的字符集">重要的字符集&lt;a class="anchor" href="#%e9%87%8d%e8%a6%81%e7%9a%84%e5%ad%97%e7%ac%a6%e9%9b%86">#&lt;/a>&lt;/h2>
&lt;p>ASCAII字符集：128个，包括空格标点数字大小写及不可见字符，使用一个字节编码&lt;br>
ISO 8859-1字符集：256个，ASCAII基础扩充128个西欧常用字符（包括德法），使用1个字节，别名Latin1&lt;br>
GB2312字符集：收录部分汉字，兼容ASCAII字符集，如果字符在ASCAII字符集中则采用1字节，否则两字节。即&lt;strong>变长编码方式&lt;/strong>&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>区分某个字节，&lt;strong>代表一个单独字符&lt;/strong>，还是&lt;strong>某个字符的一部分&lt;/strong>。&lt;br>
比如0xB0AE75，由于是16进制，所有两个代表1个字节。所以这里有三个字节，其中最后那个字节为7*16+5=117 &amp;lt; 127 所以代表一个单独字符。而AE=10 * 16 +15=175 &amp;gt;127 ，所以是某个字符的一部分&lt;/p>&lt;/blockquote>&lt;p>GBK字符集：对GB2312字符集扩充，编码方式兼容GB2312&lt;br>
UTF-8字符集：几乎收录所有字符，且不断扩充，兼容ASCAII字符集。变长：采用1&lt;sub>4字节&lt;br>
L-&amp;gt;0x4C 1字节，啊-&amp;gt;0xE5958A，两字节&lt;br>
UTF-8是Unicode字符集的一种编码方案，Unicode字符集有三种方案：UTF-8(1&lt;/sub>4字节编码一个字符)，UTF-16(2或4字节编码一个字符)，UTF-32(4字节编码一个字符)&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>对于**“我”**，ASCLL中没有，UTF-8中采用3字节编码，GB22312采用2字节编码&lt;/p>&lt;/blockquote>&lt;h1 id="mysql中支持的字符集和比较规则">MySQL中支持的字符集和比较规则&lt;a class="anchor" href="#mysql%e4%b8%ad%e6%94%af%e6%8c%81%e7%9a%84%e5%ad%97%e7%ac%a6%e9%9b%86%e5%92%8c%e6%af%94%e8%be%83%e8%a7%84%e5%88%99">#&lt;/a>&lt;/h1>
&lt;p>MySQL中，区分utf8mb3和utf8mb4，前者只是用1&lt;sub>3字节表示字符；后者使用1&lt;/sub>4字节表示字符。MySQL中，utf8代表utf8mb3。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#查看当前MySQL支持的字符集(注意，是字符集，名称都是小写)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#Default collation 默认比较规则&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; SHOW CHARSET;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+----------+---------------------------------+---------------------+--------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| Charset | Description | Default collation | Maxlen |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+----------+---------------------------------+---------------------+--------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| big5 | Big5 Traditional Chinese | big5_chinese_ci | &lt;span style="color:#ae81ff">2&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| dec8 | DEC West European | dec8_swedish_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp850 | DOS West European | cp850_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| hp8 | HP West European | hp8_english_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| koi8r | KOI8-R Relcom Russian | koi8r_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| latin1 | cp1252 West European | latin1_swedish_ci | &lt;span style="color:#ae81ff">1&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| latin2 | ISO 8859-2 Central European | latin2_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| swe7 | 7bit Swedish | swe7_swedish_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| ascii | US ASCII | ascii_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| ujis | EUC-JP Japanese | ujis_japanese_ci | &lt;span style="color:#ae81ff">3&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| sjis | Shift-JIS Japanese | sjis_japanese_ci | &lt;span style="color:#ae81ff">2&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| hebrew | ISO 8859-8 Hebrew | hebrew_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| tis620 | TIS620 Thai | tis620_thai_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| euckr | EUC-KR Korean | euckr_korean_ci | &lt;span style="color:#ae81ff">2&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| koi8u | KOI8-U Ukrainian | koi8u_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| gb2312 | GB2312 Simplified Chinese | gb2312_chinese_ci | &lt;span style="color:#ae81ff">2&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| greek | ISO 8859-7 Greek | greek_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp1250 | Windows Central European | cp1250_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| gbk | GBK Simplified Chinese | gbk_chinese_ci | &lt;span style="color:#ae81ff">2&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| latin5 | ISO 8859-9 Turkish | latin5_turkish_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| armscii8 | ARMSCII-8 Armenian | armscii8_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| utf8 | UTF-8 Unicode | utf8_general_ci | &lt;span style="color:#ae81ff">3&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| ucs2 | UCS-2 Unicode | ucs2_general_ci | &lt;span style="color:#ae81ff">2&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp866 | DOS Russian | cp866_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| keybcs2 | DOS Kamenicky Czech-Slovak | keybcs2_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| macce | Mac Central European | macce_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| macroman | Mac West European | macroman_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp852 | DOS Central European | cp852_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| latin7 | ISO 8859-13 Baltic | latin7_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> | 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | &lt;span style="color:#ae81ff">4&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp1251 | Windows Cyrillic | cp1251_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| utf16 | UTF-16 Unicode | utf16_general_ci | &lt;span style="color:#ae81ff">4&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| utf16le | UTF-16LE Unicode | utf16le_general_ci | &lt;span style="color:#ae81ff">4&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp1256 | Windows Arabic | cp1256_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp1257 | Windows Baltic | cp1257_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| utf32 | UTF-32 Unicode | utf32_general_ci | &lt;span style="color:#ae81ff">4&lt;/span> | &amp;lt;---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| binary | Binary pseudo charset | binary | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| geostd8 | GEOSTD8 Georgian | geostd8_general_ci | &lt;span style="color:#ae81ff">1&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| cp932 | SJIS &lt;span style="color:#66d9ef">for&lt;/span> Windows Japanese | cp932_japanese_ci | &lt;span style="color:#ae81ff">2&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| eucjpms | UJIS &lt;span style="color:#66d9ef">for&lt;/span> Windows Japanese | eucjpms_japanese_ci | &lt;span style="color:#ae81ff">3&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| gb18030 | China National Standard GB18030 | gb18030_chinese_ci | &lt;span style="color:#ae81ff">4&lt;/span> |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+----------+---------------------------------+---------------------+--------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">41&lt;/span> rows in set &lt;span style="color:#f92672">(&lt;/span>0.00 sec&lt;span style="color:#f92672">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>字符集的比较规则（这里先看utf8的）&lt;/p></description></item><item><title>02启动选项和系统变量</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/02/</link><pubDate>Mon, 17 Apr 2023 20:49:00 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/02/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="启动选项和配置文件">启动选项和配置文件&lt;a class="anchor" href="#%e5%90%af%e5%8a%a8%e9%80%89%e9%a1%b9%e5%92%8c%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6">#&lt;/a>&lt;/h1>
&lt;p>在程序启动时指定的设置项，也称之为&lt;strong>启动选项startup option&lt;/strong>（可以在命令行中/配置文件中 指定）&lt;br>
&lt;strong>由于在centos7中使用systemctl start mysqld启动mysql，所以好像没法用命令行指定启动选项了&lt;/strong>&lt;/p>
&lt;p>程序(可能有些程序新版本已经没有了)的对应类别和能读取的组：&lt;br>
&lt;img src="img/ly-20241212142154302.png" alt="ly-20241212142154302" />&lt;/p>
&lt;p>这里讲配置文件的方式设置启动选项：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#添加配置&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>vim /etc/my.cnf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>server&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>skip-networking &lt;span style="color:#75715e">#禁止tcp网络连接&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>default-storage-engine&lt;span style="color:#f92672">=&lt;/span>MyISAM &lt;span style="color:#75715e">#建表默认使用M有ISAM存储引擎&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#效果&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>▶ mysql -h127.0.0.1 -uroot -p
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Enter password: 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ERROR &lt;span style="color:#ae81ff">2003&lt;/span> &lt;span style="color:#f92672">(&lt;/span>HY000&lt;span style="color:#f92672">)&lt;/span>: Can&lt;span style="color:#e6db74">&amp;#39;t connect to MySQL server on &amp;#39;&lt;/span>127.0.0.1&lt;span style="color:#960050;background-color:#1e0010">&amp;#39;&lt;/span> &lt;span style="color:#f92672">(&lt;/span>111&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#去除tcp网络连接限制后新建一个表&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>▶ mysql -h127.0.0.1 -uroot -p &lt;span style="color:#75715e">#可以连接上&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; create table default_storage_engine_demo&lt;span style="color:#f92672">(&lt;/span>i int&lt;span style="color:#f92672">)&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Query OK, &lt;span style="color:#ae81ff">0&lt;/span> rows affected &lt;span style="color:#f92672">(&lt;/span>0.01 sec&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql&amp;gt; show create table default_storage_engine_demo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+-----------------------------+----------------------------------------------------------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| Table | Create Table |
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>+-----------------------------+----------------------------------------------------------------------------------------------------------------+
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>| default_storage_engine_demo | CREATE TABLE &lt;span style="color:#e6db74">`&lt;/span>default_storage_engine_demo&lt;span style="color:#e6db74">`&lt;/span> &lt;span style="color:#f92672">(&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">`&lt;/span>i&lt;span style="color:#e6db74">`&lt;/span> int&lt;span style="color:#f92672">(&lt;/span>11&lt;span style="color:#f92672">)&lt;/span> DEFAULT NULL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">)&lt;/span> ENGINE&lt;span style="color:#f92672">=&lt;/span>MyISAM DEFAULT CHARSET&lt;span style="color:#f92672">=&lt;/span>latin1 |&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果多个配置文件都配置了某个选项，如&lt;code>/etc/my.cnf /etc/mysql/my.cnf /usr/etc/my.cnf ~/.my.cnf&lt;/code>都配置了，则以最后一个配置的为主&lt;br>
如果同一个配置文件，比如[server]组和[mysqld]组都出现了default-storage-engine配置，则以后出现的组中的配置为准&lt;br>
如果一个启动选项既在命令行中出现，又在配置文件中配置，则以命令行中的为准&lt;/p></description></item><item><title>01初识MySQL</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/01/</link><pubDate>Mon, 17 Apr 2023 11:39:48 +0000</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_how_mysql_run_/01/</guid><description>&lt;blockquote class='book-hint '>
&lt;p>学习《MySQL是怎样运行的》，感谢作者！&lt;/p>&lt;/blockquote>&lt;h1 id="原文">原文&lt;a class="anchor" href="#%e5%8e%9f%e6%96%87">#&lt;/a>&lt;/h1>
&lt;h1 id="下载与安装">下载与安装&lt;a class="anchor" href="#%e4%b8%8b%e8%bd%bd%e4%b8%8e%e5%ae%89%e8%a3%85">#&lt;/a>&lt;/h1>
&lt;p>环境Centos7&lt;/p>
&lt;p>添加MySQL5.7仓库&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo rpm -ivh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>解决证书问题&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>查看是否添加成功&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo yum repolist all | grep mysql | grep 启用 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql-connectors-community/x86_64 MySQL Connectors Community 启用: &lt;span style="color:#ae81ff">213&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql-tools-community/x86_64 MySQL Tools Community 启用: &lt;span style="color:#ae81ff">96&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql57-community/x86_64 MySQL 5.7 Community Server 启用: &lt;span style="color:#ae81ff">642&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>MySQL安装&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo yum -y install mysql-community-server&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="运行与密码修改">运行与密码修改&lt;a class="anchor" href="#%e8%bf%90%e8%a1%8c%e4%b8%8e%e5%af%86%e7%a0%81%e4%bf%ae%e6%94%b9">#&lt;/a>&lt;/h1>
&lt;p>Centos7中安装目录查看，在/usr/bin中，与Max有所不同&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>whereis mysql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql: /usr/bin/mysql /usr/lib64/mysql /usr/share/mysql /usr/share/man/man1/mysql.1.gz
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ls /usr/bin |grep mysql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqladmin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlbinlog
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlcheck
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_config_editor
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqld_pre_systemd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqldump
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqldumpslow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlimport
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_install_db
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_plugin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlpump
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_secure_installation
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlshow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysqlslap
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_ssl_rsa_setup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_tzinfo_to_sql
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mysql_upgrade&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>添加mysqld目录到环境变量中（这里可省略，因为mysqld默认在/usr/bin中了&lt;/p></description></item></channel></rss>