<?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/</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/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>封面-版权</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E5%B0%81%E9%9D%A2-%E7%89%88%E6%9D%83/</link><pubDate>Sun, 05 Jan 2025 08:42:21 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E5%B0%81%E9%9D%A2-%E7%89%88%E6%9D%83/</guid><description>&lt;p>&lt;img src="img/000001.jpeg" alt="" />&lt;/p>
&lt;p>&lt;strong>内容简介&lt;/strong>&lt;/p>
&lt;p>本书是MySQL领域的经典之作，拥有广泛的影响力。第3版更新了大量的内容，不但涵盖了最新MySQL 5.5版本的新特性，也讲述了关于固态盘、高可扩展性设计和云计算环境下的数据库相关的新内容，原有的基准测试和性能优化部分也做了大量的扩展和补充。全书共分为16章和6个附录，内容涵盖MySQL架构和历史，基准测试和性能剖析，数据库软硬件性能优化，复制、备份和恢复，高可用与高可扩展性，以及云端的MySQL和MySQL相关工具等方面的内容。每一章都是相对独立的主题，读者可以有选择性地单独阅读。&lt;/p>
&lt;p>本书不但适合数据库管理员（DBA）阅读，也适合开发人员参考学习。不管是数据库新手还是专家，相信都能从本书有所收获。&lt;/p>
&lt;p>©2012 by Baron Schwartz，Peter Zaitsev，Vadim Tkachenko.&lt;/p>
&lt;p>Simplified Chinese Edition，jointly published by O&amp;rsquo;Reilly Media，Inc. and Publishing House of Electronics Industry，2013. Authorized translation of the English edition，2012 O&amp;rsquo;Reilly Media，Inc.，the owner of all rights to publish and sell the same.&lt;/p>
&lt;p>All rights reserved including the rights of reproduction in whole or in part in any form.&lt;/p>
&lt;p>本书简体中文版专有出版权由O&amp;rsquo;Reilly Media，Inc.授予电子工业出版社。未经许可，不得以任何方式复制或抄袭本书的任何部分。专有出版权受法律保护。&lt;/p>
&lt;p>版权贸易合同登记号图字：01-2013-1661&lt;/p>
&lt;p>&lt;strong>图书在版编目（CIP）数据&lt;/strong>&lt;/p>
&lt;p>高性能MySQL：第3版／（美）施瓦茨（Schwartz，B.），（美）扎伊采夫（Zaitsev，P.），（美）特卡琴科（Tkachenko，V.）著；宁海元等译．—北京：电子工业出版社，2013.5&lt;/p>
&lt;p>书名原文：High Performance MySQL，Third Edition&lt;/p>
&lt;p>ISBN 978-7-121-19885-4&lt;/p></description></item><item><title>第9章操作系统和硬件优化</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC9%E7%AB%A0%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%92%8C%E7%A1%AC%E4%BB%B6%E4%BC%98%E5%8C%96/</link><pubDate>Sun, 05 Jan 2025 08:42:19 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC9%E7%AB%A0%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E5%92%8C%E7%A1%AC%E4%BB%B6%E4%BC%98%E5%8C%96/</guid><description>&lt;p>第9章　操作系统和硬件优化&lt;/p>
&lt;p>MySQL服务器性能受制于整个系统最薄弱的环节，承载它的操作系统和硬件往往是限制因素。磁盘大小、可用内存和CPU资源、网络，以及所有连接它们的组件，都会限制系统的最终容量。因此，需要小心地选择硬件，并对硬件和操作系统进行合适的配置。例如，若工作负载是I/O密集型的，一种方法是设计应用程序使得最大限度地减少MySQL的I/O操作。然而，更聪明的方式通常是升级I/O子系统，安装更多的内存，或重新配置现有的磁盘。&lt;/p>
&lt;p>硬件的更新换代非常迅速，所以本章有关特定产品或组件的内容可能将很快变得过时。像往常一样，我们的目标是帮助提升对这些概念的理解，这样对于即使没有直接覆盖到的知识也可以举一反三。这里我们将通过现有的硬件来阐明我们的观点。&lt;/p>
&lt;h1 id="91什么限制了mysql的性能">9.1　什么限制了MySQL的性能&lt;a class="anchor" href="#91%e4%bb%80%e4%b9%88%e9%99%90%e5%88%b6%e4%ba%86mysql%e7%9a%84%e6%80%a7%e8%83%bd">#&lt;/a>&lt;/h1>
&lt;p>许多不同的硬件都可以影响MySQL的性能，但我们认为最常见的两个瓶颈是CPU和I/O资源。当数据可以放在内存中或者可以从磁盘中以足够快的速度读取时，CPU可能出现瓶颈。把大量的数据集完全放到大容量的内存中，以现在的硬件条件完全是可行的(1)。&lt;/p>
&lt;p>另一方面，I/O瓶颈，一般发生在工作所需的数据远远超过有效内存容量的时候。如果应用程序是分布在网络上的，或者如果有大量的查询和低延迟的要求，瓶颈可能转移到网络上，而不再是磁盘I/O(2)。&lt;/p>
&lt;p>第3章中提及的技巧可以帮助找到系统的限制因素，但即使你认为已经找到了瓶颈，也应该透过表象去看更深层次的问题。某一方面的缺陷常常会将压力施加在另一个子系统，导致这个子系统出问题。例如，若没有足够的内存，MySQL可能必须刷出缓存来腾出空间给需要的数据——然后，过了一小会，再读回刚刚刷新的数据（读取和写入操作都可能发生这个问题）。本来是内存不足，却导致出现了I/O容量不足。当找到一个限制系统性能的因素时，应该问问自己，“是这个部分本身的问题，还是系统中其他不合理的压力转移到这里所导致的？”在第3章的诊断案例中也有讨论到这个问题。&lt;/p>
&lt;p>还有另外一个例子：内存总线的瓶颈也可能表现为CPU问题。事实上，我们说一个应用程序有“CPU瓶颈”或者是“CPU密集型”，真正的意思应该是计算的瓶颈。接下来将深入探讨这个问题。&lt;/p>
&lt;h1 id="92如何为mysql选择cpu">9.2　如何为MySQL选择CPU&lt;a class="anchor" href="#92%e5%a6%82%e4%bd%95%e4%b8%bamysql%e9%80%89%e6%8b%a9cpu">#&lt;/a>&lt;/h1>
&lt;p>在升级当前硬件或购买新的硬件时，应该考虑下工作负载是不是CPU密集型。&lt;/p>
&lt;p>可以通过检查CPU利用率来判断是否是CPU密集型的工作负载，但是仅看CPU整体的负载是不合理的，还需要看看CPU使用率和大多数重要的查询的I/O之间的平衡，并注意CPU负载是否分配均匀。本章稍后讨论的工具可以用来弄清楚是什么限制了服务器的性能。&lt;/p>
&lt;h2 id="921哪个更好更快的cpu还是更多的cpu">9.2.1　哪个更好：更快的CPU还是更多的CPU&lt;a class="anchor" href="#921%e5%93%aa%e4%b8%aa%e6%9b%b4%e5%a5%bd%e6%9b%b4%e5%bf%ab%e7%9a%84cpu%e8%bf%98%e6%98%af%e6%9b%b4%e5%a4%9a%e7%9a%84cpu">#&lt;/a>&lt;/h2>
&lt;p>当遇到CPU密集型的工作时，MySQL通常可以从更快的CPU中获益（相对更多的CPU）。&lt;/p>
&lt;p>但这不是绝对的，因为还依赖于负载情况和CPU数量。更古老的MySQL版本在多CPU上有扩展性问题，即使新版本也不能对单个查询并发利用多个CPU。因此，CPU速度限制了每个CPU密集型查询的响应时间。&lt;/p>
&lt;p>当我们讨论CPU的时候，为保证本文易于阅读，对某些术语将不会做严格的定义。现在一般的服务器通常都有多个插槽（Socket），每个插槽上都可以插一个有多个核心的CPU（有独立的执行单元），并且每个核心可能有多个“硬件线程”。这些复杂的架构需要有点耐心去了解，并且我们不会总是明确地区分它们。不过，在一般情况下，当谈到CPU速度的时候，谈论的其实是执行单元的速度，当提到的CPU数量时，指的通常是在操作系统上看到的数量，尽管这可能是独立的执行单元数量的多倍(3)。&lt;/p>
&lt;p>这几年CPU在各个方面都有了很大的提升。例如，今天的Intel CPU速度远远超过前几代，这得益于像直接内存连接（directly attached memory）技术以及PCIe卡之类的设备互联上的改善等。这些改进对于存储设备尤其有效，例如Fusion-io和Virident的PCIe闪存驱动器。&lt;/p>
&lt;p>超线程的效果相比以前也要好得多，现在操作系统也更了解如何更好地使用超线程。而以前版本的操作系统无法识别两个虚拟处理器实际上是在同一芯片上，认为它们是独立的，于是会把任务安排在两个实际上是相同物理执行单元上的虚拟处理器。实际上单个执行单元并不是真的可以在同一时间运行两个进程，所以这样做会发生冲突和争夺资源。而同时其他CPU却可能在闲置，从而浪费资源。操作系统需要能感知超线程，因为它必须知道什么时候执行单元实际上是闲置的，然后切换相应的任务去执行。这个问题之前常见的原因是在等待内存总线，可能花费需要高达一百个CPU周期，这已经类似于一个轻量级的I/O等待。新的操作系统在这方面有了很大的改善。超线程现在已经工作得很好。过去，我们时常提醒人们禁用它，但现在已经不需要这样做了。&lt;/p>
&lt;p>这就是说，现在可以得到大量的快速的CPU——比本书的第2版出版的时候要多得多。所以多和快哪个更重要？一般来说两个都想要。从广义上来说，调优服务器可能有如下两个目标：&lt;/p>
&lt;p>低延时（快速响应）&lt;/p>
&lt;p>要做到这一点，需要高速CPU，因为每个查询只能使用一个CPU。&lt;/p>
&lt;p>高吞吐&lt;/p>
&lt;p>如果能同时运行很多查询语句，则可以从多个CPU处理查询中受益。然而，在实践中，还要取决于具体情况。因为MySQL还不能在多个CPU中完美地扩展，能用多少个CPU还是有极限的。在旧版本的MySQL中（MySQL 5.1以后的版本已经有一些提升），这个限制非常严重。在新的版本中，则可以放心地扩展到16或24个CPU，或者更多，取决于使用的是哪个版本（Percona往往在这方面略占优势）。&lt;/p>
&lt;p>如果有多路CPU，并且没有并发执行查询语句，MySQL依然可以利用额外的CPU为后台任务（例如清理InnoDB缓冲、网络操作，等等）服务。然而，这些任务通常比执行查询语句更加轻量化。&lt;/p>
&lt;p>MySQL复制（将在下一章中讨论）也能在高速CPU下工作得非常好，而多CPU对复制的帮助却不大。如果工作负载是CPU密集型，主库上的并发任务传递到备库以后会被简化为串行任务，这样即使备库硬件比主库好，也可能无法保持跟主库之间的同步。也就是说，备库的瓶颈通常是I/O子系统，而不是CPU。&lt;/p>
&lt;p>如果有一个CPU密集型的工作负载，考虑是需要更快的CPU还是更多CPU的另外一个因素是查询语句实际在做什么。在硬件层面，一个查询可以在执行或等待。处于等待状态常见的原因是在运行队列中等待（进程已经是可运行状态，但所有的CPU都忙）、等待闩锁（Latch）或锁（Lock）、等待磁盘或网络。那么你期望查询是等待什么呢？如果等待闩锁或锁，通常需要更快的CPU；如果在运行队列中等待，那么更多或者更快的CPU都可能有帮助。（也可能有例外，例如，查询等待InnoDB日志缓冲区的Mutex，直到I/O完成前都不会释放——这可能表明需要更多的I/O容量）。&lt;/p>
&lt;p>这就是说，MySQL在某些工作负载下可以有效地利用很多CPU。例如，假设有很多连接查询的是不同表（假设这些查询不会造成表锁的竞争，实际上对MyISAM和MEMORY表可能会有问题），并且服务器的总吞吐量比任何单个查询的响应时间都更重要。吞吐量在这种情况下可以非常高，因为线程可以同时运行而互不争用。&lt;/p>
&lt;p>再次说明，在理论上这可能更好地工作：不管查询是读取不同的表还是相同的表， InnoDB都会有一些全局共享的数据结构，而MyISAM在每个缓冲区都有全局锁。而且不仅仅是存储引擎，服务器层也有全局锁。以前InnoDB承担了所有的骂名，但最近做了一些改进后，暴露了服务器层中的其他瓶颈。例如臭名昭著的LOCK_open互斥量（Mutex），在MySQL 5.1和更早版本中可能就是个大问题，另外还有其他一些服务器级别的互斥量（例如查询缓存）。&lt;/p>
&lt;p>通常可以通过堆栈跟踪来诊断这些类型的竞争问题，例如Percona Toolkit中的&lt;em>pt-pmp&lt;/em>工具。如果遇到这样的问题，可能需要改变服务器的配置，禁用或改变引起问题的组件，进行数据分片（Sharding），或者通过某种方式改变做事的方法。这里无法列举所有的问题和相应的解决方案，但是一旦有一个确定的诊断，答案通常是显而易见的。大部分不幸遇到的问题都是边缘场景，最常见的问题随着时间的推移都在服务器上被修复了。&lt;/p>
&lt;h2 id="922cpu架构">9.2.2　CPU架构&lt;a class="anchor" href="#922cpu%e6%9e%b6%e6%9e%84">#&lt;/a>&lt;/h2>
&lt;p>可能99%以上的MySQL实例（不含嵌入式使用）都运行在Intel或者AMD芯片的x86架构下。本书中我们基本都是针对这种情况。&lt;/p>
&lt;p>64位架构现在都是默认的了，32位CPU已经很难买到了。MySQL在64位架构上工作良好，尽管有些事暂时不能利用64位架构来做。因此，如果使用的是较老旧版本的MySQL，在64位服务器上可能要小心。例如，在MySQL 5.0发布的早期时候，每个MyISAM键缓冲区被限制为4 GB，由一个32位整数负责寻址。（可以创建多个键缓冲区来解决这个问题。）&lt;/p>
&lt;p>确保在64位硬件上使用64位操作系统！最近这种情况已经不太常见了，但以前经常可以遇到，大多数主机托管提供商暂时还是在服务器上安装32位操作系统，即使是64位CPU。32位操作系统意味着不能使用大量的内存：尽管某些32位系统可以支持大量的内存，但不能像64位系统一样有效地利用，并且在32位系统上，任何一个单独的进程都不能寻址4 GB以上的内存。&lt;/p>
&lt;h2 id="923扩展到多个cpu和核心">9.2.3　扩展到多个CPU和核心&lt;a class="anchor" href="#923%e6%89%a9%e5%b1%95%e5%88%b0%e5%a4%9a%e4%b8%aacpu%e5%92%8c%e6%a0%b8%e5%bf%83">#&lt;/a>&lt;/h2>
&lt;p>多CPU在联机事务处理（OLTP）系统的场景中非常有用。这些系统通常执行许多小的操作，并且是从多个连接发起请求，因此可以在多个CPU上运行。在这样的环境中，并发可能成为瓶颈。大多数Web应用程序都属于这一类。&lt;/p>
&lt;p>OLTP服务器一般使用InnoDB，尽管它在多CPU的环境中还存在一些未解决的并发问题。然而，不只是InnoDB可能成为瓶颈：任何共享资源都是潜在的竞争点。InnoDB之所以获得大量关注是因为它是高并发环境下最常见的存储引擎，但MyISAM在大压力时的表现也不好，即使不修改任何数据只是读取数据也是如此。许多并发瓶颈，如InnoDB的行级锁和MyISAM的表锁，没有办法优化——除了尽可能快地处理任务之外，没有别的办法解决，这样，锁就可以尽快分配给等待的任务。如果一个锁是造成它们（其他任务）都在等待的原因，那么不管有多少CPU都一样。因此，即使是一些高并发工作负载，也可以从更快的CPU中受益。&lt;/p>
&lt;p>实际上有两种类型的数据库并发问题，需要不同的方法来解决，如下所示。&lt;/p>
&lt;p>逻辑并发问题&lt;/p>
&lt;p>应用程序可以看到资源的竞争，如表或行锁争用。这些问题通常需要好的策略来解决，如改变应用程序、使用不同的存储引擎、改变服务器的配置，或使用不同的锁定提示或事务隔离级别。&lt;/p>
&lt;p>内部并发问题&lt;/p>
&lt;p>比如信号量、访问InnoDB缓冲池页面的资源争用，等等。可以尝试通过改变服务器的设置、改变操作系统，或使用不同的硬件解决这些问题，但通常只能缓解而无法彻底消灭。在某些情况下，使用不同的存储引擎或给存储引擎打补丁，可以帮助缓解这些问题。&lt;/p>
&lt;p>MySQL的“扩展模式”是指它可以有效利用的CPU数量，以及在压力不断增长的情况下如何扩展，这同时取决于工作负载和系统架构。通过“系统架构”的手段是指通过调整操作系统和硬件，而不是通过优化使用MySQL的应用程序。CPU架构（RISC、CISC、流水线深度等）、CPU型号和操作系统都影响MySQL的扩展模式。这也是为什么说基准测试是非常重要的：一些系统可以在不断增加的并发下依然运行得很好，而另一些的表现则糟糕得多。&lt;/p>
&lt;p>有些系统在更多的处理器下甚至可能降低整体性能。这是相当普遍的情况，我们了解到许多人试图升级到有多个CPU的系统，最后只能被迫恢复到旧系统（或绑定MySQL进程到其中某些核心），因为这种升级反而降低了性能。在MySQL 5.0时代，Google的补丁和Percona Server出现之前，能有效利用的CPU核数是4核，但是现在甚至可以看到操作系统报告多达80个“CPU”的服务器。如果规划一个大的升级，必须要同时考虑硬件、服务器版本和工作负载。&lt;/p>
&lt;p>某些MySQL扩展性瓶颈在服务器层，而其他一些在存储引擎层。存储引擎是怎么设计的至关重要，有时更换到一个不同的引擎就可以从多处理器上获得更多效果。&lt;/p>
&lt;p>我们看到在世纪之交围绕处理器速度的战争在一定程度上已经平息，CPU厂商更多地专注于多核CPU和多线程的变化。CPU设计的未来很可能是数百个处理器核心，四核心和六核心的CPU在今天是很常见的。不同厂商的内部架构差异很大，不可能概括出线程、CPU和内核之间的相互作用。内存和总线如何设计也是非常重要的。归根结底，多个内核和多个物理CPU哪个更好，这是由硬件体系结构决定的。&lt;/p>
&lt;p>现代CPU的另外两个复杂之处也值得提一下。首先是频率调整。这是一种电源管理技术，可以根据CPU上的压力而动态地改变CPU的时钟速度。问题是，它有时不能很好地处理间歇性突发的短查询的情况，因为操作系统可能需要一段时间来决定CPU的时钟是否应该变化。结果，查询可能会有一段时间速度较慢，并且响应时间增加了。频率调整可能使间歇性的工作负载性能低下，但可能更重要的是，它会导致性能波动。&lt;/p>
&lt;p>第二个复杂之处是boost技术，这个技术改变了我们对CPU模式的看法。我们曾经以为四核2GHz CPU有四个同样强大的核心，不管其中有些是闲置或非闲置。因此，一个完美的可扩展系统，当它使用所有四个内核的时候，可以预计得到四倍的提升。但是现在已经不是这样了，因为当系统只使用一个核心时，处理器会运行在更高的时钟速度上，例如3GHz。这给很多的规划容量和可扩展性建模的工具出了一个难题，因为系统性能表现不再是线性的变化了。这也意味着，“空闲CPU”并不代表相同规模的资源浪费，如果有一台服务器上只运行了备库的复制，而复制执行是单线程的，所以有三个CPU是空闲的，因此认为可以利用这些CPU资源执行其他任务而不影响复制，可能就想错了。&lt;/p>
&lt;h1 id="93平衡内存和磁盘资源">9.3　平衡内存和磁盘资源&lt;a class="anchor" href="#93%e5%b9%b3%e8%a1%a1%e5%86%85%e5%ad%98%e5%92%8c%e7%a3%81%e7%9b%98%e8%b5%84%e6%ba%90">#&lt;/a>&lt;/h1>
&lt;p>配置大量内存最大的原因其实不是因为可以在内存中保存大量数据：最终目的是避免磁盘I/O，因为磁盘I/O比在内存中访问数据要慢得多。关键是要平衡内存和磁盘的大小、速度、成本和其他因素，以便为工作负载提供高性能的表现。在讨论如何做到这一点之前，暂时先回到基础知识上来。&lt;/p>
&lt;p>计算机包含一个金字塔型的缓存体系，更小、更快、更昂贵的缓存在顶端，如图9-1所示。&lt;/p>
&lt;p>&lt;img src="img/000000.jpeg" alt="" />
&lt;strong>图9-1：缓存层级&lt;/strong>&lt;/p></description></item><item><title>第8章优化服务器设置</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC8%E7%AB%A0%E4%BC%98%E5%8C%96%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE/</link><pubDate>Sun, 05 Jan 2025 08:42:18 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC8%E7%AB%A0%E4%BC%98%E5%8C%96%E6%9C%8D%E5%8A%A1%E5%99%A8%E8%AE%BE%E7%BD%AE/</guid><description>&lt;p>第8章　优化服务器设置&lt;/p>
&lt;p>在这一章，我们将解释为&lt;em>&lt;strong>这是我的撒旦JFK数据库嘎斯公开就开始打山豆根士大夫 圣诞节复活节是是国家开始大幅机啊可是对方看见噶开暗杀是的JFK开始讲课的感觉爱看书的JFK史蒂夫卡卡萨丁咖啡碱撒快递费始东方会i二位人家儿童科技数据库的房价开始JFK注释&lt;/strong>&lt;/em>MySQL服务器创建一个靠谱的配置文件的过程。这是一个很绕的过程，有很多有意思的关注点和值得关&lt;strong>注的思路。关注这&lt;/strong>些点很有必要，因为创建一个好配置的最快方法不是从学习配置项开始，也不是从问哪个配置项应该怎么设置或者怎么修改开始，更不是从检查服务器行为和询问哪个配置项可以提升性能开始。最好是从理解MySQL内核和行为开始。然后可以利用这些知识来指导配置MySQL。最后，可以将想要的配置和当前配置进行比较，然后纠正重要并且有价值的不同之处。&lt;/p>
&lt;p>人们经常问，“我的服务器有32GB内存，12核CPU，怎样配置最好？”很遗憾，问题没这么简单。服务器的配置应该符合它的工作负载、数据，以及应用需求，并不仅仅看硬件的情况。&lt;/p>
&lt;p>MySQL有大量可以修改的参数——但不应该随便去修改。通常只需要把基本的项配置正确（大部分情况下只有很少一些参数是真正重要的），应该将更多的时间花在schema的优化、索引，以及查询设计上。在正确地配置了MySQL的基本配置项之后，再花力气去修改其他配置项的收益通常就比较小了。&lt;/p>
&lt;p>从另一方面来说，没用的配置导致潜在风险的可能更大。我们碰到过不止一个“高度调优”过的服务器不停地崩溃，停止服务或者运行缓慢，结果都是因为错误的配置导致的。我们将花一点时间来解释为什么会发生这种情况，并且告诉大家什么是不该做的。&lt;/p>
&lt;p>那么什么是该做的呢？确保基本的配置是正确的，例如InnoDB的Buffer Pool和日志文件缓存大小，如果想防止出问题（提醒一下，这样做通常不能提升性能——它们只能避免问题），就设置一个比较安全和稳健的值，剩下的配置就不用管了。如果碰到了问题，可以使用第3章提到的技巧小心地进行诊断。如果问题是由于服务器的某部分导致的，而这恰好可以通过某个配置项解决，那么需要做的就是更改配置。&lt;/p>
&lt;p>有时候，在某些特定的场景下，也有可能设置某些特殊的配置项会有显著的性能提升。但无论如何，这些特殊的配置项不应该成为服务器基本配置文件的一部分。只有当发现特定的性能问题才应该设置它们。这就是为什么我们不建议通过寻找有问题的地方修改配置项的原因。如果有些地方确实需要提升，也需要在查询响应时间上有所体现。最好是从查询语句和响应时间入手来开始分析问题，而不是通过配置项。这可以节省大量的时间，避免很多的问题。&lt;/p>
&lt;p>另一个节省时间和避免麻烦的好办法是使用默认配置，除非是明确地知道默认值会有问题。很多人都是在默认配置下运行的，这种情况非常普遍。这使得默认配置是经过最多实际测试的。对配置项做一些不必要的修改可能会遇到一些意料之外的bug。&lt;/p>
&lt;h1 id="81mysql配置的工作原理">8.1　MySQL配置的工作原理&lt;a class="anchor" href="#81mysql%e9%85%8d%e7%bd%ae%e7%9a%84%e5%b7%a5%e4%bd%9c%e5%8e%9f%e7%90%86">#&lt;/a>&lt;/h1>
&lt;p>在讨论如何配置MySQL之前，我们先来解释一下MySQL的配置机制。MySQL对配置要求非常宽松，但是下面这些建议可能会为你节省大量的工作和时间。&lt;/p>
&lt;p>首先应该知道的是MySQL从哪里获得配置信息：命令行参数和配置文件。在类UNIX系统中，配置文件的位置一般在*/etc/my.cnf&lt;em>或者&lt;/em>/etc/mysql/my.cnf*。如果使用操作系统的启动脚本，这通常是唯一指定配置设置的地方。如果手动启动MySQL，例如在测试安装时，也可以在命令行指定设置。实际上，服务器会读取配置文件的内容，删除所有注释和换行，然后和命令行选项一起处理。&lt;/p>
&lt;p>&lt;img src="img/000000.jpeg" alt="i-" />关于术语的说明：因为很多MySQL命令行选项跟服务器变量相同，我们有时把选项和变量替换使用。大部分变量和它们对应的命令行选项名称一样，但是有一些例外。例如，&lt;em>&amp;ndash;memlock&lt;/em>选项设置了locked_in_memory变量。&lt;/p>
&lt;p>任何打算长期使用的设置都应该写到全局配置文件，而不是在命令行特别指定。否则，如果偶然在启动时忘了设置就会有风险。把所有的配置文件放在同一个地方以方便检查也是个好办法。&lt;/p>
&lt;p>一定要清楚地知道服务器配置文件的位置！我们见过有些人尝试修改配置文件但是不生效，因为他们修改的并不是服务器读取的文件，例如Debian下，&lt;em>/etc/mysql/my.cnf&lt;/em>才是MySQL读取的配置文件，而不是*/etc/my.cnf*。有时候好几个地方都有配置文件，也许是因为之前的系统管理员也没搞清楚情况（因此在各个可能的位置都放了一份）。如果不知道当前使用的配置文件路径，可以尝试下面的操作：&lt;/p>
&lt;pre>&lt;code> ** $ which mysqld** 
 /usr/sbin/mysqld
 ** $ /usr/sbin/mysqld --verbose --help | grep -A 1 'Default options'** 
 Default options are read from the following files in the given order:
 /etc/mysql/my.cnf ~/.my.cnf /usr/etc/my.cnf
&lt;/code>&lt;/pre>
&lt;p>对于服务器上只有一个MySQL实例的典型安装，这个命令很有用。也可以设计更复杂的配置，但是没有标准的方法告诉你怎么来做。MySQL发行版包含了一个现在废弃了的程序，叫mysqlmanager，可以在一个有多个独立部分的配置文件上运行多个实例。（现在已经被一样古老的mysqld_multi脚本替代。）然而许多操作系统发行版本在启动脚本中并不包含或使用这个程序。实际上，很多系统甚至没有使用MySQL提供的启动脚本。&lt;/p>
&lt;p>配置文件通常分成多个部分，每个部分的开头是一个用方括号括起来的分段名称。MySQL程序通常读取跟它同名的分段部分，许多客户端程序还会读取client部分，这是一个存放公用设置的地方。服务器通常读取mysqld这一段。一定要确认配置项放在了文件正确的分段中，否则配置是不会生效的。&lt;/p>
&lt;h2 id="811语法作用域和动态性">8.1.1　语法、作用域和动态性&lt;a class="anchor" href="#811%e8%af%ad%e6%b3%95%e4%bd%9c%e7%94%a8%e5%9f%9f%e5%92%8c%e5%8a%a8%e6%80%81%e6%80%a7">#&lt;/a>&lt;/h2>
&lt;p>配置项设置都使用小写，单词之间用下画线或横线隔开。下面的例子是等价的，并且可能在命令行和配置文件中都看到这两种格式：&lt;/p>
&lt;pre>&lt;code> /usr/sbin/mysqld --auto-increment-offset=5
 /usr/sbin/mysqld --auto-increment-offset=5
&lt;/code>&lt;/pre>
&lt;p>我们建议使用一种固定的风格。这样在配置文件中搜索配置项时会容易得多。&lt;/p>
&lt;p>配置项可以有多个作用域。有些设置是服务器级的（全局作用域），有些对每个连接是不同的（会话作用域），剩下的一些是对象级的。许多会话级变量跟全局变量相等，可以认为是默认值。如果改变会话级变量，它只影响改动的当前连接，当连接关闭时所有参数变更都会失效。下面有一些例子，你应该清楚这些不同类型的行为：&lt;/p>
&lt;ul>
&lt;li>query_cache_sizey变量是全局的。&lt;/li>
&lt;li>sort_buffer_sizey变量默认是全局相同的，但是每个线程里也可以设置。&lt;/li>
&lt;li>join_buffer_sizey变量也有全局默认值且每个线程是可以设置的，但是若一个查询中关联多张表，可以为每个关联分配一个关联缓冲（join buffer），所以每个查询可能有多个关联缓冲。&lt;/li>
&lt;/ul>
&lt;p>另外，除了在配置文件中设置变量，有很多变量（但不是所有）也可以在服务器运行时修改。MySQL把这些归为动态配置变量。下面的语句展示了动态改变sort_buffer_size的会话值和全局值的不同方式：&lt;/p></description></item><item><title>第7章MySQL高级特性</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC7%E7%AB%A0MySQL%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7/</link><pubDate>Sun, 05 Jan 2025 08:42:17 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC7%E7%AB%A0MySQL%E9%AB%98%E7%BA%A7%E7%89%B9%E6%80%A7/</guid><description>&lt;p>第7章　MySQL高级特性&lt;/p>
&lt;p>MySQL从5.0和5.1版本开始引入了很多高级特性，例如分区、触发器等，这对有其他关系型数据库使用背景的用户来说可能并不陌生。这些新特性吸引了很多用户开始使用MySQL。不过，这些特性的性能到底如何，还需要用户真正使用过才能知道。本章我们将为大家介绍，在真实的世界中，这些特性表现如何，而不是只简单地介绍参考手册或者宣传材料上的数据。&lt;/p>
&lt;h1 id="71分区表">7.1　分区表&lt;a class="anchor" href="#71%e5%88%86%e5%8c%ba%e8%a1%a8">#&lt;/a>&lt;/h1>
&lt;p>对用户来说，分区表是一个独立的逻辑表，但是底层由多个物理子表组成。实现分区的代码实际上是对一组底层表的句柄对象（Handler Object）的封装。对分区表的请求，都会通过句柄对象转化成对存储引擎的接口调用。所以分区对于SQL层来说是一个完全封装底层实现的黑盒子，对应用是透明的，但是从底层的文件系统来看就很容易发现，每一个分区表都有一个使用#分隔命名的表文件。&lt;/p>
&lt;p>MySQL实现分区表的方式——对底层表的封装——意味着索引也是按照分区的子表定义的，而没有全局索引。这和Oracle不同，在Oracle中可以更加灵活地定义索引和表是否进行分区。&lt;/p>
&lt;p>MySQL在创建表时使用PARTITION BY子句定义每个分区存放的数据。在执行查询的时候，优化器会根据分区定义过滤那些没有我们需要数据的分区，这样查询就无须扫描所有分区——只需要查找包含需要数据的分区就可以了。&lt;/p>
&lt;p>分区的一个主要目的是将数据按照一个较粗的粒度分在不同的表中。这样做可以将相关的数据存放在一起，另外，如果想一次批量删除整个分区的数据也会变得很方便。&lt;/p>
&lt;p>在下面的场景中，分区可以起到非常大的作用：&lt;/p>
&lt;ul>
&lt;li>表非常大以至于无法全部都放在内存中，或者只在表的最后部分有热点数据，其他均是历史数据。&lt;/li>
&lt;li>分区表的数据更容易维护。例如，想批量删除大量数据可以使用清除整个分区的方式。另外，还可以对一个独立分区进行优化、检查、修复等操作。&lt;/li>
&lt;li>分区表的数据可以分布在不同的物理设备上，从而高效地利用多个硬件设备。&lt;/li>
&lt;li>可以使用分区表来避免某些特殊的瓶颈，例如InnoDB的单个索引的互斥访问、ext3文件系统的inode锁竞争等。&lt;/li>
&lt;li>如果需要，还可以备份和恢复独立的分区，这在非常大的数据集的场景下效果非常好。&lt;/li>
&lt;/ul>
&lt;p>MySQL的分区实现非常复杂，我们不打算介绍实现的全部细节。这里我们将专注在分区性能方面，所以如果想了解更多的关于分区的基础知识，我们建议阅读MySQL官方手册中的“分区”一节，其中介绍了很多分区相关的基础知识。另外，还可以阅读CREATE TABLE、SHOW CREATE TABLE、ALTER TABLE和INFORMATION_SCHEMA.PARTITIONS、EXPLAIN关于分区部分的介绍。分区特性使得CREATE TABLE和ALTER TABLE命令变得更加复杂了。&lt;/p>
&lt;p>分区表本身也有一些限制，下面是其中比较重要的几点：&lt;/p>
&lt;ul>
&lt;li>一个表最多只能有1024个分区。&lt;/li>
&lt;li>在MySQL 5.1中，分区表达式必须是整数，或者是返回整数的表达式。在MySQL 5.5中，某些场景中可以直接使用列来进行分区。&lt;/li>
&lt;li>如果分区字段中有主键或者唯一索引的列，那么所有主键列和唯一索引列都必须包含进来。&lt;/li>
&lt;li>分区表中无法使用外键约束。&lt;/li>
&lt;/ul>
&lt;h2 id="711分区表的原理">7.1.1　分区表的原理&lt;a class="anchor" href="#711%e5%88%86%e5%8c%ba%e8%a1%a8%e7%9a%84%e5%8e%9f%e7%90%86">#&lt;/a>&lt;/h2>
&lt;p>如前所述，分区表由多个相关的底层表实现，这些底层表也是由句柄对象（Handler object）表示，所以我们也可以直接访问各个分区。存储引擎管理分区的各个底层表和管理普通表一样（所有的底层表都必须使用相同的存储引擎），分区表的索引只是在各个底层表上各自加上一个完全相同的索引。从存储引擎的角度来看，底层表和一个普通表没有任何不同，存储引擎也无须知道这是一个普通表还是一个分区表的一部分。&lt;/p>
&lt;p>分区表上的操作按照下面的操作逻辑进行：&lt;/p>
&lt;p>SELECT查询&lt;/p>
&lt;p>当查询一个分区表的时候，分区层先打开并锁住所有的底层表，优化器先判断是否可以过滤部分分区，然后再调用对应的存储引擎接口访问各个分区的数据。&lt;/p>
&lt;p>INSERT操作&lt;/p>
&lt;p>当写入一条记录时，分区层先打开并锁住所有的底层表，然后确定哪个分区接收这条记录，再将记录写入对应底层表。&lt;/p>
&lt;p>DELETE操作&lt;/p>
&lt;p>当删除一条记录时，分区层先打开并锁住所有的底层表，然后确定数据对应的分区，最后对相应底层表进行删除操作。&lt;/p>
&lt;p>UPDATE操作&lt;/p>
&lt;p>当更新一条记录时，分区层先打开并锁住所有的底层表，MySQL先确定需要更新的记录在哪个分区，然后取出数据并更新，再判断更新后的数据应该放在哪个分区，最后对底层表进行写入操作，并对原数据所在的底层表进行删除操作。&lt;/p>
&lt;p>有些操作是支持过滤的。例如，当删除一条记录时，MySQL需要先找到这条记录，如果WHERE条件恰好和分区表达式匹配，就可以将所有不包含这条记录的分区都过滤掉。这对UPDATE语句同样有效。如果是INSERT操作，则本身就是只命中一个分区，其他分区都会被过滤掉。MySQL先确定这条记录属于哪个分区，再将记录写入对应的底层分区表，无须对任何其他分区进行操作。&lt;/p>
&lt;p>虽然每个操作都会“先打开并锁住所有的底层表”，但这并不是说分区表在处理过程中是锁住全表的。如果存储引擎能够自己实现行级锁，例如InnoDB，则会在分区层释放对应表锁。这个加锁和解锁过程与普通InnoDB上的查询类似。&lt;/p>
&lt;p>后面我们会通过一些例子来看看，当访问一个分区表的时候，打开和锁住所有底层表的代价及其带来的后果。&lt;/p>
&lt;h2 id="712分区表的类型">7.1.2　分区表的类型&lt;a class="anchor" href="#712%e5%88%86%e5%8c%ba%e8%a1%a8%e7%9a%84%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>MySQL支持多种分区表。我们看到最多的是根据范围进行分区，每个分区存储落在某个范围的记录，分区表达式可以是列，也可以是包含列的表达式。例如，下表就可以将每一年的销售额存放在不同的分区里：&lt;/p>
&lt;pre>&lt;code> CREATE TABLE sales (
 order_date DATETIME NOT NULL,
 -- Other columns omitted
 ) ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date)) (
 PARTITION p_2010 VALUES LESS THAN (2010),
 PARTITION p_2011 VALUES LESS THAN (2011),
 PARTITION p_2012 VALUES LESS THAN (2012),
 PARTITION p_catchall VALUES LESS THAN MAXVALUE );
&lt;/code>&lt;/pre>
&lt;p>PARTITION分区子句中可以使用各种函数。但有一个要求，表达式返回的值要是一个确定的整数，且不能是一个常数。这里我们使用函数YEAR()，也可以使用任何其他的函数，如TO_DAYS()。根据时间间隔进行分区，是一种很常见的分区方式，后面我们还会再回过头来看这个例子，看看如何优化这个例子来避免一些问题。&lt;/p></description></item><item><title>第6章查询性能优化</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC6%E7%AB%A0%E6%9F%A5%E8%AF%A2%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</link><pubDate>Sun, 05 Jan 2025 08:42:16 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC6%E7%AB%A0%E6%9F%A5%E8%AF%A2%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/</guid><description>&lt;p>第6章　查询性能优化&lt;/p>
&lt;p>前面的章节我们介绍了如何设计最优的库表结构、如何建立最好的索引，这些对于高性能来说是必不可少的。但这些还不够——还需要合理的设计查询。如果查询写得很糟糕，即使库表结构再合理、索引再合适，也无法实现高性能。&lt;/p>
&lt;p>查询优化、索引优化、库表结构优化需要齐头并进，一个不落。在获得编写MySQL查询的经验的同时，也将学习到如何为高效的查询设计表和索引。同样的，也可以学习到在优化库表结构时会影响到哪些类型的查询。这个过程需要时间，所以建议大家在学习后面章节的时候多回头看看这三章的内容。&lt;/p>
&lt;p>本章将从查询设计的一些基本原则开始——这也是在发现查询效率不高的时候首先需要考虑的因素。然后会介绍一些更深的查询优化的技巧，并会介绍一些MySQL优化器内部的机制。我们将展示MySQL是如何执行查询的，你也将学会如何去改变一个查询的执行计划。最后，我们要看一下MySQL优化器在哪些方面做得还不够，并探索查询优化的模式，以帮助MySQL更有效地执行查询。&lt;/p>
&lt;p>本章的目标是帮助大家更深刻地理解MySQL如何真正地执行查询，并明白高效和低效的原因何在，这样才能充分发挥MySQL的优势，并避开它的弱点。&lt;/p>
&lt;h1 id="61为什么查询速度会慢">6.1　为什么查询速度会慢&lt;a class="anchor" href="#61%e4%b8%ba%e4%bb%80%e4%b9%88%e6%9f%a5%e8%af%a2%e9%80%9f%e5%ba%a6%e4%bc%9a%e6%85%a2">#&lt;/a>&lt;/h1>
&lt;p>在尝试编写快速的查询之前，需要清楚一点，真正重要是响应时间。如果把查询看作是一个任务，那么它由一系列子任务组成，每个子任务都会消耗一定的时间。如果要优化查询，实际上要优化其子任务，要么消除其中一些子任务，要么减少子任务的执行次数，要么让子任务运行得更快(1)。&lt;/p>
&lt;p>MySQL在执行查询的时候有哪些子任务，哪些子任务运行的速度很慢？这里很难给出完整的列表，但如果按照第3章介绍的方法对查询进行剖析，就能看到查询所执行的子任务。通常来说，查询的生命周期大致可以按照顺序来看：从客户端，到服务器，然后在服务器上进行解析，生成执行计划，执行，并返回结果给客户端。其中“执行”可以认为是整个生命周期中最重要的阶段，这其中包括了大量为了检索数据到存储引擎的调用以及调用后的数据处理，包括排序、分组等。&lt;/p>
&lt;p>在完成这些任务的时候，查询需要在不同的地方花费时间，包括网络，CPU计算，生成统计信息和执行计划、锁等待（互斥等待）等操作，尤其是向底层存储引擎检索数据的调用操作，这些调用需要在内存操作、CPU操作和内存不足时导致的I/O操作上消耗时间。根据存储引擎不同，可能还会产生大量的上下文切换以及系统调用。&lt;/p>
&lt;p>在每一个消耗大量时间的查询案例中，我们都能看到一些不必要的额外操作、某些操作被额外地重复了很多次、某些操作执行得太慢等。优化查询的目的就是减少和消除这些操作所花费的时间。&lt;/p>
&lt;p>再次申明一点，对于一个查询的全部生命周期，上面列的并不完整。这里我们只是想说明：了解查询的生命周期、清楚查询的时间消耗情况对于优化查询有很大的意义。有了这些概念，我们再一起来看看如何优化查询。&lt;/p>
&lt;h1 id="62慢查询基础优化数据访问">6.2　慢查询基础：优化数据访问&lt;a class="anchor" href="#62%e6%85%a2%e6%9f%a5%e8%af%a2%e5%9f%ba%e7%a1%80%e4%bc%98%e5%8c%96%e6%95%b0%e6%8d%ae%e8%ae%bf%e9%97%ae">#&lt;/a>&lt;/h1>
&lt;p>查询性能低下最基本的原因是访问的数据太多。某些查询可能不可避免地需要筛选大量数据，但这并不常见。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询，我们发现通过下面两个步骤来分析总是很有效：&lt;/p>
&lt;ol>
&lt;li>确认应用程序是否在检索大量超过需要的数据。这通常意味着访问了太多的行，但有时候也可能是访问了太多的列。&lt;/li>
&lt;li>确认MySQL服务器层是否在分析大量超过需要的数据行。&lt;/li>
&lt;/ol>
&lt;h2 id="621是否向数据库请求了不需要的数据">6.2.1　是否向数据库请求了不需要的数据&lt;a class="anchor" href="#621%e6%98%af%e5%90%a6%e5%90%91%e6%95%b0%e6%8d%ae%e5%ba%93%e8%af%b7%e6%b1%82%e4%ba%86%e4%b8%8d%e9%9c%80%e8%a6%81%e7%9a%84%e6%95%b0%e6%8d%ae">#&lt;/a>&lt;/h2>
&lt;p>有些查询会请求超过实际需要的数据，然后这些多余的数据会被应用程序丢弃。这会给MySQL服务器带来额外的负担，并增加网络开销(2)，另外也会消耗应用服务器的CPU和内存资源。&lt;/p>
&lt;p>这里有一些典型案例：&lt;/p>
&lt;p>查询不需要的记录&lt;/p>
&lt;p>一个常见的错误是常常会误以为MySQL会只返回需要的数据，实际上MySQL却是先返回全部结果集再进行计算。我们经常会看到一些了解其他数据库系统的人会设计出这类应用程序。这些开发者习惯使用这样的技术，先使用SELECT语句查询大量的结果，然后获取前面的N行后关闭结果集（例如在新闻网站中取出100条记录，但是只是在页面上显示前面10条）。他们认为MySQL会执行查询，并只返回他们需要的10条数据，然后停止查询。实际情况是MySQL会查询出全部的结果集，客户端的应用程序会接收全部的结果集数据，然后抛弃其中大部分数据。最简单有效的解决方法就是在这样的查询后面加上LIMIT。&lt;/p>
&lt;p>多表关联时返回全部列&lt;/p>
&lt;p>如果你想查询所有在电影&lt;em>Academy Dinosaur&lt;/em>中出现的演员，千万不要按下面的写法编写查询：&lt;/p>
&lt;pre>&lt;code> mysql&amp;gt; ** SELECT * FROM sakila.actor** 
 -&amp;gt; ** INNER JOIN sakila.film_actor USING(actor_id)** 
 -&amp;gt; ** INNER JOIN sakila.film USING(film_id)** 
 -&amp;gt; ** WHERE sakila.film.title = 'Academy Dinosaur';** 
&lt;/code>&lt;/pre>
&lt;p>这将返回这三个表的全部数据列。正确的方式应该是像下面这样只取需要的列：&lt;/p>
&lt;pre>&lt;code> mysql&amp;gt; ** SELECT sakila.actor.* FROM sakila.actor...;** 
&lt;/code>&lt;/pre>
&lt;p>总是取出全部列&lt;/p>
&lt;p>每次看到SELECT *的时候都需要用怀疑的眼光审视，是不是真的需要返回全部的列？很可能不是必需的。取出全部列，会让优化器无法完成索引覆盖扫描这类优化，还会为服务器带来额外的I/O、内存和CPU的消耗。因此，一些DBA是严格禁止SELECT *的写法的，这样做有时候还能避免某些列被修改带来的问题。&lt;/p>
&lt;p>当然，查询返回超过需要的数据也不总是坏事。在我们研究过的许多案例中，人们会告诉我们说这种有点浪费数据库资源的方式可以简化开发，因为能提高相同代码片段的复用性，如果清楚这样做的性能影响，那么这种做法也是值得考虑的。如果应用程序使用了某种缓存机制，或者有其他考虑，获取超过需要的数据也可能有其好处，但不要忘记这样做的代价是什么。获取并缓存所有的列的查询，相比多个独立的只获取部分列的查询可能就更有好处。&lt;/p>
&lt;p>重复查询相同的数据&lt;/p>
&lt;p>如果你不太小心，很容易出现这样的错误——不断地重复执行相同的查询，然后每次都返回完全相同的数据。例如，在用户评论的地方需要查询用户头像的URL，那么用户多次评论的时候，可能就会反复查询这个数据。比较好的方案是，当初次查询的时候将这个数据缓存起来，需要的时候从缓存中取出，这样性能显然会更好。&lt;/p></description></item><item><title>第5章创建高性能的索引</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC5%E7%AB%A0%E5%88%9B%E5%BB%BA%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84%E7%B4%A2%E5%BC%95/</link><pubDate>Sun, 05 Jan 2025 08:42:15 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC5%E7%AB%A0%E5%88%9B%E5%BB%BA%E9%AB%98%E6%80%A7%E8%83%BD%E7%9A%84%E7%B4%A2%E5%BC%95/</guid><description>&lt;p>第5章　创建高性能的索引&lt;/p>
&lt;p>索引（在MySQL中也叫做“键（key）”）是存储引擎用于快速找到记录的一种数据结构。这是索引的基本功能，除此之外，本章还将讨论索引其他一些方面有用的属性。&lt;/p>
&lt;p>索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时，索引对性能的影响愈发重要。在数据量较小且负载较低时，不恰当的索引对性能的影响可能还不明显，但当数据量逐渐增大时，性能则会急剧下降(1)。&lt;/p>
&lt;p>不过，索引却经常被忽略，有时候甚至被误解，所以在实际案例中经常会遇到由糟糕索引导致的问题。这也是我们把索引优化放在了靠前的章节，甚至比查询优化还靠前的原因。&lt;/p>
&lt;p>索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高几个数量级，“最优”的索引有时比一个“好的”索引性能要好两个数量级。创建一个真正“最优”的索引经常需要重写查询，所以，本章和下一章的关系非常紧密。&lt;/p>
&lt;h1 id="51索引基础">5.1　索引基础&lt;a class="anchor" href="#51%e7%b4%a2%e5%bc%95%e5%9f%ba%e7%a1%80">#&lt;/a>&lt;/h1>
&lt;p>要理解MySQL中索引是如何工作的，最简单的方法就是去看看一本书的“索引”部分：如果想在一本书中找到某个特定主题，一般会先看书的“索引”，找到对应的页码。&lt;/p>
&lt;p>在MySQL中，存储引擎用类似的方法使用索引，其先在索引中找到对应值，然后根据匹配的索引记录找到对应的数据行。假如要运行下面的查询：&lt;/p>
&lt;pre>&lt;code> mysql&amp;gt; ** SELECT first_name FROM sakila.actor WHERE actor_id=5;** 
&lt;/code>&lt;/pre>
&lt;p>如果在actor_id列上建有索引，则MySQL将使用该索引找到actor_id为5的行，也就是说，MySQL先在索引上按值进行查找，然后返回所有包含该值的数据行。&lt;/p>
&lt;p>索引可以包含一个或多个列的值。如果索引包含多个列，那么列的顺序也十分重要，因为MySQL只能高效地使用索引的最左前缀列。创建一个包含两个列的索引，和创建两个只包含一列的索引是大不相同的，下面将详细介绍。&lt;/p>
&lt;p>&lt;strong>如果使用的是ORM，是否还需要关心索引？&lt;/strong>&lt;/p>
&lt;p>简而言之：是的，仍然需要理解索引，即使是使用对象关系映射（ORM）工具。&lt;/p>
&lt;p>ORM工具能够生产符合逻辑的、合法的查询（多数时候），除非只是生成非常基本的查询（例如仅是根据主键查询），否则它很难生成适合索引的查询。无论是多么复杂的ORM工具，在精妙和复杂的索引面前都是“浮云”。读完本章后面的内容以后，你就会同意这个观点的!很多时候，即使是查询优化技术专家也很难兼顾到各种情况，更别说ORM了。&lt;/p>
&lt;h2 id="511索引的类型">5.1.1　索引的类型&lt;a class="anchor" href="#511%e7%b4%a2%e5%bc%95%e7%9a%84%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>索引有很多种类型，可以为不同的场景提供更好的性能。在MySQL中，索引是在存储引擎层而不是服务器层实现的。所以，并没有统一的索引标准：不同存储引擎的索引的工作方式并不一样，也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引，其底层的实现也可能不同。&lt;/p>
&lt;p>下面我们先来看看MySQL支持的索引类型，以及它们的优点和缺点。&lt;/p>
&lt;h3 id="b-tree索引">B-Tree索引&lt;a class="anchor" href="#b-tree%e7%b4%a2%e5%bc%95">#&lt;/a>&lt;/h3>
&lt;p>当人们谈论索引的时候，如果没有特别指明类型，那多半说的是B-Tree索引，它使用B-Tree数据结构来存储数据(2)。大多数MySQL引擎都支持这种索引。Archive引擎是一个例外：5.1之前Archive不支持任何索引，直到5.1才开始支持单个自增列（AUTO_INCREMENT）的索引。&lt;/p>
&lt;p>我们使用术语“B-Tree”，是因为MySQL在CREATE TABLE和其他语句中也使用该关键字。不过，底层的存储引擎也可能使用不同的存储结构，例如，NDB集群存储引擎内部实际上使用了T-Tree结构存储这种索引，即使其名字是BTREE；InnoDB则使用的是B+Tree，各种数据结构和算法的变种不在本书的讨论范围之内。&lt;/p>
&lt;p>存储引擎以不同的方式使用B-Tree索引，性能也各有不同，各有优劣。例如，MyISAM使用前缀压缩技术使得索引更小，但InnoDB则按照原数据格式进行存储。再如MyISAM索引通过数据的物理位置引用被索引的行，而InnoDB则根据主键引用被索引的行。&lt;/p>
&lt;p>B-Tree通常意味着所有的值都是按顺序存储的，并且每一个叶子页到根的距离相同。图5-1展示了B-Tree索引的抽象表示，大致反映了InnoDB索引是如何工作的。MyISAM使用的结构有所不同，但基本思想是类似的。&lt;/p>
&lt;p>&lt;img src="img/000001.jpeg" alt="" />
&lt;strong>图5-1：建立在B-Tree结构（从技术上来说是B+Tree）上的索引&lt;/strong>&lt;/p>
&lt;p>B-Tree索引能够加快访问数据的速度，因为存储引擎不再需要进行全表扫描来获取需要的数据，取而代之的是从索引的根节点（图示并未画出）开始进行搜索。根节点的槽中存放了指向子节点的指针，存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点，这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么是找到对应的值，要么该记录不存在。&lt;/p>
&lt;p>叶子节点比较特别，它们的指针指向的是被索引的数据，而不是其他的节点页（不同引擎的“指针”类型不同）。图5-1中仅绘制了一个节点和其对应的叶子节点，其实在根节点和叶子节点之间可能有很多层节点页。树的深度和表的大小直接相关。&lt;/p>
&lt;p>B-Tree对索引列是顺序组织存储的，所以很适合查找范围数据。例如，在一个基于文本域的索引树上，按字母顺序传递连续的值进行查找是非常合适的，所以像“找出所有以I到K开头的名字”这样的查找效率会非常高。&lt;/p>
&lt;p>假设有如下数据表：&lt;/p>
&lt;pre>&lt;code> CREATE TABLE People (
 last_name varchar(50) not null,
 first_name varchar(50) not null,
 dob date not null,
 gender enum('m', 'f') not null,
 key(last_name, first_name, dob)
 );
&lt;/code>&lt;/pre>
&lt;p>对于表中的每一行数据，索引中包含了last_name、frst_name和dob列的值，图5-2显示了该索引是如何组织数据的存储的。&lt;/p></description></item><item><title>第4章Schema与数据类型优化</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC4%E7%AB%A0Schema%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%BC%98%E5%8C%96/</link><pubDate>Sun, 05 Jan 2025 08:42:14 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC4%E7%AB%A0Schema%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E4%BC%98%E5%8C%96/</guid><description>&lt;p>第4章　Schema与数据类型优化&lt;/p>
&lt;p>良好的逻辑设计和物理设计是高性能的基石，应该根据系统将要执行的查询语句来设计schema，这往往需要权衡各种因素。例如，反范式的设计可以加快某些类型的查询，但同时可能使另一些类型的查询变慢。比如添加计数表和汇总表是一种很好的优化查询的方式，但这些表的维护成本可能会很高。MySQL独有的特性和实现细节对性能的影响也很大。&lt;/p>
&lt;p>本章和聚焦在索引优化的下一章，覆盖了MySQL特有的schema设计方面的主题。我们假设读者已经知道如何设计数据库，所以本章既不会介绍如何入门数据库设计，也不会讲解数据库设计方面的深入内容。这一章关注的是MySQL数据库的设计，主要介绍的是MySQL数据库设计与其他关系型数据库管理系统的区别。如果需要学习数据库设计方面的基础知识，建议阅读Clare Churcher的&lt;em>Beginning Database Design&lt;/em>（Apress出版社）一书。&lt;/p>
&lt;p>本章内容是为接下来的两个章节做铺垫。在这三章中，我们将讨论逻辑设计、物理设计和查询执行，以及它们之间的相互作用。这既需要关注全局，也需要专注细节。还需要理解整个系统以便弄清楚各个部分如何相互影响。如果在阅读完索引和查询优化章节后再回头来看这一章，也许会发现本章很有用，很多讨论的议题不能孤立地考虑。&lt;/p>
&lt;h1 id="41选择优化的数据类型">4.1　选择优化的数据类型&lt;a class="anchor" href="#41%e9%80%89%e6%8b%a9%e4%bc%98%e5%8c%96%e7%9a%84%e6%95%b0%e6%8d%ae%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h1>
&lt;p>MySQL支持的数据类型非常多，选择正确的数据类型对于获得高性能至关重要。不管存储哪种类型的数据，下面几个简单的原则都有助于做出更好的选择。&lt;/p>
&lt;p>更小的通常更好。&lt;/p>
&lt;p>一般情况下，应该尽量使用可以正确存储数据的最小数据类型(1)。更小的数据类型通常更快，因为它们占用更少的磁盘、内存和CPU缓存，并且处理时需要的CPU周期也更少。&lt;/p>
&lt;p>但是要确保没有低估需要存储的值的范围，因为在schema中的多个地方增加数据类型的范围是一个非常耗时和痛苦的操作。如果无法确定哪个数据类型是最好的，就选择你认为不会超过范围的最小类型。（如果系统不是很忙或者存储的数据量不多，或者是在可以轻易修改设计的早期阶段，那之后修改数据类型也比较容易）。&lt;/p>
&lt;p>简单就好&lt;/p>
&lt;p>简单数据类型的操作通常需要更少的CPU周期。例如，整型比字符操作代价更低，因为字符集和校对规则（排序规则）使字符比较比整型比较更复杂。这里有两个例子：一个是应该使用MySQL内建的类型(2)而不是字符串来存储日期和时间，另外一个是应该用整型存储IP地址。稍后我们将专门讨论这个话题。&lt;/p>
&lt;p>尽量避免NULL&lt;/p>
&lt;p>很多表都包含可为NULL（空值）的列，即使应用程序并不需要保存NULL也是如此，这是因为可为NULL是列的默认属性(3)。通常情况下最好指定列为NOT NULL，除非真的需要存储NULL值。&lt;/p>
&lt;p>如果查询中包含可为NULL的列，对MySQL来说更难优化，因为可为NULL的列使得索引、索引统计和值比较都更复杂。可为NULL的列会使用更多的存储空间，在MySQL里也需要特殊处理。当可为NULL的列被索引时，每个索引记录需要一个额外的字节，在MyISAM里甚至还可能导致固定大小的索引（例如只有一个整数列的索引）变成可变大小的索引。&lt;/p>
&lt;p>通常把可为NULL的列改为NOT NULL带来的性能提升比较小，所以（调优时）没有必要首先在现有schema中查找并修改掉这种情况，除非确定这会导致问题。但是，如果计划在列上建索引，就应该尽量避免设计成可为NULL的列。&lt;/p>
&lt;p>当然也有例外，例如值得一提的是，InnoDB使用单独的位（bit）存储NULL值，所以对于稀疏数据(4)有很好的空间效率。但这一点不适用于MyISAM。&lt;/p>
&lt;p>在为列选择数据类型时，第一步需要确定合适的大类型：数字、字符串、时间等。这通常是很简单的，但是我们会提到一些特殊的不是那么直观的案例。&lt;/p>
&lt;p>下一步是选择具体类型。很多MySQL的数据类型可以存储相同类型的数据，只是存储的长度和范围不一样、允许的精度不同，或者需要的物理空间（磁盘和内存空间）不同。相同大类型的不同子类型数据有时也有一些特殊的行为和属性。&lt;/p>
&lt;p>例如，DATETIME和TIMESAMP列都可以存储相同类型的数据：时间和日期，精确到秒。&lt;/p>
&lt;p>然而TIMESTAMP只使用DATETIME一半的存储空间，并且会根据时区变化，具有特殊的自动更新能力。另一方面，TIMESTAMP允许的时间范围要小得多，有时候它的特殊能力会成为障碍。&lt;/p>
&lt;p>本章只讨论基本的数据类型。MySQL为了兼容性支持很多别名，例如INTEGER、BOOL，以及NUMERIC。它们都只是别名。这些别名可能令人不解，但不会影响性能。如果建表时采用数据类型的别名，然后用SHOW CREATE TABLE检查，会发现MySQL报告的是基本类型，而不是别名。&lt;/p>
&lt;h2 id="411整数类型">4.1.1　整数类型&lt;a class="anchor" href="#411%e6%95%b4%e6%95%b0%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>有两种类型的数字：整数（whole number）和实数（real number）。如果存储整数，可以使用这几种整数类型：TINYINT，SMALLINT，MEDIUMINT，INT，BIGINT。分别使用8，16，24，32，64位存储空间。它们可以存储的值的范围从−2（N−1）到2（N−1）−1，其中N是存储空间的位数。&lt;/p>
&lt;p>整数类型有可选的UNSIGNED属性，表示不允许负值，这大致可以使正数的上限提高一倍。例如TINYINT UNSIGNED可以存储的范围是0～255，而TINYINT的存储范围是−128～127。&lt;/p>
&lt;p>有符号和无符号类型使用相同的存储空间，并具有相同的性能，因此可以根据实际情况选择合适的类型。&lt;/p>
&lt;p>你的选择决定MySQL是怎么在内存和磁盘中保存数据的。然而，整数计算一般使用64位的BIGINT整数，即使在32位环境也是如此。（一些聚合函数是例外，它们使用DECIMAL或DOUBLE进行计算）。&lt;/p>
&lt;p>MySQL可以为整数类型指定宽度，例如INT（11），对大多数应用这是没有意义的：它不会限制值的合法范围，只是规定了MySQL的一些交互工具（例如MySQL命令行客户端）用来显示字符的个数。对于存储和计算来说，INT（1）和INT（20）是相同的。&lt;/p>
&lt;p>&lt;img src="img/000000.jpeg" alt="" />一些第三方存储引擎，比如Infobright，有时也有自定义的存储格式和压缩方案，并不一定使用常见的MySQL内置引擎的方式。&lt;/p>
&lt;h2 id="412实数类型">4.1.2　实数类型&lt;a class="anchor" href="#412%e5%ae%9e%e6%95%b0%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>实数是带有小数部分的数字。然而，它们不只是为了存储小数部分；也可以使用DECIMAL存储比BIGINT还大的整数。MySQL既支持精确类型，也支持不精确类型。&lt;/p>
&lt;p>FLOAT和DOUBLE类型支持使用标准的浮点运算进行近似计算。如果需要知道浮点运算是怎么计算的，则需要研究所使用的平台的浮点数的具体实现。&lt;/p>
&lt;p>DECIMAL类型用于存储精确的小数。在MySQL 5.0和更高版本，DECIMAL类型支持精确计算。MySQL 4.1以及更早版本则使用浮点运算来实现DECIAML的计算，这样做会因为精度损失导致一些奇怪的结果。在这些版本的MySQL中，DECIMAL只是一个“存储类型”。&lt;/p>
&lt;p>因为CPU不支持对DECIMAL的直接计算，所以在MySQL 5.0以及更高版本中，MySQL服务器自身实现了DECIMAL的高精度计算。相对而言，CPU直接支持原生浮点计算，所以浮点运算明显更快。&lt;/p>
&lt;p>浮点和DECIMAL类型都可以指定精度。对于DECIMAL列，可以指定小数点前后所允许的最大位数。这会影响列的空间消耗。MySQL 5.0和更高版本将数字打包保存到一个二进制字符串中（每4个字节存9个数字）。例如，DECIMAL（18,9）小数点两边将各存储9个数字，一共使用9个字节：小数点前的数字用4个字节，小数点后的数字用4个字节，小数点本身占1个字节。&lt;/p>
&lt;p>MySQL 5.0和更高版本中的DECIMAL类型允许最多65个数字。而早期的MySQL版本中这个限制是254个数字，并且保存为未压缩的字符串（每个数字一个字节）。然而，这些（早期）版本实际上并不能在计算中使用这么大的数字，因为DECIMAL只是一种存储格式；在计算中DECIMAL会转换为DOUBLE类型。&lt;/p>
&lt;p>有多种方法可以指定浮点列所需要的精度，这会使得MySQL悄悄选择不同的数据类型，或者在存储时对值进行取舍。这些精度定义是非标准的，所以我们建议只指定数据类型，不指定精度。&lt;/p>
&lt;p>浮点类型在存储同样范围的值时，通常比DECIMAL使用更少的空间。FLOAT使用4个字节存储。DOUBLE占用8个字节，相比FLOAT有更高的精度和更大的范围。和整数类型一样，能选择的只是存储类型；MySQL使用DOUBLE作为内部浮点计算的类型。&lt;/p>
&lt;p>因为需要额外的空间和计算开销，所以应该尽量只在对小数进行精确计算时才使用DECIMAL——例如存储财务数据。但在数据量比较大的时候，可以考虑使用BIGINT代替DECIMAL，将需要存储的货币单位根据小数的位数乘以相应的倍数即可。假设要存储财务数据精确到万分之一分，则可以把所有金额乘以一百万，然后将结果存储在BIGINT里，这样可以同时避免浮点存储计算不精确和DECIMAL精确计算代价高的问题。&lt;/p>
&lt;h2 id="413字符串类型">4.1.3　字符串类型&lt;a class="anchor" href="#413%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h2>
&lt;p>MySQL支持多种字符串类型，每种类型还有很多变种。这些数据类型在4.1和5.0版本发生了很大的变化，使得情况更加复杂。从MySQL 4.1开始，每个字符串列可以定义自己的字符集和排序规则，或者说校对规则（collation）（更多关于这个主题的信息请参考第7章）。这些东西会很大程度上影响性能。&lt;/p>
&lt;h3 id="varchar和char类型">&lt;strong>VARCHAR&lt;/strong>和CHAR类型&lt;a class="anchor" href="#varchar%e5%92%8cchar%e7%b1%bb%e5%9e%8b">#&lt;/a>&lt;/h3>
&lt;p>VARCHAR和CHAR是两种最主要的字符串类型。不幸的是，很难精确地解释这些值是怎么存储在磁盘和内存中的，因为这跟存储引擎的具体实现有关。下面的描述假设使用的存储引擎是InnoDB和/或者MyISAM。如果使用的不是这两种存储引擎，请参考所使用的存储引擎的文档。&lt;/p>
&lt;p>先看看VARCHAR和CHAR值通常在磁盘上怎么存储。请注意，存储引擎存储CHAR或者VARCHAR值的方式在内存中和在磁盘上可能不一样，所以MySQL服务器从存储引擎读出的值可能需要转换为另一种存储格式。下面是关于两种类型的一些比较。&lt;/p>
&lt;p>VARCHAR&lt;/p>
&lt;p>VARCHAR类型用于存储可变长字符串，是最常见的字符串数据类型。它比定长类型更节省空间，因为它仅使用必要的空间（例如，越短的字符串使用越少的空间）。有一种情况例外，如果MySQL表使用ROW_FORMAT=FIXED创建的话，每一行都会使用定长存储，这会很浪费空间。&lt;/p>
&lt;p>VARCHAR需要使用1或2个额外字节记录字符串的长度：如果列的最大长度小于或等于255字节，则只使用1个字节表示，否则使用2个字节。假设采用latin1字符集，一个VARCHAR（10）的列需要11个字节的存储空间。VARCHAR（1000）的列则需要1002个字节，因为需要2个字节存储长度信息。&lt;/p>
&lt;p>VARCHAR节省了存储空间，所以对性能也有帮助。但是，由于行是变长的，在UPDATE时可能使行变得比原来更长，这就导致需要做额外的工作。如果一个行占用的空间增长，并且在页内没有更多的空间可以存储，在这种情况下，不同的存储引擎的处理方式是不一样的。例如，MyISAM会将行拆成不同的片段存储，InnoDB则需要分裂页来使行可以放进页内。其他一些存储引擎也许从不在原数据位置更新数据。&lt;/p>
&lt;p>下面这些情况下使用VARCHAR是合适的：字符串列的最大长度比平均长度大很多；列的更新很少，所以碎片不是问题；使用了像UTF-8这样复杂的字符集，每个字符都使用不同的字节数进行存储。&lt;/p></description></item><item><title>第3章服务器性能剖析</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC3%E7%AB%A0%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%80%A7%E8%83%BD%E5%89%96%E6%9E%90/</link><pubDate>Sun, 05 Jan 2025 08:42:13 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC3%E7%AB%A0%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%80%A7%E8%83%BD%E5%89%96%E6%9E%90/</guid><description>&lt;p>第3章　服务器性能剖析&lt;/p>
&lt;p>在我们的技术咨询生涯中，最常碰到的三个性能相关的服务请求是：如何确认服务器是否达到了性能最佳的状态、找出某条语句为什么执行不够快，以及诊断被用户描述成“停顿”、“堆积”或者“卡死”的某些间歇性疑难故障。本章将主要针对这三个问题做出解答。我们将提供一些工具和技巧来优化整机的性能、优化单条语句的执行速度，以及诊断或者解决那些很难观察到的问题（这些问题用户往往很难知道其根源，有时候甚至都很难察觉到它的存在）。&lt;/p>
&lt;p>这看起来是个艰巨的任务，但是事实证明，有一个简单的方法能够从噪声中发现苗头。这个方法就是专注于测量服务器的时间花费在哪里，使用的技术则是性能剖析（profiling）。在本章，我们将展示如何测量系统并生成剖析报告，以及如何分析系统的整个堆栈（stack），包括从应用程序到数据库服务器到单个查询。&lt;/p>
&lt;p>首先我们要保持空杯精神，抛弃掉一些关于性能的常见的误解。这有一定的难度，下面我们一起通过一些例子来说明问题在哪里。&lt;/p>
&lt;h1 id="31性能优化简介">3.1　性能优化简介&lt;a class="anchor" href="#31%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96%e7%ae%80%e4%bb%8b">#&lt;/a>&lt;/h1>
&lt;p>问10个人关于性能的问题，可能会得到10个不同的回答，比如“每秒查询次数”、“CPU利用率”、“可扩展性”之类。这其实也没有问题，每个人在不同场景下对性能有不同的理解，但本章将给性能一个正式的定义。我们将性能定义为完成某件任务所需要的时间度量，换句话说，性能即响应时间，这是一个非常重要的原则。我们通过任务和时间而不是资源来测量性能。数据库服务器的目的是执行SQL语句，所以它关注的任务是查询或者语句，如SELECT、UPDATE、DELETE等(1)。数据库服务器的性能用查询的响应时间来度量，单位是每个查询花费的时间。&lt;/p>
&lt;p>还有另外一个问题：什么是优化？我们暂时不讨论这个问题，而是假设性能优化就是在一定的工作负载下尽可能地(2)降低响应时间。&lt;/p>
&lt;p>很多人对此很迷茫。假如你认为性能优化是降低CPU利用率，那么可以减少对资源的使用。但这是一个陷阱，资源是用来消耗并用来工作的，所以有时候消耗更多的资源能够加快查询速度。很多时候将使用老版本InnoDB引擎的MySQL升级到新版本后，CPU利用率会上升得很厉害，这并不代表性能出现了问题，反而说明新版本的InnoDB对资源的利用率上升了。查询的响应时间则更能体现升级后的性能是不是变得更好。版本升级有时候会带来一些bug，比如不能利用某些索引从而导致CPU利用率上升。CPU利用率只是一种现象，而不是很好的可度量的目标。&lt;/p>
&lt;p>同样，如果把性能优化仅仅看成是提升每秒查询量，这其实只是吞吐量优化。吞吐量的提升可以看作性能优化的副产品(3)。对查询的优化可以让服务器每秒执行更多的查询，因为每条查询执行的时间更短了（吞吐量的定义是单位时间内的查询数量，这正好是我们对性能的定义的倒数）。&lt;/p>
&lt;p>所以如果目标是降低响应时间，那么就需要理解为什么服务器执行查询需要这么多时间，然后去减少或者消除那些对获得查询结果来说不必要的工作。也就是说，先要搞清楚时间花在哪里。这就引申出优化的第二个原则：无法测量就无法有效地优化。所以第一步应该测量时间花在什么地方。&lt;/p>
&lt;p>我们观察到，很多人在优化时，都将精力放在修改一些东西上，却很少去进行精确的测量。我们的做法完全相反，将花费非常多，甚至90％的时间来测量响应时间花在哪里。如果通过测量没有找到答案，那要么是测量的方式错了，要么是测量得不够完整。如果测量了系统中完整而且正确的数据，性能问题一般都能暴露出来，对症下药的解决方案也就比较明了。测量是一项很有挑战性的工作，并且分析结果也同样有挑战性，测出时间花在哪里，和知道为什么花在那里，是两码事。&lt;/p>
&lt;p>前面提到需要合适的测量范围，这是什么意思呢？合适的测量范围是说只测量需要优化的活动。有两种比较常见的情况会导致不合适的测量：&lt;/p>
&lt;ul>
&lt;li>在错误的时间启动和停止测量。&lt;/li>
&lt;li>测量的是聚合后的信息，而不是目标活动本身。&lt;/li>
&lt;/ul>
&lt;p>例如，一个常见的错误是先查看慢查询，然后又去排查整个服务器的情况来判断问题在哪里。如果确认有慢查询，那么就应该测量慢查询，而不是测量整个服务器。测量的应该是从慢查询的开始到结束的时间，而不是查询之前或查询之后的时间。&lt;/p>
&lt;p>完成一项任务所需要的时间可以分成两部分：执行时间和等待时间。如果要优化任务的执行时间，最好的办法是通过测量定位不同的子任务花费的时间，然后优化去掉一些子任务、降低子任务的执行频率或者提升子任务的效率。而优化任务的等待时间则相对要复杂一些，因为等待有可能是由其他系统间接影响导致，任务之间也可能由于争用磁盘或者CPU资源而相互影响。根据时间是花在执行还是等待上的不同，诊断也需要不同的工具和技术。&lt;/p>
&lt;p>刚才说到需要定位和优化子任务，但只是一笔带过。一些运行不频繁或者很短的子任务对整体响应时间的影响很小，通常可以忽略不计。那么如何确认哪些子任务是优化的目标呢？这个时候性能剖析就可以派上用场了。&lt;/p>
&lt;p>&lt;strong>如何判断测量是正确的？&lt;/strong>&lt;/p>
&lt;p>如果测量是如此重要，那么测量错了会有什么后果？实际上，测量经常都是错误的。对数量的测量并不等于数量本身。测量的错误可能很小，跟实际情况区别不大，但错的终归是错的。所以这个问题其实应该是：“测量到底有多么不准确？”这个问题在其他一些书中有详细的讨论，但不是本书的主题。但是要意识到使用的是测量数据，而不是其所代表的实际数据。通常来说，测量的结果也可能有多种模糊的表现，这可能导致推断出错误的结论。&lt;/p>
&lt;h2 id="311通过性能剖析进行优化">3.1.1　通过性能剖析进行优化&lt;a class="anchor" href="#311%e9%80%9a%e8%bf%87%e6%80%a7%e8%83%bd%e5%89%96%e6%9e%90%e8%bf%9b%e8%a1%8c%e4%bc%98%e5%8c%96">#&lt;/a>&lt;/h2>
&lt;p>一旦掌握并实践面向响应时间的优化方法，就会发现需要不断地对系统进行性能剖析（profiling）。&lt;/p>
&lt;p>性能剖析是测量和分析时间花费在哪里的主要方法。性能剖析一般有两个步骤：测量任务所花费的时间；然后对结果进行统计和排序，将重要的任务排到前面。&lt;/p>
&lt;p>性能剖析工具的工作方式基本相同。在任务开始时启动计时器，在任务结束时停止计时器，然后用结束时间减去启动时间得到响应时间。也有些工具会记录任务的父任务。这些结果数据可以用来绘制调用关系图，但对于我们的目标来说更重要的是，可以将相似的任务分组并进行汇总。对相似的任务分组并进行汇总可以帮助对那些分到一组的任务做更复杂的统计分析，但至少需要知道每一组有多少任务，并计算出总的响应时间。通过性能剖析报告（&lt;em>profile report&lt;/em>）可以获得需要的结果。性能剖析报告会列出所有任务列表。每行记录一个任务，包括任务名、任务的执行时间、任务的消耗时间、任务的平均执行时间，以及该任务执行时间占全部时间的百分比。性能剖析报告会按照任务的消耗时间进行降序排序。&lt;/p>
&lt;p>为了更好地说明，这里举一个对整个数据库服务器工作负载的性能剖析的例子，主要输出的是各种类型的查询和执行查询的时间。这是从整体的角度来分析响应时间，后面会演示其他角度的分析结果。下面的输出是用Percona Toolkit中的&lt;em>pt-query-digest&lt;/em>（实际上就是著名的Maatkit工具中的&lt;em>mk-query-digest&lt;/em>）分析得到的结果。为了显示方便，对结果做了一些微调，并且只截取了前面几行结果：&lt;/p>
&lt;pre>&lt;code> Rank Response time Calls R/Call Item
 ==== ================ ===== ====== =======
 1 11256.3618 68.1% 78069 0.1442 SELECT InvitesNew
 2 2029.4730 12.3% 14415 0.1408 SELECT StatusUpdate
 3 1345.3445 8.1% 3520 0.3822 SHOW STATUS
&lt;/code>&lt;/pre>
&lt;p>上面只是性能剖析结果的前几行，根据总响应时间进行排名，只包括剖析所需要的最小列组合。每一行都包括了查询的响应时间和占总时间的百分比、查询的执行次数、单次执行的平均响应时间，以及该查询的摘要。通过这个性能剖析可以很清楚地看到每个查询相互之间的成本比较，以及每个查询占总成本的比较。在这个例子中，任务指的就是查询，实际上在分析MySQL的时候经常都指的是查询。&lt;/p>
&lt;p>我们将实际地讨论两种类型的性能剖析：基于执行时间的分析和基于等待的分析。基于执行时间的分析研究的是什么任务的执行时间最长，而基于等待的分析则是判断任务在什么地方被阻塞的时间最长。&lt;/p>
&lt;p>如果任务执行时间长是因为消耗了太多的资源且大部分时间花费在执行上，等待的时间不多，这种情况下基于等待的分析作用就不大。反之亦然，如果任务一直在等待，没有消耗什么资源，去分析执行时间就不会有什么结果。如果不能确认问题是出在执行还是等待上，那么两种方式都需要试试。后面会给出详细的例子。&lt;/p>
&lt;p>事实上，当基于执行时间的分析发现一个任务需要花费太多时间的时候，应该深入去分析一下，可能会发现某些“执行时间”实际上是在等待。例如，上面简单的性能剖析的输出显示表InvitesNew上的SELECT查询花费了大量时间，如果深入研究，则可能发现时间都花费在等待I/O完成上。&lt;/p>
&lt;p>在对系统进行性能剖析前，必须先要能够进行测量，这需要系统可测量化的支持。可测量的系统一般会有多个测量点可以捕获并收集数据，但实际系统很少可以做到可测量化。大部分系统都没有多少可测量点，即使有也只提供一些活动的计数，而没有活动花费的时间统计。MySQL就是一个典型的例子，直到版本5.5才第一次提供了Performance Schema，其中有一些基于时间的测量点(4)，而版本5.1及之前的版本没有任何基于时间的测量点。能够从MySQL收集到的服务器操作的数据大多是show status计数器的形式，这些计数器统计的是某种活动发生的次数。这也是我们最终决定创建Percona Server的主要原因，Percona Server从版本5.0开始提供很多更详细的查询级别的测量点。&lt;/p></description></item><item><title>第2章MySQL基准测试</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC2%E7%AB%A0MySQL%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/</link><pubDate>Sun, 05 Jan 2025 08:42:12 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC2%E7%AB%A0MySQL%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95/</guid><description>&lt;p>第2章　MySQL基准测试&lt;/p>
&lt;p>基准测试（benchmark）是MySQL新手和专家都需要掌握的一项基本技能。简单地说，基准测试是针对系统设计的一种压力测试。通常的目标是为了掌握系统的行为。但也有其他原因，如重现某个系统状态，或者是做新硬件的可靠性测试。本章将讨论MySQL和基于MySQL的应用的基准测试的重要性、策略和工具。我们将特别讨论一下sysbench，这是一款非常优秀的MySQL基准测试工具。&lt;/p>
&lt;h1 id="21为什么需要基准测试">2.1　为什么需要基准测试&lt;a class="anchor" href="#21%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e5%9f%ba%e5%87%86%e6%b5%8b%e8%af%95">#&lt;/a>&lt;/h1>
&lt;p>为什么基准测试很重要？因为基准测试是唯一方便有效的、可以学习系统在给定的工作负载下会发生什么的方法。基准测试可以观察系统在不同压力下的行为，评估系统的容量，掌握哪些是重要的变化，或者观察系统如何处理不同的数据。基准测试可以在系统实际负载之外创造一些虚构场景进行测试。基准测试可以完成以下工作，或者更多：&lt;/p>
&lt;ul>
&lt;li>验证基于系统的一些假设，确认这些假设是否符合实际情况。&lt;/li>
&lt;li>重现系统中的某些异常行为，以解决这些异常。&lt;/li>
&lt;li>测试系统当前的运行情况。如果不清楚系统当前的性能，就无法确认某些优化的效果如何。也可以利用历史的基准测试结果来分析诊断一些无法预测的问题。&lt;/li>
&lt;li>模拟比当前系统更高的负载，以找出系统随着压力增加而可能遇到的扩展性瓶颈。&lt;/li>
&lt;li>规划未来的业务增长。基准测试可以评估在项目未来的负载下，需要什么样的硬件，需要多大容量的网络，以及其他相关资源。这有助于降低系统升级和重大变更的风险。&lt;/li>
&lt;li>测试应用适应可变环境的能力。例如，通过基准测试，可以发现系统在随机的并发峰值下的性能表现，或者是不同配置的服务器之间的性能表现。基准测试也可以测试系统对不同数据分布的处理能力。&lt;/li>
&lt;li>测试不同的硬件、软件和操作系统配置。比如RAID 5还是RAID 10更适合当前的系统？如果系统从ATA硬盘升级到SAN存储，对于随机写性能有什么帮助？Linux 2.4系列的内核会比2.6系列的可扩展性更好吗？升级MySQL的版本能改善性能吗？为当前的数据采用不同的存储引擎会有什么效果？所有这类问题都可以通过专门的基准测试来获得答案。&lt;/li>
&lt;li>证明新采购的设备是否配置正确。笔者曾经无数次地通过基准测试来对新系统进行压测，发现了很多错误的配置，以及硬件组件的失效等问题。因此在新系统正式上线到生产环境之前进行基准测试是一个好习惯，永远不要相信主机提供商或者硬件供应商的所谓系统已经安装好，并且能运行多快的说法。如果可能，执行实际的基准测试永远是一个好主意。&lt;/li>
&lt;/ul>
&lt;p>基准测试还可以用于其他目的，比如为应用创建单元测试套件。但本章我们只关注与性能有关的基准测试。&lt;/p>
&lt;p>基准测试的一个主要问题在于其不是真实压力的测试。基准测试施加给系统的压力相对真实压力来说，通常比较简单。真实压力是不可预期而且变化多端的，有时候情况会过于复杂而难以解释。所以使用真实压力测试，可能难以从结果中分析出确切的结论。&lt;/p>
&lt;p>基准测试的压力和真实压力在哪些方面不同？有很多因素会影响基准测试，比如数据量、数据和查询的分布，但最重要的一点还是基准测试通常要求尽可能快地执行完成，所以经常给系统造成过大的压力。在很多案例中，我们都会调整给测试工具的最大压力，以在系统可以容忍的压力阈值内尽可能快地执行测试，这对于确定系统的最大容量非常有帮助。然而大部分压力测试工具不支持对压力进行复杂的控制。务必要记住，测试工具自身的局限也会影响到结果的有效性。&lt;/p>
&lt;p>使用基准测试进行容量规划也要掌握技巧，不能只根据测试结果做简单的推断。例如，假设想知道使用新数据库服务器后，系统能够支撑多大的业务增长。首先对原系统进行基准测试，然后对新系统做测试，结果发现新系统可以支持原系统40倍的TPS（每秒事务数），这时候就不能简单地推断说新系统一定可以支持40倍的业务增长。这是因为在业务增长的同时，系统的流量、用户、数据以及不同数据之间的交互都在增长，它们不可能都有40倍的支撑能力，尤其是相互之间的关系。而且当业务增长到40倍时，应用本身的设计也可能已经随之改变。可能有更多的新特性会上线，其中某些特性可能对数据库造成的压力远大于原有功能。而这些压力、数据、关系和特性的变化都很难模拟，所以它们对系统的影响也很难评估。&lt;/p>
&lt;p>结论就是，我们只能进行大概的测试，来确定系统大致的余量有多少。当然也可以做一些真实压力测试（和基准测试有区别），但在构造数据集和压力的时候要特别小心，而且这样就不再是基准测试了。基准测试要尽量简单直接，结果之间容易相互比较，成本低且易于执行。尽管有诸多限制，基准测试还是非常有用的（只要搞清楚测试的原理，并且了解如何分析结果所代表的意义）。&lt;/p>
&lt;h1 id="22基准测试的策略">2.2　基准测试的策略&lt;a class="anchor" href="#22%e5%9f%ba%e5%87%86%e6%b5%8b%e8%af%95%e7%9a%84%e7%ad%96%e7%95%a5">#&lt;/a>&lt;/h1>
&lt;p>基准测试有两种主要的策略：一是针对整个系统的整体测试，另外是单独测试MySQL。这两种策略也被称为集成式（full-stack）以及单组件式（single-component）基准测试。针对整个系统做集成式测试，而不是单独测试MySQL的原因主要有以下几点：&lt;/p>
&lt;ul>
&lt;li>测试整个应用系统，包括Web服务器、应用代码、网络和数据库是非常有用的，因为用户关注的并不仅仅是MySQL本身的性能，而是应用整体的性能。&lt;/li>
&lt;li>MySQL并非总是应用的瓶颈，通过整体的测试可以揭示这一点。&lt;/li>
&lt;li>只有对应用做整体测试，才能发现各部分之间的缓存带来的影响。&lt;/li>
&lt;li>整体应用的集成式测试更能揭示应用的真实表现，而单独组件的测试很难做到这一点。&lt;/li>
&lt;/ul>
&lt;p>另外一方面，应用的整体基准测试很难建立，甚至很难正确设置。如果基准测试的设计有问题，那么结果就无法反映真实的情况，从而基于此做的决策也就可能是错误的。&lt;/p>
&lt;p>不过，有时候不需要了解整个应用的情况，而只需要关注MySQL的性能，至少在项目初期可以这样做。基于以下情况，可以选择只测试MySQL：&lt;/p>
&lt;ul>
&lt;li>需要比较不同的schema或查询的性能。&lt;/li>
&lt;li>针对应用中某个具体问题的测试。&lt;/li>
&lt;li>为了避免漫长的基准测试，可以通过一个短期的基准测试，做快速的“周期循环”，来检测出某些调整后的效果。&lt;/li>
&lt;/ul>
&lt;p>另外，如果能够在真实的数据集上执行重复的查询，那么针对MySQL的基准测试也是有用的，但是数据本身和数据集的大小都应该是真实的。如果可能，可以采用生产环境的数据快照。&lt;/p>
&lt;p>不幸的是，设置一个基于真实数据的基准测试复杂而且耗时。如果能得到一份生产数据集的拷贝，当然很幸运，但这通常不太可能。比如要测试的是一个刚开发的新应用，它只有很少的用户和数据。如果想测试该应用在规模扩张到很大以后的性能表现，就只能通过模拟大量的数据和压力来进行。&lt;/p>
&lt;h2 id="221测试何种指标">2.2.1　测试何种指标&lt;a class="anchor" href="#221%e6%b5%8b%e8%af%95%e4%bd%95%e7%a7%8d%e6%8c%87%e6%a0%87">#&lt;/a>&lt;/h2>
&lt;p>在开始执行甚至是在设计基准测试之前，需要先明确测试的目标。测试目标决定了选择什么样的测试工具和技术，以获得精确而有意义的测试结果。可以将测试目标细化为一系列的问题，比如，“这种CPU是否比另外一种要快？”，或“新索引是否比当前索引性能更好？”&lt;/p>
&lt;p>有时候需要用不同的方法测试不同的指标。比如，针对延迟（latency）和吞吐量（throughput）就需要采用不同的测试方法。&lt;/p>
&lt;p>请考虑以下指标，看看如何满足测试的需求。&lt;/p>
&lt;p>吞吐量&lt;/p>
&lt;p>吞吐量指的是单位时间内的事务处理数。这一直是经典的数据库应用测试指标。一些标准的基准测试被广泛地引用，如TPC-C（参考&lt;em>
&lt;a target="_blank" href="http://www.tpc.org">http://www.tpc.org&lt;/a>&lt;/em>），而且很多数据库厂商都努力争取在这些测试中取得好成绩。这类基准测试主要针对在线事务处理（OLTP）的吞吐量，非常适用于多用户的交互式应用。常用的测试单位是每秒事务数（TPS），有些也采用每分钟事务数（TPM）。&lt;/p>
&lt;p>响应时间或者延迟&lt;/p>
&lt;p>这个指标用于测试任务所需的整体时间。根据具体的应用，测试的时间单位可能是微秒、毫秒、秒或者分钟。根据不同的时间单位可以计算出平均响应时间、最小响应时间、最大响应时间和所占百分比。最大响应时间通常意义不大，因为测试时间越长，最大响应时间也可能越大。而且其结果通常不可重复，每次测试都可能得到不同的最大响应时间。因此，通常可以使用百分比响应时间（percentile response time）来替代最大响应时间。例如，如果95％的响应时间都是5毫秒，则表示任务在95％的时间段内都可以在5毫秒之内完成。&lt;/p>
&lt;p>使用图表有助于理解测试结果。可以将测试结果绘制成折线图（比如平均值折线或者95％百分比折线）或者散点图，直观地表现数据结果集的分布情况。通过这些图可以发现长时间测试的趋势。本章后面将更详细地讨论这一点。&lt;/p>
&lt;p>并发性&lt;/p>
&lt;p>并发性是一个非常重要又经常被误解和误用的指标。例如，它经常被表示成多少用户在同一时间浏览一个Web站点，经常使用的指标是有多少个会话(1)。然而，HTTP协议是无状态的，大多数用户只是简单地读取浏览器上显示的信息，这并不等同于Web服务器的并发性。而且，Web服务器的并发性也不等同于数据库的并发性，而仅仅只表示会话存储机制可以处理多少数据的能力。Web服务器的并发性更准确的度量指标，应该是在任意时间有多少同时发生的并发请求。&lt;/p>
&lt;p>在应用的不同环节都可以测量相应的并发性。Web服务器的高并发，一般也会导致数据库的高并发，但服务器采用的语言和工具集对此都会有影响。注意不要将创建数据库连接和并发性搞混淆。一个设计良好的应用，同时可以打开成百上千个MySQL数据库服务器连接，但可能同时只有少数连接在执行查询。所以说，一个Web站点“同时有50000个用户”访问，却可能只有10～15个并发请求到MySQL数据库。&lt;/p>
&lt;p>换句话说，并发性基准测试需要关注的是正在工作中的并发操作，或者是同时工作中的线程数或者连接数。当并发性增加时，需要测量吞吐量是否下降，响应时间是否变长，如果是这样，应用可能就无法处理峰值压力。&lt;/p>
&lt;p>并发性的测量完全不同于响应时间和吞吐量。它不像是一个结果，而更像是设置基准测试的一种属性。并发性测试通常不是为了测试应用能达到的并发度，而是为了测试应用在不同并发下的性能。当然，数据库的并发性还是需要测量的。可以通过&lt;em>sysbench&lt;/em>指定32、64或者128个线程的测试，然后在测试期间记录MySQL数据库的Threads_running状态值。在第11章将讨论这个指标对容量规划的影响。&lt;/p>
&lt;p>可扩展性&lt;/p>
&lt;p>在系统的业务压力可能发生变化的情况下，测试可扩展性就非常必要了。第11章将更进一步讨论可扩展性的话题。简单地说，可扩展性指的是，给系统增加一倍的工作，在理想情况下就能获得两倍的结果（即吞吐量增加一倍）。或者说，给系统增加一倍的资源（比如两倍的CPU数），就可以获得两倍的吞吐量。当然，同时性能（响应时间）也必须在可以接受的范围内。大多数系统是无法做到如此理想的线性扩展的。随着压力的变化，吞吐量和性能都可能越来越差。&lt;/p>
&lt;p>可扩展性指标对于容量规范非常有用，它可以提供其他测试无法提供的信息，来帮助发现应用的瓶颈。比如，如果系统是基于单个用户的响应时间测试（这是一个很糟糕的测试策略）设计的，虽然测试的结果很好，但当并发度增加时，系统的性能有可能变得非常糟糕。而一个基于不断增加用户连接的情况下的响应时间测试则可以发现这个问题。&lt;/p>
&lt;p>一些任务，比如从细粒度数据创建汇总表的批量工作，需要的是周期性的快速响应时间。当然也可以测试这些任务纯粹的响应时间，但要注意考虑这些任务之间的相互影响。批量工作可能导致相互之间有影响的查询性能变差，反之亦然。&lt;/p>
&lt;p>归根结底，应该测试那些对用户来说最重要的指标。因此应该尽可能地去收集一些需求，比如，什么样的响应时间是可以接受的，期待多少的并发性，等等。然后基于这些需求来设计基准测试，避免目光短浅地只关注部分指标，而忽略其他指标。&lt;/p>
&lt;h1 id="23基准测试方法">2.3　基准测试方法&lt;a class="anchor" href="#23%e5%9f%ba%e5%87%86%e6%b5%8b%e8%af%95%e6%96%b9%e6%b3%95">#&lt;/a>&lt;/h1>
&lt;p>在了解基本概念之后，现在可以来具体讨论一下如何设计和执行基准测试。但在讨论如何设计好的基准测试之前，先来看一下如何避免一些常见的错误，这些错误可能导致测试结果无用或者不精确：&lt;/p>
&lt;ul>
&lt;li>使用真实数据的子集而不是全集。例如应用需要处理几百GB的数据，但测试只有1GB数据；或者只使用当前数据进行测试，却希望模拟未来业务大幅度增长后的情况。&lt;/li>
&lt;li>使用错误的数据分布。例如使用均匀分布的数据测试，而系统的真实数据有很多热点区域（随机生成的测试数据通常无法模拟真实的数据分布）。&lt;/li>
&lt;li>使用不真实的分布参数，例如假定所有用户的个人信息（profile）都会被平均地读取(2)。&lt;/li>
&lt;li>在多用户场景中，只做单用户的测试。&lt;/li>
&lt;li>在单服务器上测试分布式应用。&lt;/li>
&lt;li>与真实用户行为不匹配。例如Web页面中的“思考时间”。真实用户在请求到一个页面后会阅读一段时间，而不是不停顿地一个接一个点击相关链接。&lt;/li>
&lt;li>反复执行同一个查询。真实的查询是不尽相同的，这可能会导致缓存命中率降低。而反复执行同一个查询在某种程度上，会全部或者部分缓存结果。&lt;/li>
&lt;li>没有检查错误。如果测试的结果无法得到合理的解释，比如一个本应该很慢的查询突然变快了，就应该检查是否有错误产生。否则可能只是测试了MySQL检测语法错误的速度了。基准测试完成后，一定要检查一下错误日志，这应当是基本的要求。&lt;/li>
&lt;li>忽略了系统预热（warm up）的过程。例如系统重启后马上进行测试。有时候需要了解系统重启后需要多长时间才能达到正常的性能容量，要特别留意预热的时长。反过来说，如果要想分析正常的性能，需要注意，若基准测试在重启以后马上启动，则缓存是冷的、还没有数据，这时即使测试的压力相同，得到的结果也和缓存已经装满数据时是不同的。&lt;/li>
&lt;li>使用默认的服务器配置。第3章将详细地讨论服务器的优化配置。&lt;/li>
&lt;li>测试时间太短。基准测试需要持续一定的时间。后面会继续讨论这个话题。&lt;/li>
&lt;/ul>
&lt;p>只有避免了上述错误，才能走上改进测试质量的漫漫长路。&lt;/p></description></item><item><title>第1章MySQL架构与历史</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC1%E7%AB%A0MySQL%E6%9E%B6%E6%9E%84%E4%B8%8E%E5%8E%86%E5%8F%B2/</link><pubDate>Sun, 05 Jan 2025 08:42:11 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC1%E7%AB%A0MySQL%E6%9E%B6%E6%9E%84%E4%B8%8E%E5%8E%86%E5%8F%B2/</guid><description>&lt;p>第1章　MySQL架构与历史&lt;/p>
&lt;p>和其他数据库系统相比，MySQL有点与众不同，它的架构可以在多种不同场景中应用并发挥好的作用，但同时也会带来一点选择上的困难。MySQL并不完美，却足够灵活，能够适应高要求的环境，例如Web类应用。同时，MySQL既可以嵌入到应用程序中，也可以支持数据仓库、内容索引和部署软件、高可用的冗余系统、在线事务处理系统（OLTP）等各种应用类型。&lt;/p>
&lt;p>为了充分发挥MySQL的性能并顺利地使用，就必须理解其设计。MySQL的灵活性体现在很多方面。例如，你可以通过配置使它在不同的硬件上都运行得很好，也可以支持多种不同的数据类型。但是，MySQL最重要、最与众不同的特性是它的存储引擎架构，这种架构的设计将查询处理（Query Processing）及其他系统任务（Server Task）和数据的存储/提取相分离。这种处理和存储分离的设计可以在使用时根据性能、特性，以及其他需求来选择数据存储的方式。&lt;/p>
&lt;p>本章概要地描述了MySQL的服务器架构、各种存储引擎之间的主要区别，以及这些区别的重要性。另外也会回顾一下MySQL的历史背景和基准测试，并试图通过简化细节和演示案例来讨论MySQL的原理。这些讨论无论是对数据库一无所知的新手，还是熟知其他数据库的专家，都不无裨益。&lt;/p>
&lt;h1 id="11mysql逻辑架构">1.1　MySQL逻辑架构&lt;a class="anchor" href="#11mysql%e9%80%bb%e8%be%91%e6%9e%b6%e6%9e%84">#&lt;/a>&lt;/h1>
&lt;p>如果能在头脑中构建出一幅MySQL各组件之间如何协同工作的架构图，就会有助于深入理解MySQL服务器。图1-1展示了MySQL的逻辑架构图。&lt;/p>
&lt;p>&lt;img src="img/000002.jpeg" alt="" />
&lt;strong>图1-1：MySQL服务器逻辑架构图&lt;/strong>&lt;/p>
&lt;p>最上层的服务并不是MySQL所独有的，大多数基于网络的客户端/服务器的工具或者服务都有类似的架构。比如连接处理、授权认证、安全等等。&lt;/p>
&lt;p>第二层架构是MySQL比较有意思的部分。大多数MySQL的核心服务功能都在这一层，包括查询解析、分析、优化、缓存以及所有的内置函数（例如，日期、时间、数学和加密函数），所有跨存储引擎的功能都在这一层实现：存储过程、触发器、视图等。&lt;/p>
&lt;p>第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。和GNU/Linux下的各种文件系统一样，每个存储引擎都有它的优势和劣势。服务器通过API与存储引擎进行通信。这些接口屏蔽了不同存储引擎之间的差异，使得这些差异对上层的查询过程透明。存储引擎API包含几十个底层函数，用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作。但存储引擎不会去解析SQL(1)，不同存储引擎之间也不会相互通信，而只是简单地响应上层服务器的请求。&lt;/p>
&lt;h2 id="111连接管理与安全性">1.1.1　连接管理与安全性&lt;a class="anchor" href="#111%e8%bf%9e%e6%8e%a5%e7%ae%a1%e7%90%86%e4%b8%8e%e5%ae%89%e5%85%a8%e6%80%a7">#&lt;/a>&lt;/h2>
&lt;p>每个客户端连接都会在服务器进程中拥有一个线程，这个连接的查询只会在这个单独的线程中执行，该线程只能轮流在某个CPU核心或者CPU中运行。服务器会负责缓存线程，因此不需要为每一个新建的连接创建或者销毁线程(2)。&lt;/p>
&lt;p>当客户端（应用）连接到MySQL服务器时，服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。如果使用了安全套接字（SSL）的方式连接，还可以使用X.509证书认证。一旦客户端连接成功，服务器会继续验证该客户端是否具有执行某个特定查询的权限（例如，是否允许客户端对world数据库的Country表执行SELECT语句）。&lt;/p>
&lt;h2 id="112优化与执行">1.1.2　优化与执行&lt;a class="anchor" href="#112%e4%bc%98%e5%8c%96%e4%b8%8e%e6%89%a7%e8%a1%8c">#&lt;/a>&lt;/h2>
&lt;p>MySQL会解析查询，并创建内部数据结构（解析树），然后对其进行各种优化，包括重写查询、决定表的读取顺序，以及选择合适的索引等。用户可以通过特殊的关键字提示（hint）优化器，影响它的决策过程。也可以请求优化器解释（explain）优化过程的各个因素，使用户可以知道服务器是如何进行优化决策的，并提供一个参考基准，便于用户重构查询和schema、修改相关配置，使应用尽可能高效运行。第6章我们将讨论更多优化器的细节。&lt;/p>
&lt;p>优化器并不关心表使用的是什么存储引擎，但存储引擎对于优化查询是有影响的。优化器会请求存储引擎提供容量或某个具体操作的开销信息，以及表数据的统计信息等。例如，某些存储引擎的某种索引，可能对一些特定的查询有优化。关于索引与schema的优化，请参见第4章和第5章。&lt;/p>
&lt;p>对于SELECT语句，在解析查询之前，服务器会先检查查询缓存（Query Cache），如果能够在其中找到对应的查询，服务器就不必再执行查询解析、优化和执行的整个过程，而是直接返回查询缓存中的结果集。第7章详细讨论了相关内容。&lt;/p>
&lt;h1 id="12并发控制">1.2　并发控制&lt;a class="anchor" href="#12%e5%b9%b6%e5%8f%91%e6%8e%a7%e5%88%b6">#&lt;/a>&lt;/h1>
&lt;p>无论何时，只要有多个查询需要在同一时刻修改数据，都会产生并发控制的问题。本章的目的是讨论MySQL在两个层面的并发控制：服务器层与存储引擎层。并发控制是一个内容庞大的话题，有大量的理论文献对其进行过详细的论述。本章只简要地讨论MySQL如何控制并发读写，因此读者需要有相关的知识来理解本章接下来的内容。&lt;/p>
&lt;p>以Unix系统的email box为例，典型的&lt;em>mbox&lt;/em>文件格式是非常简单的。一个&lt;em>mbox&lt;/em>邮箱中的所有邮件都串行在一起，彼此首尾相连。这种格式对于读取和分析邮件信息非常友好，同时投递邮件也很容易，只要在文件末尾附加新的邮件内容即可。&lt;/p>
&lt;p>但如果两个进程在同一时刻对同一个邮箱投递邮件，会发生什么情况？显然，邮箱的数据会被破坏，两封邮件的内容会交叉地附加在邮箱文件的末尾。设计良好的邮箱投递系统会通过锁（lock）来防止数据损坏。如果客户试图投递邮件，而邮箱已经被其他客户锁住，那就必须等待，直到锁释放才能进行投递。&lt;/p>
&lt;p>这种锁的方案在实际应用环境中虽然工作良好，但并不支持并发处理。因为在任意一个时刻，只有一个进程可以修改邮箱的数据，这在大容量的邮箱系统中是个问题。&lt;/p>
&lt;h2 id="121读写锁">1.2.1　读写锁&lt;a class="anchor" href="#121%e8%af%bb%e5%86%99%e9%94%81">#&lt;/a>&lt;/h2>
&lt;p>从邮箱中读取数据没有这样的麻烦，即使同一时刻多个用户并发读取也不会有什么问题。因为读取不会修改数据，所以不会出错。但如果某个客户正在读取邮箱，同时另外一个用户试图删除编号为25的邮件，会产生什么结果？结论是不确定，读的客户可能会报错退出，也可能读取到不一致的邮箱数据。所以，为安全起见，即使是读取邮箱也需要特别注意。&lt;/p>
&lt;p>如果把上述的邮箱当成数据库中的一张表，把邮件当成表中的一行记录，就很容易看出，同样的问题依然存在。从很多方面来说，邮箱就是一张简单的数据库表。修改数据库表中的记录，和删除或者修改邮箱中的邮件信息，十分类似。&lt;/p>
&lt;p>解决这类经典问题的方法就是并发控制，其实非常简单。在处理并发读或者写时，可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为共享锁（shared lock）和排他锁（exclusive lock），也叫读锁（read lock）和写锁（write lock）。&lt;/p>
&lt;p>这里先不讨论锁的具体实现，描述一下锁的概念如下：读锁是共享的，或者说是相互不阻塞的。多个客户在同一时刻可以同时读取同一个资源，而互不干扰。写锁则是排他的，也就是说一个写锁会阻塞其他的写锁和读锁，这是出于安全策略的考虑，只有这样，才能确保在给定的时间里，只有一个用户能执行写入，并防止其他用户读取正在写入的同一资源。&lt;/p>
&lt;p>在实际的数据库系统中，每时每刻都在发生锁定，当某个用户在修改某一部分数据时，MySQL会通过锁定防止其他用户读取同一数据。大多数时候，MySQL锁的内部管理都是透明的。&lt;/p>
&lt;h2 id="122锁粒度">1.2.2　锁粒度&lt;a class="anchor" href="#122%e9%94%81%e7%b2%92%e5%ba%a6">#&lt;/a>&lt;/h2>
&lt;p>一种提高共享资源并发性的方式就是让锁定对象更有选择性。尽量只锁定需要修改的部分数据，而不是所有的资源。更理想的方式是，只对会修改的数据片进行精确的锁定。任何时候，在给定的资源上，锁定的数据量越少，则系统的并发程度越高，只要相互之间不发生冲突即可。&lt;/p>
&lt;p>问题是加锁也需要消耗资源。锁的各种操作，包括获得锁、检查锁是否已经解除、释放锁等，都会增加系统的开销。如果系统花费大量的时间来管理锁，而不是存取数据，那么系统的性能可能会因此受到影响。&lt;/p>
&lt;p>所谓的锁策略，就是在锁的开销和数据的安全性之间寻求平衡，这种平衡当然也会影响到性能。大多数商业数据库系统没有提供更多的选择，一般都是在表上施加行级锁（row-level lock），并以各种复杂的方式来实现，以便在锁比较多的情况下尽可能地提供更好的性能。&lt;/p>
&lt;p>而MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎的设计中，锁管理是个非常重要的决定。将锁粒度固定在某个级别，可以为某些特定的应用场景提供更好的性能，但同时却会失去对另外一些应用场景的良好支持。好在MySQL支持多个存储引擎的架构，所以不需要单一的通用解决方案。下面将介绍两种最重要的锁策略。&lt;/p>
&lt;h3 id="表锁table-lock">表锁（table lock）&lt;a class="anchor" href="#%e8%a1%a8%e9%94%81table-lock">#&lt;/a>&lt;/h3>
&lt;p>表锁是MySQL中最基本的锁策略，并且是开销最小的策略。表锁非常类似于前文描述的邮箱加锁机制：它会锁定整张表。一个用户在对表进行写操作（插入、删除、更新等）前，需要先获得写锁，这会阻塞其他用户对该表的所有读写操作。只有没有写锁时，其他读取的用户才能获得读锁，读锁之间是不相互阻塞的。&lt;/p>
&lt;p>在特定的场景中，表锁也可能有良好的性能。例如，READ LOCAL表锁支持某些类型的并发写操作。另外，写锁也比读锁有更高的优先级，因此一个写锁请求可能会被插入到读锁队列的前面（写锁可以插入到锁队列中读锁的前面，反之读锁则不能插入到写锁的前面）。&lt;/p>
&lt;p>尽管存储引擎可以管理自己的锁，MySQL本身还是会使用各种有效的表锁来实现不同的目的。例如，服务器会为诸如ALTER TABLE之类的语句使用表锁，而忽略存储引擎的锁机制。&lt;/p>
&lt;h3 id="行级锁row-lock">行级锁（row lock）&lt;a class="anchor" href="#%e8%a1%8c%e7%ba%a7%e9%94%81row-lock">#&lt;/a>&lt;/h3>
&lt;p>行级锁可以最大程度地支持并发处理（同时也带来了最大的锁开销）。众所周知，在InnoDB和XtraDB，以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现，而MySQL服务器层（如有必要，请回顾前文的逻辑架构图）没有实现。服务器层完全不了解存储引擎中的锁实现。在本章的后续内容以及全书中，所有的存储引擎都以自己的方式显现了锁机制。&lt;/p>
&lt;h1 id="13事务">1.3　事务&lt;a class="anchor" href="#13%e4%ba%8b%e5%8a%a1">#&lt;/a>&lt;/h1>
&lt;p>在理解事务的概念之前，接触数据库系统的其他高级特性还言之过早。事务就是一组原子性的SQL查询，或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句，那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行，那么所有的语句都不会执行。也就是说，事务内的语句，要么全部执行成功，要么全部执行失败。&lt;/p>
&lt;p>本节的内容并非专属于MySQL，如果读者已经熟悉了事务的ACID的概念，可以直接跳转到1.3.4节。&lt;/p>
&lt;p>银行应用是解释事务必要性的一个经典例子。假设一个银行的数据库有两张表：支票（checking）表和储蓄（savings）表。现在要从用户Jane的支票账户转移200美元到她的储蓄账户，那么需要至少三个步骤：&lt;/p>
&lt;ol>
&lt;li>检查支票账户的余额高于200美元。&lt;/li>
&lt;li>从支票账户余额中减去200美元。&lt;/li>
&lt;li>在储蓄账户余额中增加200美元。&lt;/li>
&lt;/ol>
&lt;p>上述三个步骤的操作必须打包在一个事务中，任何一个步骤失败，则必须回滚所有的步骤。&lt;/p></description></item><item><title>第13章云端的MySQL</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC13%E7%AB%A0%E4%BA%91%E7%AB%AF%E7%9A%84MySQL/</link><pubDate>Sun, 05 Jan 2025 08:42:07 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC13%E7%AB%A0%E4%BA%91%E7%AB%AF%E7%9A%84MySQL/</guid><description>&lt;p>第13章　云端的MySQL&lt;/p>
&lt;p>许多人在云中使用MySQL，有时候规模还非常庞大，这并不奇怪。从我们的经验来看，大多数人使用的是Amazon Web Services平台（AWS）：特别是Amazon的弹性计算云（Elastic Compute Cloud，EC2），弹性块存储（Elastic Block Store，EBS），以及更小众的关系数据库服务（Relational Database Service，RDS）。&lt;/p>
&lt;p>为了便于讨论MySQL在云中的应用，可以将其粗略分为两类。&lt;/p>
&lt;p>IaaS（基础设施即服务）&lt;/p>
&lt;p>&lt;em>Iaas&lt;/em>是用于托管自有的MySQL服务器的云端基础架构。可以在云端购买虚拟的服务器资源来安装运行MySQL实例。也可以根据需求随意配置MySQL和操作系统，但没有权限也无法看到处于底层的物理硬件设备。&lt;/p>
&lt;p>&lt;em>DBaaS&lt;/em>（数据库即服务）&lt;/p>
&lt;p>MySQL本身作为由云端管理的资源。用户需要先收到MySQL服务器的访问许可（通常是一个连接串）才能访问。也可以配置一些MySQL选项，但没有权限去控制或查看底层的操作系统或虚拟服务器实例。例如 Amazon运行MySQL的RDS。其中一些服务器并非真的使用MySQL，但它们能兼容MySQL协议和查询语言。&lt;/p>
&lt;p>我们讨论的重点主要集中在第一类：云托管平台，例如AWS、Rackspace Cloud以及Joyent(1)。有许多很好的资源介绍如何部署和管理MySQL及其运行所需要的资源，并且也有非常多的平台来完全满足这样的需求，所以我们不会展示代码样例或讨论具体的操作技术。因此，本章关注的重点是，在云端运行MySQL还是在传统服务器上部署MySQL，它们在最终经济上和性能特性上的关键区别是什么。我们假定你对云计算很熟悉。这里不是对云计算概念的简单介绍，我们的目的只是帮助那些还不熟悉在云端部署MySQL的用户在使用时避免一些可能遇到的陷阱。&lt;/p>
&lt;p>一般来说，MySQL能够在云中很好地运行。在云中运行MySQL并不比在其他平台困难，但有一些非常重要的差别。你需要注意这些差别并据此设计应用和架构来获得好的效果。某些场景下在云端托管MySQL并不是非常适合，有时候则很适合，但大多数时候云仅仅是另外一个部署平台而已。&lt;/p>
&lt;p>云是一个部署平台，而不是一种架构，理解这一点很重要。架构会受平台的影响，但平台和架构明显不同。如果你把架构和平台搞混了，就可能会做出不合适的选择而给以后带来麻烦。这也正是我们要花时间讨论云端的MySQL到底有什么不同的原因。&lt;/p>
&lt;h1 id="131云的优点缺点和相关误解">13.1　云的优点、缺点和相关误解&lt;a class="anchor" href="#131%e4%ba%91%e7%9a%84%e4%bc%98%e7%82%b9%e7%bc%ba%e7%82%b9%e5%92%8c%e7%9b%b8%e5%85%b3%e8%af%af%e8%a7%a3">#&lt;/a>&lt;/h1>
&lt;p>云计算有许多优点，但很少是为MySQL特别设计。有一些书籍已经介绍了相关的话题(2)，这里我们不再赘述。不过我们会列出一些比较重要的条目供参考，因为接下来会讨论到云计算的缺点，我们不希望你认为我们是在过分苛求云计算。&lt;/p>
&lt;ul>
&lt;li>云是一种将基础设施外包出去无须自己管理的方法。你不需要寻找供应商购买硬件，也不需要维护和供应商之间的关系，更无须替换失效的硬盘驱动器等。&lt;/li>
&lt;li>云一般是按照即用即付的方式支付，可以把前期的大量资本支出转换为持续的运营成本。&lt;/li>
&lt;li>随着供应商发布新的服务和成本降低，云提供的价值越来越大。你自己无须做任何事情（例如升级服务器），就可以从这些提升中获益；随着时间推移你会很容易地获得更多更好的选择并且费用更低。&lt;/li>
&lt;li>云能够帮助你轻松地准备好服务器和其他资源，在用完后直接将其关闭，而无须关注怎么处理它们，或者怎么卖掉它们收回成本。&lt;/li>
&lt;li>云代表了对基础设施的另一种思考方式——作为通过API来定义和控制的资源——支持更多的自动化操作。从“私有云”中也可以获得这些好处。&lt;/li>
&lt;/ul>
&lt;p>当然，不是所有跟云相关的东西都是好的。这里有一些缺点可能会构成挑战（在本章稍后部分我们会列出MySQL特有的缺点）。&lt;/p>
&lt;ul>
&lt;li>资源是共享并且不可预测的，实际上你可以获得比你支付的更多的资源。这听起来很不错，但却导致容量规划很难做。如果你在不知情的情况下获得了比理应享受到的更多的计算资源，那么就存在这样的风险：别人也许会索要他们应得的资源，这会使你的应用性能退化到应有的水平。一般来说，很难确切地知道本来应该得到多少（资源），大多数云托管服务提供商不会对此给出确切的答案。&lt;/li>
&lt;li>无法保证容量和可用性。你可能以为还可以获得新实例，但如果供应商已经超额销售了呢？这在有很多共享资源的情况下会发生，同样也会发生在云中。&lt;/li>
&lt;li>虚拟的共享资源导致排查故障更加困难，特别是在无法访问底层物理硬件的情况下无法检查并弄清到底发生了什么。例如，我们曾经看到过一些系统的&lt;em>iostat&lt;/em>显示的I/O很正常或者&lt;em>vmstat&lt;/em>显示的CPU很正常，而当实际衡量完成一个任务需要的时间时，资源却被系统上的其他东西严重占用了。如果在云平台上出现了性能问题，尤其需要去仔细地分析检测。如果对此并不擅长，可能就无法确认到底是底层系统性能差，还是你做了什么事情导致应用出现不合理的资源需求。&lt;/li>
&lt;/ul>
&lt;p>总的来说，云平台上对性能、可用性和容量的透明性和控制力都有所下降。最后，还有一些对云的误解需要记住。&lt;/p>
&lt;p>云天生具备更好的可扩展性&lt;/p>
&lt;p>应用、云的架构，以及管理云服务的组织是不是都是可扩展的。云并不是天生可扩展的，云也仅仅是云而已，选择一个可扩展的平台并不能自动使应用变得可扩展。的确，如果云托管提供商没有超售，那么你可以根据需求来购买资源，但在需要时能够获得资源仅仅是扩展性的一个方面而已。&lt;/p>
&lt;p>云可以自动改善甚至保证可用时间&lt;/p>
&lt;p>一般来说，个别在云端托管的服务器比那些经过良好设计的专用基础设施更容易发生故障或运行中断。但是许多人并没有意识到这一点。例如，有人这样写道：“我们将基础设施升级到基于云构建的系统以保证100％的可用时间和可扩展性”。而就在这之前AWS遭受了两次大规模的运行中断故障，导致很大一部分用户受影响。好的架构能够用不可靠的组件设计出可靠的系统，但通常更可靠的基础设施可以获得更高的可用性。（当然不可能有100％的可用时间的系统。）&lt;/p>
&lt;p>另一方面，购买云计算服务，实际上是购买一个由专家构建的平台。他们已经考虑了许多底层的东西，这意味着你可以更专注于上层工作。如果构建自己的平台而对其中的那些细枝末节并不精通，就可能犯一些初学者的错误，早晚会导致一些宕机时间。从这一点来说，云计算能够帮助改善可用时间。&lt;/p>
&lt;p>云是唯一能提供[这里填入任意的优点]的东西&lt;/p>
&lt;p>事实上，许多云的优点是继承自构建云平台所用到的技术，即使不使用云也可以获得(3)。例如，通过管理得当的虚拟化和容量规划，可以像任何一个云平台那样简单快速地启动（spin up）一台新的机器。完全没必要专门使用云来做到这一点。&lt;/p>
&lt;p>云是一个“银弹”（silver bullet）&lt;/p>
&lt;p>虽然大部分人会认为这很荒谬，但确实有人会这么认为。实际上完全没有这回事。&lt;/p>
&lt;p>无可否认，云计算提供了独特的优点，随着时间的推移，关于云计算是什么，以及它们在什么情况下会有帮助，我们会获得更多的共识。但有一点非常肯定：它是全新的，我们现在所做的任何预测都未必经得起时间的考验。我们会在本书讨论相对安全的部分，而将剩下的部分留给读者讨论。&lt;/p>
&lt;h1 id="132mysql在云端的经济价值">13.2　MySQL在云端的经济价值&lt;a class="anchor" href="#132mysql%e5%9c%a8%e4%ba%91%e7%ab%af%e7%9a%84%e7%bb%8f%e6%b5%8e%e4%bb%b7%e5%80%bc">#&lt;/a>&lt;/h1>
&lt;p>在一些场景下云托管比传统的服务器部署方式更经济。以我们的经验来看，云托管比较适合尚处于初级阶段的企业，或者那些持续接触新概念并且本质上是以适用为主的企业，例如移动应用开发者或游戏开发者。这些技术的市场随着移动计算的扩张出现了爆炸式增长，并且仍然是快速发展的领域。在许多情况下，成功的因素并不为开发者所控制，例如口口相传的推荐或者恰逢重要国际事件的时机。&lt;/p>
&lt;p>我们已经帮助很多公司在云中构建移动应用、社交网络以及游戏应用。其中一个他们大量使用的策略是尽可能又快又便宜地开发和发布应用。如果一个应用碰巧变得流行了，公司将投入资源扩大其规模；否则就会很快终结这些应用。一些公司构建并发布的应用的生命周期甚至只有几个星期，在这样的环境下，可以毫不犹豫地选择云托管。&lt;/p>
&lt;p>如果是一个小规模的公司，可能无法提供足够的硬件来自建数据中心以满足一个非常流行的Facebook应用的发展曲线。我们也协助过一些大型的Facebook应用进行扩展，它们能够以今人惊讶的速度增长——有时甚至会快到让一个主机托管公司耗尽资源。更为严重的是，这些应用的增长是完全无法预测的；它们可能只有极少量的用户（也可能突然有了爆炸性的用户数量增长）。我们在数据中心和云中都遇到过这样的应用。如果是一个小公司，云可以帮你避免前期快速注入大量的资金来获得更快更大规模的风险。&lt;/p>
&lt;p>云的另一种潜在的大用途是运行不是很重要的基础设施，例如集成环境、开发测试平台，以及评估环境。假设部署周期是两个星期。你会每天每个小时都测试部署一次，还是只在项目最后的冲刺时测试？许多用户只是偶尔需要筹划和部署测试环境。在这种场景下，云可以帮助节约不少钱。&lt;/p>
&lt;p>以下是我们使用云的两种方式。第一个是作为我们对技术职员面试的一部分，我们会询问如何解决一些实际的问题。我们使用AMI（Amazon Machine Images）来模拟一些被“破坏”的机器，然后让求职者登录并在服务器上执行一系列任务。我们不必开放他们到内部网络的授权，这种方案显然要方便得多。另一个是作为新项目的工作平台和开发服务器。有一个这样的项目已经在一台云端开发服务器上运行了数个月，而花费不足一美元！这在我们自己的基础设施上是不可能做到的。单是发送一封邮件给系统管理员申请开发服务器的时间价值就不止一美元。&lt;/p>
&lt;p>但是另一方面，云托管对于长期项目而言可能会更加昂贵。如果打算长远地使用云，就需要花时间来计算一下（它是否划算）。除了猜想未来的创新能给云计算和商用硬件带来什么，还需要做基准测试以及一个完整的总体持有成本（TCO）账单。为了理清事情的本质并考虑全面所有相关的细节，你需要把所有的事情最终归结为一个数字：每美元的业务交易数。事情变化得太快，所以我们将这个留给读者思考。&lt;/p>
&lt;h1 id="133云中的mysql的可扩展性和高可用性">13.3　云中的MySQL的可扩展性和高可用性&lt;a class="anchor" href="#133%e4%ba%91%e4%b8%ad%e7%9a%84mysql%e7%9a%84%e5%8f%af%e6%89%a9%e5%b1%95%e6%80%a7%e5%92%8c%e9%ab%98%e5%8f%af%e7%94%a8%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;p>正如我们之前提到的，MySQL并不会在云端自动变得更具扩展性。事实上，如果机器的性能较差，会导致过早使用横向扩展策略。况且云托管服务器相比专用的硬件可靠性和可预测性要更差些，所以想在云端获得高可用性需要更多的创新。&lt;/p>
&lt;p>但是总的来说，在云端中扩展MySQL和在其他地方扩展没有太多的差别。最大的不同就是按需提供服务器的能力。但是也有某些限制会导致扩展和高可用实现起来有点麻烦，至少在有些云环境中是这样的。例如，在AWS云平台中，无法使用类似虚拟IP地址的功能来完成快速原子故障转移。像这种对资源的有限控制意味着你需要使用其他办法，例如代理。（ScaleBase也值得去看看。）&lt;/p>
&lt;p>云另外一个迷惑人的地方是梦想中的自动扩展——就是根据需求的增加或减少来启动或关闭实例。尽管对于诸如Web服务器这样的无状态部分是可行的，但对于数据库服务器而言则很难做到，因为它是有状态的。对于一些特定的场景，例如以读为主的应用，可以通过增加备库的方式来获得有限的自动扩展(4)，但这并不是一个通用的解决方案。实际上，虽然许多应用在Web层使用了自动扩展，但MySQL并不具备在一个无共享（Shared Nothing）集群中的对等角色服务器之间迁移的能力。你可以通过分片架构来自动重新分片并自动增长或收缩(5)，但MySQL本身是无法自动扩展的。&lt;/p>
&lt;p>事实上，因为数据库通常是一个应用系统中主要或唯一的有状态并且持久化的组件，所以把应用服务迁移到云端是很普遍的事情，因为除数据库之外的所有部分都可以从云中收益——Web服务器、工作队列服务器、缓存等——而MySQL只需要处理剩下的东西。毕竟，数据库并非世界的中心。如果应用系统其他部分获得的好处，超过了让MySQL运行得足够好而投入的额外开销和必需的工作量，那这不是一个是否会发生的问题，而是怎么发生的问题。要回答这个问题，最好先了解你在云中可能碰到的额外的挑战。这些通常围绕着数据库服务器的可用资源。&lt;/p>
&lt;h1 id="134四种基础资源">13.4　四种基础资源&lt;a class="anchor" href="#134%e5%9b%9b%e7%a7%8d%e5%9f%ba%e7%a1%80%e8%b5%84%e6%ba%90">#&lt;/a>&lt;/h1>
&lt;p>MySQL需要四种基础资源来完成工作：CPU周期、内存、I/O，以及网络。这四种资源的特性和重要程度在不同的云平台上各不相同。可以通过了解它们的不同之处和对MySQL的影响，以决定是否选择在云中托管MySQL。&lt;/p>
&lt;ul>
&lt;li>CPU通常很少且慢。在写作本书时最大的标准EC2实例提供8个虚拟CPU核心。EC2提供的虚拟CPU比高端CPU的速度明显要慢很多（可以查看本章稍后的基准测试结果）。虽然可能略有不同，但很可能在大多数云托管平台中这都是一种普遍现象。EC2提供使用多个CPU资源的实例，但它们的最大可用内存却更低。在写作本书时商用服务器能提供几十个CPU核心——甚至更多，如果按硬件线程算的话。(6)&lt;/li>
&lt;li>内存大小受限制。最大的EC2实例当前能提供68.4GB的内存。与此相比，商用服务器能提供512GB～1TB的内存。&lt;/li>
&lt;li>I/O的吞吐量、延迟以及一致性受到限制。在AWS云中有两个存储选项。&lt;br>
第一个选择是使用EBS卷，这有点类似云中的SAN。AWS的最佳实践是在用EBS组建的RAID10卷上建立服务器。但是EBS是一个共享资源，就像EC2服务器和EBS服务器之间的网络连接。延迟可能会很高并且不可预测，即使是在适量的吞吐量需求下也是如此。我们已经测得EBS设备的I/O延迟可以达到十几分之一秒。相比之下，直接插在本机的商用硬盘驱动器只需几个毫秒，而闪存设备比硬盘驱动器的速度又要高出几个数量级。但另一方面，EBS卷也有许多很好的特性，例如和其他AWS服务、快照等结合起来使用。&lt;br>
第二个选择是实例的本地存储。每个EC2服务器有一定数量的本地存储，实际安装在底层服务器上。它能够比EBS提供更多的一致性性能(7)，但如果实例停止了就无法做到持久化。正是由于这样的特性导致其不适合大多数的数据库服务器场景。&lt;/li>
&lt;li>尽管网络通常是一个变化多端的共享资源，但是性能通常比较好。虽然使用商用硬件可以获得更快更持续的网络性能，但CPU、RAM和I/O更容易成为主要的性能瓶颈，在AWS云中我们还没有遇到过网络性能问题。&lt;/li>
&lt;/ul>
&lt;p>正如你所看到的，四种基础资源中有三种在AWS云中是受限的，在某些场景下尤其明显。总的来说，这些基础资源并没有商业硬件那样的性能。下一节我们会讨论这些确切的结论。&lt;/p></description></item><item><title>第12章高可用性</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC12%E7%AB%A0%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7/</link><pubDate>Sun, 05 Jan 2025 08:42:06 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC12%E7%AB%A0%E9%AB%98%E5%8F%AF%E7%94%A8%E6%80%A7/</guid><description>&lt;p>第12章　高可用性&lt;/p>
&lt;p>本章将讲述我们提到的复制、可扩展性以及高可用性三个主题中的第三个。归根结底，高可用性实际上意味着“更少的宕机时间”。然而糟糕的是，高可用性经常和其他相关的概念混淆，例如冗余、保障数据不丢失，以及负载均衡。我们希望之前的两章已经为清楚地理解高可用性做了足够的铺垫。跟其他两章一样，这一章也不仅仅是关注高可用性的内容，一些相关的话题也会综合阐述。&lt;/p>
&lt;h1 id="121什么是高可用性">12.1　什么是高可用性&lt;a class="anchor" href="#121%e4%bb%80%e4%b9%88%e6%98%af%e9%ab%98%e5%8f%af%e7%94%a8%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;p>高可用性实际上有点像神秘的野兽。它通常以百分比表示，这本身也是一种暗示：高可用性不是绝对的，只有相对更高的可用性。100％的可用性是不可能达到的。可用性的“9”规则是表示可用性目标最普遍的方法。你可能也知道，“5个9”表示99.999％的正常可用时间。换句话说，每年只允许5分钟的宕机时间。对于大多数应用这已经是令人惊叹的数字，尽管还有一些人试图获得更多的“9”。&lt;/p>
&lt;p>每个应用对可用性的需求各不相同。在设定一个可用时间的目标之前，先问问自己，是不是确实需要达到这个目标。可用性每提高一点，所花费的成本都会远超之前；可用性的效果和开销的比例并不是线性的。需要保证多少可用时间，取决于能够承担多少成本。高可用性实际上是在宕机造成的损失与降低宕机时间所花费的成本之间取一个平衡。换句话说，如果需要花大量金钱去获得更好的可用时间，但所带来的收益却很低，可能就不值得去做。总的来说，应用在超过一定的点以后追求更高的可用性是非常困难的，成本也会很高，因此我们建议设定一个更现实的目标并且避免过度设计。幸运的是，建立2个9或3个9的可用时间的目标可能并不困难，具体情况取决于应用。&lt;/p>
&lt;p>有时候人们将可用性定义成服务正在运行的时间段。我们认为可用性的定义还应该包括应用是否能以足够好的性能处理请求。有许多方法可以让一个服务器保持运行，但服务并不是真正可用。对一个很大的服务器而言，重启MySQL之后，可能需要几个小时才能充分预热以保证查询请求的响应时间是可以接受的，即使服务器只接收了正常流量的一小部分也是如此。&lt;/p>
&lt;p>另一个需要考虑的问题是，即使应用并没有停止服务，但是否可能丢失了数据。如果服务器遭遇灾难性故障，可能多少都会丢失一些数据，例如最近已经写入（最新丢失的）二进制日志但尚未传递到备库的中继日志中的事务。你能够容忍吗？大多数应用能够容忍；因为替代方案大多非常昂贵且复杂，或者有一些性能开销。例如，可以使用同步复制，或是将二进制日志放到一个通过DRBD进行复制的设备上，这样就算服务器完全失效也不用担心丢失数据。（但是整个数据中心也有可能会掉电。）&lt;/p>
&lt;p>一个良好的应用架构通常可以降低可用性方面的需求，至少对部分系统而言是这样的，良好的架构也更容易做到高可用。将应用中重要和不重要的部分进行分离可以节约不少工作量和金钱，因为对于一个更小的系统改进可用性会更容易。可以通过计算“风险敞口（risk exposure）”，将失效概率与失效代价相乘来确认高优先级的风险。画一个简单的风险计算表，以概率、代价和风险敞口作为列，这样很容易找到需要优先处理的项目。&lt;/p>
&lt;p>在前一章我们通过讨论如何避免导致糟糕的可扩展性的原因，来推出如何获得更好的可扩展性。这里也会使用相似的方法来讨论可用性，因为我们相信，理解可用性最好的方法就是研究它的反面——宕机时间。接下来的小节我们会讨论为什么会出现宕机。&lt;/p>
&lt;h1 id="122导致宕机的原因">12.2　导致宕机的原因&lt;a class="anchor" href="#122%e5%af%bc%e8%87%b4%e5%ae%95%e6%9c%ba%e7%9a%84%e5%8e%9f%e5%9b%a0">#&lt;/a>&lt;/h1>
&lt;p>我们经常听到导致数据库宕机最主要的原因是编写的SQL查询性能很差，真的是这样吗？2009年我们决定分析我们客户的数据库所遇到的问题，以找出那些真正引起宕机的问题，以及如何避免这些问题(1)。结果证实了一些我们已有的猜想，但也否定了一些（错误的）认识，我们从中学到了很多。&lt;/p>
&lt;p>我们首先对宕机事件按表现方式而非导致的原因进行分类。一般来说，“运行环境”是排名第一的宕机类别，大约35％的事件属于这一类。运行环境可以看作是支持数据库服务器运行的系统和资源集合，包括操作系统、硬盘以及网络等。性能问题紧随其后，也是约占35％；然后是复制，占20％；最后剩下的10％包含各种类型的数据丢失或损坏，以及其他问题。&lt;/p>
&lt;p>我们对事件按类型进行分类后，确定了导致这些事件的原因。以下是一些需要注意的地方：&lt;/p>
&lt;ul>
&lt;li>在运行环境的问题中，最普遍的问题是磁盘空间耗尽。&lt;/li>
&lt;li>在性能问题中，最普遍的宕机原因确实是运行很糟糕的SQL，但也不一定都是这个原因，比如也有很多问题是由于服务器Bug或错误的行为导致的。&lt;/li>
&lt;li>糟糕的Schema和索引设计是第二大影响性能的问题。&lt;/li>
&lt;li>复制问题通常由于主备数据不一致导致。&lt;/li>
&lt;li>数据丢失问题通常由于DROP TABLE的误操作导致，并总是伴随着缺少可用备份的问题。&lt;/li>
&lt;/ul>
&lt;p>复制虽然常被人们用来改善可用时间，但却也可能导致宕机。这主要是由于不正确的使用导致的，即便如此，它也阐明了一个普遍的情况：许多高可用性策略可能会产生反作用，我们会在后面讨论这个话题。&lt;/p>
&lt;p>现在我们已经知道了主要宕机类别，以及有什么需要注意，下面我们将专门介绍如何获得高可用性。&lt;/p>
&lt;h1 id="123如何实现高可用性">12.3　如何实现高可用性&lt;a class="anchor" href="#123%e5%a6%82%e4%bd%95%e5%ae%9e%e7%8e%b0%e9%ab%98%e5%8f%af%e7%94%a8%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;p>可以通过同时进行以下两步来获得高可用性。首先，可以尝试避免导致宕机的原因来减少宕机时间。许多问题其实很容易避免，例如通过适当的配置、监控，以及规范或安全保障措施来避免人为错误。第二，尽量保证在发生宕机时能够快速恢复。最常见的策略是在系统中制造冗余，并且具备故障转移能力。这两个维度的高可用性可以通过两个相关的度量来确定：平均失效时间（MTBF）和平均恢复时间（MTTR）。一些组织会非常仔细地追踪这些度量值。&lt;/p>
&lt;p>第二步——通过冗余快速恢复——很不幸，这里是最应该注意的地方，但预防措施的投资回报率会很高。接下来我们来探讨一些预防措施。&lt;/p>
&lt;h2 id="1231提升平均失效时间mtbf">12.3.1　提升平均失效时间（MTBF）&lt;a class="anchor" href="#1231%e6%8f%90%e5%8d%87%e5%b9%b3%e5%9d%87%e5%a4%b1%e6%95%88%e6%97%b6%e9%97%b4mtbf">#&lt;/a>&lt;/h2>
&lt;p>其实只要尽职尽责地做好一些应做的事情，就可以避免很多宕机。在分类整理宕机事件并追查导致宕机的根源时，我们还发现，很多宕机本来是有一些方法可以避免的。我们发现大部分宕机事件都可以通过全面的常识性系统管理办法来避免。以下是从我们的白皮书中摘录的指导性建议，在白皮书中有我们详细的分析结果。&lt;/p>
&lt;ul>
&lt;li>测试恢复工具和流程，包括从备份中恢复数据。&lt;/li>
&lt;li>遵从最小权限原则。&lt;/li>
&lt;li>保持系统干净、整洁。&lt;/li>
&lt;li>使用好的命名和组织约定来避免产生混乱，例如服务器是用于开发还是用于生产环境。&lt;/li>
&lt;li>谨慎安排升级数据库服务器。&lt;/li>
&lt;li>在升级前，使用诸如Percona Toolkit中的&lt;em>pt-upgrade&lt;/em>之类的工具仔细检查系统。&lt;/li>
&lt;li>使用InnoDB并进行适当的配置，确保InnoDB是默认存储引擎。如果存储引擎被禁止，服务器就无法启动。&lt;/li>
&lt;li>确认基本的服务器配置是正确的。&lt;/li>
&lt;li>通过skip_name_resolve禁止DNS。&lt;/li>
&lt;li>除非能证明有效，否则禁用查询缓存。&lt;/li>
&lt;li>避免使用复杂的特性，例如复制过滤和触发器，除非确实需要。&lt;/li>
&lt;li>监控重要的组件和功能，特别是像磁盘空间和RAID卷状态这样的关键项目，但也要避免误报，只有当确实发生问题时才发送告警。&lt;/li>
&lt;li>尽量记录服务器的状态和性能指数，如果可能就尽量久地保存。&lt;/li>
&lt;li>定期检查复制完整性。&lt;/li>
&lt;li>将备库设置为只读，不要让复制自动启动。&lt;/li>
&lt;li>定期进行查询语句审查。&lt;/li>
&lt;li>归档并清理不需要的数据。&lt;/li>
&lt;li>为文件系统保留一些空间。在GNU/Linux中，可以使用-&lt;em>m&lt;/em>选项来为文件系统本身保留空间。还可以在LVM卷组中留下一些空闲空间。或者，更简单的方法，仅仅创建一个巨大的空文件，在文件系统快满时，直接将其删除。(2)&lt;/li>
&lt;li>养成习惯，评估和管理系统的改变、状态以及性能信息。&lt;/li>
&lt;/ul>
&lt;p>我们发现对系统变更管理的缺失是所有导致宕机的事件中最普遍的原因。典型的错误包括粗心的升级导致升级失败并遭遇一些Bug，或是尚未测试就将Schema或查询语句的更改直接运行到线上，或者没有为一些失败的情况制定计划，例如达到了磁盘容量限制。另外一个导致问题的主要原因是缺少严格的评估，例如因为疏忽没有确认备份是否是可以恢复的。最后，可能没有正确地监控MySQL的相关信息。例如缓存命中率报警并不能说明出现问题，并且可能产生大量的误报，这会使监控系统被认为不太有用，于是一些人就会忽略报警。有时候监控系统失效了，甚至没人会注意到，直至你的老板质问你，“为什么Nagios没有告诉我们磁盘已经满了”。&lt;/p>
&lt;h2 id="1232降低平均恢复时间mttr">12.3.2　降低平均恢复时间（MTTR）&lt;a class="anchor" href="#1232%e9%99%8d%e4%bd%8e%e5%b9%b3%e5%9d%87%e6%81%a2%e5%a4%8d%e6%97%b6%e9%97%b4mttr">#&lt;/a>&lt;/h2>
&lt;p>之前提到，可以通过减少恢复时间来获得高可用性。事实上，一些人走得更远，只专注于减少恢复时间的某个方面：通过在系统中建立冗余来避免系统完全失效，并避免单点失效问题。&lt;/p>
&lt;p>在降低恢复时间上进行投资非常重要，一个能够提供冗余和故障转移能力的系统架构，则是降低恢复时间的关键环节。但实现高可用性不单单是一个技术问题，还有许多个人和组织的因素。组织和个人在避免宕机和从宕机事件中恢复的成熟度和能力层次各不相同。&lt;/p>
&lt;p>团队成员是最重要的高可用性资产，所以为恢复制定一个好的流程非常重要。拥有熟练技能、应变能力、训练有素的雇员，以及处理紧急事件的详细文档和经过仔细测试的流程，对从宕机中恢复有巨大的作用。但也不能完全依赖工具和系统，因为它们并不能理解实际情况的细微差别，有时候它们的行为在一般情况下是正确的，但在某些场景下却会是个灾难！&lt;/p>
&lt;p>对宕机事件进行评估有助于提升组织学习能力，可以帮助避免未来发生相似的错误，但是不要对“事后反思”或“事后的调查分析”期待太高。后见之明被严重曲解，并且一味想找到导致问题的唯一根源，这可能会影响你的判断力(3)。许多流行的方法，例如“五个为什么”，可能会被过度使用，导致一些人将他们的精力集中在找到唯一的替罪羊。很难去回顾我们解决的问题当时所处的状况，也很难理解真正的原因，因为原因通常是多方面的。因此，尽管事后反思可能是有用的，但也应该对结论有所保留。即使是我们给出的建议，也是基于长期研究导致宕机事件的原因以及如何预防它们所得，并且只是我们的观点而已。&lt;/p>
&lt;p>这里我们要反复提醒：所有的宕机事件都是由多方面的失效联合在一起导致的。因此，可以通过利用合适的方法确保单点的安全来避免。整个链条必须要打断，而不仅仅是单个环节。例如，那些向我们求助恢复数据的人不仅遭受数据丢失（存储失效，DBA误操作等），同时还缺少一个可用的备份。&lt;/p>
&lt;p>这样说来，当开始调查并尝试阻止失效或加速恢复时，大多数人和组织不应太过于内疚，而是要专注于技术上的一些措施——特别是那些很酷的方法，例如集群系统和冗余架构。这些是有用的，但要记住这些系统依然会失效。事实上，在本书第二版中提到的MMM复制管理，我们已经失去了兴趣，因为它被证明可能导致更多的宕机时间。你应该不会奇怪一组Perl脚本会陷于混乱，但即使是特别昂贵并精密设计的系统也会出现灾难性的失效——是的，即使是花费了大量金钱的SAN也是如此。我们已经见过太多的SAN失效。&lt;/p>
&lt;h1 id="124避免单点失效">12.4　避免单点失效&lt;a class="anchor" href="#124%e9%81%bf%e5%85%8d%e5%8d%95%e7%82%b9%e5%a4%b1%e6%95%88">#&lt;/a>&lt;/h1>
&lt;p>找到并消除系统中的可能失效的单点，并结合切换到备用组件的机制，这是一种通过减少恢复时间（MTTR）来改善可用性的方法。如果你够聪明，有时候甚至能将实际的恢复时间降低至0，但总的来说这很困难。（即使一些非常引人注目的技术，例如昂贵的负载均衡器，在发现问题并进行反馈时也会导致一定的延迟。）&lt;/p>
&lt;p>思考并梳理整个应用，尝试去定位任何可能失效的单点。是一个硬盘驱动器，一台服务器，一台交换或路由器，还是某个机架的电源？所有数据都在一个数据中心，或者冗余数据中心是由同一个公司提供的吗？系统中任何不冗余的部分都是一个可能失效的单点。其他比较普遍的单点失效依赖于一些服务，例如DNS、单一网络提供商(4)、单个云“可用区域”，以及单个电力输送网，具体有哪些取决于你的关注点。&lt;/p>
&lt;p>单点失效并不总是能够消除。增加冗余或许也无法做到，因为有些限制无法避开，例如地理位置，预算，或者时间限制等。试着去理解每一个影响可用性的部分，采取一种平衡的观点来看待风险，并首先解决其中影响最大的那个。一些人试图编写一个软件来处理所有的硬件失效，但软件本身导致的宕机时间可能比它节约的还要多。也有人想建立一种“永不沉没”的系统，包括各种冗余，但他们忘记了数据中心可能掉电或失去连接。或许他们彻底忘记了恶意攻击者和程序错误的可能性，这些情况可能会删除或损坏数据——一个不小心执行的DROP TABLE也会产生宕机时间。&lt;/p>
&lt;p>可以采用两种方法来为系统增加冗余：增加空余容量和重复组件。增加容量余量通常很简单——可以使用本章或前一章讨论的任何技术。一个提升可用性的方法是创建一个集群或服务器池，并使用负载均衡解决方案。如果一台服务器失效，其他服务器可以接管它的负载。有些人有意识地不使用组件的全部能力，这样可以保留一些“动态余量”来处理因为负载增加或组件失效导致的性能问题。&lt;/p>
&lt;p>出于很多方面的考虑会需要冗余组件，并在主要组件失效时能有一个备件来随时替换。冗余组件可以是空闲的网卡、路由器或者硬盘驱动器——任何能想到的可能失效的东西。完全冗余MySQL服务器可能有点困难，因为一个服务器在没有数据时毫无用处。这意味着你必须确保备用服务器能够获得主服务器上的数据。共享或复制存储是一个比较流行的办法，但这真的是一个高可用性架构吗？让我们深入其中看看。&lt;/p>
&lt;h2 id="1241共享存储或磁盘复制">12.4.1　共享存储或磁盘复制&lt;a class="anchor" href="#1241%e5%85%b1%e4%ba%ab%e5%ad%98%e5%82%a8%e6%88%96%e7%a3%81%e7%9b%98%e5%a4%8d%e5%88%b6">#&lt;/a>&lt;/h2>
&lt;p>共享存储能够为数据库服务器和存储解耦合，通常使用的是SAN。使用共享存储时，服务器能够正常挂载文件系统并进行操作。如果服务器挂了，备用服务器可以挂载相同的文件系统，执行需要的恢复操作，并在失效服务器的数据上启动MySQL。这个过程在逻辑上跟修复那台故障的服务器没什么两样，不过更快速，因为备用服务器已经启动，随时可以运行。当开始故障转移时，检查文件系统、恢复InnoDB以及预热(5)是最有可能遇到延迟的地方，但检测失效本身在许多设置中也会花费很长时间。&lt;/p></description></item><item><title>第11章可扩展的MySQL</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC11%E7%AB%A0%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84MySQL/</link><pubDate>Sun, 05 Jan 2025 08:42:05 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC11%E7%AB%A0%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84MySQL/</guid><description>&lt;p>第11章　可扩展的MySQL&lt;/p>
&lt;p>本章将展示如何构建一个基于MySQL的应用，并且当规模变得越来越庞大时，还能保证快速、高效并且经济。&lt;/p>
&lt;p>有些应用仅仅适用于一台或少数几台服务器，那么哪些可扩展性建议是和这些应用相关的呢？大多数人从不会维护超大规模的系统，并且通常也无法效仿在主流大公司所使用的策略。本章会涵盖这一系列的策略。我们已经建立或者协助建立了许多应用，包括从单台或少量服务器的应用到使用上千台服务器的应用。选择一个合适的策略能够大大地节约时间和金钱。&lt;/p>
&lt;p>MySQL经常被批评很难进行扩展，有些情况下这种看法是正确的，但如果选择正确的架构并很好地实现，就能够非常好地扩展MySQL。但是扩展性并不是一个很好理解的主题，所以我们先来理清一些容易混淆的地方。&lt;/p>
&lt;h1 id="111什么是可扩展性">11.1　什么是可扩展性&lt;a class="anchor" href="#111%e4%bb%80%e4%b9%88%e6%98%af%e5%8f%af%e6%89%a9%e5%b1%95%e6%80%a7">#&lt;/a>&lt;/h1>
&lt;p>人们常常把诸如“可扩展性”、“高可用性”以及“性能”等术语在一些非正式的场合用作同义词，但事实上它们是完全不同的。在第3章已经解释过，我们将性能定义为响应时间。我们也可以很精确地定义可扩展性，稍后将完整讨论。简要地说，可扩展性表明了当需要增加资源以执行更多工作时系统能够获得划算的等同提升（equal bang for the buck）的能力。缺乏扩展能力的系统在达到收益递减的转折点后，将无法进一步增长。&lt;/p>
&lt;p>容量是一个和可扩展性相关的概念。系统容量表示在一定时间内能够完成的工作量(1)，但容量必须是可以有效利用的。系统的最大吞吐量并不等同于容量。大多数基准测试能够衡量一个系统的最大吞吐量，但真实的系统一般不会使用到极限。如果达到最大吞吐量，则性能会下降，并且响应时间会变得不可接受地大且非常不稳定。我们将系统的真实容量定义为在保证可接受的性能的情况下能够达到的吞吐量。这就是为什么基准测试的结果通常不应该简化为一个单独的数字。&lt;/p>
&lt;p>容量和可扩展性并不依赖于性能。以高速公路上的汽车来类比的话：&lt;/p>
&lt;ul>
&lt;li>性能是汽车的时速。&lt;/li>
&lt;li>容量是车道数乘以最大安全时速。&lt;/li>
&lt;li>可扩展性就是在不减慢交通的情况下，能增加更多车和车道的程度。&lt;/li>
&lt;/ul>
&lt;p>在这个类比中，可扩展性依赖于多个条件：换道设计得是否合理、路上有多少车抛锚或者发生事故，汽车行驶速度是否不同或者是否频繁变换车道——但一般来说和汽车的引擎是否强大无关。这并不是说性能不重要，性能确实重要，只是需要指出，即使系统性能不是很高也可以具备可扩展性。&lt;/p>
&lt;p>从较高层次看，可扩展性就是能够通过增加资源来提升容量的能力。&lt;/p>
&lt;p>即使MySQL架构是可扩展的，但应用本身也可能无法扩展，如果很难增加容量，不管原因是什么，应用都是不可扩展的。之前我们从吞吐量方面来定义容量，但同样也需要从较高的层次来看待容量问题。从有利的角度来看，容量可以简单地认为是处理负载的能力，从不同的角度来考虑负载很有帮助。&lt;/p>
&lt;p>数据量&lt;/p>
&lt;p>应用所能累积的数据量是可扩展性最普遍的挑战，特别是对于现在的许多互联网应用而言，这些应用从不删除任何数据。例如社交网站，通常从不会删除老的消息或评论。&lt;/p>
&lt;p>用户量&lt;/p>
&lt;p>即使每个用户只有少量的数据，但在累计到一定数量的用户后，数据量也会开始不成比例地增长且速度快过用户数增长。更多的用户意味着要处理更多的事务，并且事务数可能和用户数不成比例。最后，大量用户（以及更多的数据）也意味着更多复杂的查询，特别是查询跟用户关系相关时（用户间的关联数可以用N×（N−1）来计算，这里N表示用户数）。&lt;/p>
&lt;p>用户活跃度&lt;/p>
&lt;p>不是所有的用户活跃度都相同，并且用户活跃度也不总是不变的。如果用户突然变得活跃，例如由于增加了一个吸引人的新特性，那么负载可能会明显提升。用户活跃度不仅仅指页面浏览数，即使同样的页面浏览数，如果网站的某个需要执行大量工作的部分变得流行，也可能导致更多的工作。另外，某些用户也会比其他用户更活跃：他们可能比一般人有更多的朋友、消息和照片。&lt;/p>
&lt;p>相关数据集的大小&lt;/p>
&lt;p>如果用户间存在关系，应用可能需要在整个相关联用户群体上执行查询和计算，这比处理一个一个的用户和用户数据要复杂得多。社交网站经常会遇到由那些人气很旺的用户组或朋友很多的用户所带来的挑战(2)。&lt;/p>
&lt;h2 id="1111正式的可扩展性定义">11.1.1　正式的可扩展性定义&lt;a class="anchor" href="#1111%e6%ad%a3%e5%bc%8f%e7%9a%84%e5%8f%af%e6%89%a9%e5%b1%95%e6%80%a7%e5%ae%9a%e4%b9%89">#&lt;/a>&lt;/h2>
&lt;p>有必要探讨一下可扩展性在数学上的定义了，这有助于在更高层次的概念上清晰地理解可扩展性。如果没有这样的基础，就可能无法理解或精确地表达可扩展性。不过不用担心，这里不会涉及高等数学，即使不是数学天才，也能够很直观地理解它。&lt;/p>
&lt;p>关键是之前我们使用的短语：“划算的等同提升（equal bang for the buck）”。另一种说法是，可扩展性是当增加资源以处理负载和增加容量时系统能够获得的投资产出率（ROI）。假设有一个只有一台服务器的系统，并且能够测量它的最大容量，如图11-1所示。&lt;/p>
&lt;p>&lt;img src="img/000001.jpeg" alt="" />
&lt;strong>图11-1：一个只有一台服务器的系统&lt;/strong>&lt;/p>
&lt;p>假设现在我们增加一台服务器，系统的能力加倍，如图11-2所示。&lt;/p>
&lt;p>&lt;img src="img/000002.jpeg" alt="" />
&lt;strong>图11-2：一个线性扩展的系统能由两台服务器获得两倍容量&lt;/strong>&lt;/p>
&lt;p>这就是线性扩展。我们增加了一倍的服务器，结果增加了一倍的容量。大部分系统并不是线性扩展的，而是如图11-3所示的扩展方式。&lt;/p>
&lt;p>&lt;img src="img/000003.jpeg" alt="" />
&lt;strong>图11-3：一个非线性扩展的系统&lt;/strong>&lt;/p>
&lt;p>大部分系统都只能以比线性扩展略低的扩展系数进行扩展。越高的扩展系数会导致越大的线性偏差。事实上，多数系统最终会达到一个最大吞吐量临界点，超过这个点后增加投入反而会带来负回报——继续增加更多工作负载，实际上会降低系统的吞吐量。(3)&lt;/p>
&lt;p>这怎么可能呢？这些年产生了许多可扩展性模型，它们有着不同程度的良好表现和实用性。我们这里所讲的可扩展性模型是基于某些能够影响系统扩展的内在机制。这就是Neil J. Gunther博士提出的通用可扩展性定律（Universal Scalability Law，USL）。Gunther博士将这些详尽地写到了他的书中，包括&lt;em>Guerrilla Capacity Planning&lt;/em> （Springer）。这里我们不会深入到背后的数学理论中，如果你对此感兴趣，他撰写的书籍以及由他的公司Performance Dynamics提供的训练课程可能是比较好的资源。(4)&lt;/p>
&lt;p>简而言之，USL说的是线性扩展的偏差可通过两个因素来建立模型：无法并发执行的一部分工作，以及需要交互的另外一部分工作。为第一个因素建模就有了著名的Amdahl定律，它会导致吞吐量趋于平缓。如果部分任务无法并行，那么不管你如何分而治之，该任务至少需要串行部分的时间。&lt;/p>
&lt;p>增加第二个因素——内部节点间或者进程间的通信——到Amdahl定律就得出了USL。这种通信的代价取决于通信信道的数量，而信道的数量将按照系统内工作者数量的二次方增长。因此最终开销比带来的收益增长得更快，这是产生扩展性倒退的原因。图11-4阐明了目前讨论到的三个概念：线性扩展、Amdahl扩展，以及USL扩展。大多数真实系统看起来更像USL曲线。&lt;/p>
&lt;p>&lt;img src="img/000004.jpeg" alt="" />
&lt;strong>图11-4：线性扩展、AmdahI扩展以及USL扩展定律&lt;/strong>&lt;/p>
&lt;p>USL可以应用于硬件和软件领域。对于硬件，横轴表示硬件的数量，例如服务器数量或CPU数量。每个硬件的工作量、数据大小以及查询的复杂度必须保持为常量(5)。对于软件，横轴表示并发度，例如用户数或线程数。每个并发的工作量必须保持为常量。&lt;/p>
&lt;p>有一点很重要，USL并不能完美地描述真实系统，它只是一个简化模型。但这是一个很好的框架，可用于理解为什么系统增长无法带来等同的收益。它也揭示了一个构建高可扩展性系统的重要原则：在系统内尽量避免串行化和交互。&lt;/p>
&lt;p>可以衡量一个系统并使用回归来确定串行和交互的量。你可以将它作为容量规划和性能预测评估的最优上限值。也可以检查系统是怎么偏离USL模型的，将其作为最差下限值以指出系统的哪一部分没有表现出它应有的性能。这两种情况下，USL给出了一个讨论可扩展性的参考。如果没有USL，那即使盯着系统看也无法知道期望的结果是什么。如果想深入了解这个主题，最好去看一下对应的书籍。Gunther博士已经写得很清楚，因此我们不会再深入讨论下去。&lt;/p>
&lt;p>另外一个理解可扩展性问题的框架是约束理论，它解释了如何通过减少依赖事件和统计变化（statistical variation）来改进系统的吞吐量和性能。这在Eliyahu M. Goldratt所撰写的&lt;em>The Goal&lt;/em>（North River）一书中有描述，其中有一个关于管理制造业设备的延伸的比喻。尽管这看起来和数据库服务器没有什么关联，但其中包含的法则和排队理论以及其他运筹学方面是一样的。&lt;/p></description></item><item><title>第10章复制</title><link>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC10%E7%AB%A0%E5%A4%8D%E5%88%B6/</link><pubDate>Sun, 05 Jan 2025 08:42:04 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/MySQL/_%E9%AB%98%E6%80%A7%E8%83%BDMySQL_/%E7%AC%AC10%E7%AB%A0%E5%A4%8D%E5%88%B6/</guid><description>&lt;p>第10章　复制&lt;/p>
&lt;p>MySQL内建的复制功能是构建基于MySQL的大规模、高性能应用的基础，这类应用使用所谓的“水平扩展”的架构。我们可以通过为服务器配置一个或多个备库(1)的方式来进行数据同步。复制功能不仅有利于构建高性能的应用，同时也是高可用性、可扩展性、灾难恢复、备份以及数据仓库等工作的基础。事实上，可扩展性和高可用性通常是相关联的话题，我们会在接下来的三章详细阐述。&lt;/p>
&lt;p>本章将阐述所有与复制相关的内容，首先简要介绍复制如何工作，然后讨论基本的复制服务搭建，包括与复制相关的配置以及如何管理和优化复制服务器。虽然本书的主题是高性能，但对于复制来说，我们同样需要关注其准确性和可靠性，因此我们也会讲述复制在什么情况下会失败，以及如何使其更好地工作。&lt;/p>
&lt;h1 id="101复制概述">10.1　复制概述&lt;a class="anchor" href="#101%e5%a4%8d%e5%88%b6%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;p>复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。一台主库的数据可以同步到多台备库上，备库本身也可以被配置成另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。&lt;/p>
&lt;p>MySQL支持两种复制方式：基于行的复制和基于语句的复制。基于语句的复制（也称为逻辑复制）早在MySQL 3.23版本中就存在，而基于行的复制方式在5.1版本中才被加进来。这两种方式都是通过在主库上记录二进制日志(2)、在备库重放日志的方式来实现异步的数据复制。这意味着，在同一时间点备库上的数据可能与主库存在不一致，并且无法保证主备之间的延迟。一些大的语句可能导致备库产生几秒、几分钟甚至几个小时的延迟。&lt;/p>
&lt;p>MySQL复制大部分是向后兼容的，新版本的服务器可以作为老版本服务器的备库，但反过来，将老版本作为新版本服务器的备库通常是不可行的，因为它可能无法解析新版本所采用的新的特性或语法，另外所使用的二进制文件的格式也可能不相同。例如，不能从MySQL 5.1复制到MySQL 4.0。在进行大的版本升级前，例如从4.1升级到5.0，或从5.1升级到5.5，最好先对复制的设置进行测试。但对于小版本号升级，如从5.1.51升级到5.1.58，则通常是兼容的。通过阅读每次版本更新的ChangeLog可以找到不同版本间做了什么修改。&lt;/p>
&lt;p>复制通常不会增加主库的开销，主要是启用二进制日志带来的开销，但出于备份或及时从崩溃中恢复的目的，这点开销也是必要的。除此之外，每个备库也会对主库增加一些负载（例如网络I/O开销），尤其当备库请求从主库读取旧的二进制日志文件时，可能会造成更高的I/O开销。另外锁竞争也可能阻碍事务的提交。最后，如果是从一个高吞吐量（例如5000或更高的TPS）的主库上复制到多个备库，唤醒多个复制线程发送事件的开销将会累加。&lt;/p>
&lt;p>通过复制可以将读操作指向备库来获得更好的读扩展，但对于写操作，除非设计得当，否则并不适合通过复制来扩展写操作。在一主库多备库的架构中，写操作会被执行多次，这时候整个系统的性能取决于写入最慢的那部分。&lt;/p>
&lt;p>当使用一主库多备库的架构时，可能会造成一些浪费，因为本质上它会复制大量不必要的重复数据。例如，对于一台主库和10台备库，会有11份数据拷贝，并且这11台服务器的缓存中存储了大部分相同的数据。这和在服务器上有11路RAID 1类似。这不是一种经济的硬件使用方式，但这种复制架构却很常见，本章我们将讨论解决这个问题的方法。&lt;/p>
&lt;h2 id="1011复制解决的问题">10.1.1　复制解决的问题&lt;a class="anchor" href="#1011%e5%a4%8d%e5%88%b6%e8%a7%a3%e5%86%b3%e7%9a%84%e9%97%ae%e9%a2%98">#&lt;/a>&lt;/h2>
&lt;p>下面是复制比较常见的用途：&lt;/p>
&lt;p>数据分布&lt;/p>
&lt;p>MySQL复制通常不会对带宽造成很大的压力，但在5.1版本引入的基于行的复制会比传统的基于语句的复制模式的带宽压力更大。你可以随意地停止或开始复制，并在不同的地理位置来分布数据备份，例如不同的数据中心。即使在不稳定的网络环境下，远程复制也可以工作。但如果为了保持很低的复制延迟，最好有一个稳定的、低延迟连接。&lt;/p>
&lt;p>负载均衡&lt;/p>
&lt;p>通过MySQL复制可以将读操作分布到多个服务器上，实现对读密集型应用的优化，并且实现很方便，通过简单的代码修改就能实现基本的负载均衡。对于小规模的应用，可以简单地对机器名做硬编码或使用DNS轮询（将一个机器名指向多个IP地址）。当然也可以使用更复杂的方法，例如网络负载均衡这一类的标准负载均衡解决方案，能够很好地将负载分配到不同的MySQL服务器上。Linux虚拟服务器（Linux Virtual Server，LVS）也能够很好地工作，第11章将详细地讨论负载均衡。&lt;/p>
&lt;p>备份&lt;/p>
&lt;p>对于备份来说，复制是一项很有意义的技术补充，但复制既不是备份也不能够取代备份。&lt;/p>
&lt;p>高可用性和故障切换&lt;/p>
&lt;p>复制能够帮助应用程序避免MySQL单点失败，一个包含复制的设计良好的故障切换系统能够显著地缩短宕机时间，我们将在第12章讨论故障切换。&lt;/p>
&lt;p>MySQL升级测试&lt;/p>
&lt;p>这种做法比较普遍，使用一个更高版本的MySQL作为备库，保证在升级全部实例前，查询能够在备库按照预期执行。&lt;/p>
&lt;h2 id="1012复制如何工作">10.1.2　复制如何工作&lt;a class="anchor" href="#1012%e5%a4%8d%e5%88%b6%e5%a6%82%e4%bd%95%e5%b7%a5%e4%bd%9c">#&lt;/a>&lt;/h2>
&lt;p>在详细介绍如何设置复制之前，让我们先看看MySQL实际上是如何复制数据的。总的来说，复制有三个步骤：&lt;/p>
&lt;ol>
&lt;li>在主库上把数据更改记录到二进制日志（Binary Log）中（这些记录被称为二进制日志事件）。&lt;/li>
&lt;li>备库将主库上的日志复制到自己的中继日志（Relay Log）中。&lt;/li>
&lt;li>备库读取中继日志中的事件，将其重放到备库数据之上。&lt;/li>
&lt;/ol>
&lt;p>以上只是概述，实际上每一步都很复杂，图10-1更详细地描述了复制的细节。&lt;/p>
&lt;p>&lt;img src="img/000002.jpeg" alt="" />
&lt;strong>图10-1：MySQL复制如何工作&lt;/strong>&lt;/p>
&lt;p>第一步是在主库上记录二进制日志（稍后介绍如何设置）。在每次准备提交事务完成数据更新前，主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后，主库会告诉存储引擎可以提交事务了。&lt;/p>
&lt;p>下一步，备库将主库的二进制日志复制到其本地的中继日志中。首先，备库会启动一个工作线程，称为I/O线程，I/O线程跟主库建立一个普通的客户端连接，然后在主库上启动一个特殊的二进制转储（&lt;em>binlog dump&lt;/em>）线程（该线程没有对应的SQL命令），这个二进制转储线程会读取主库上二进制日志中的事件。它不会对事件进行轮询。如果该线程追赶上了主库，它将进入睡眠状态，直到主库发送信号量通知其有新的事件产生时才会被唤醒，备库I/O线程会将接收到的事件记录到中继日志中。&lt;/p>
&lt;p>&lt;img src="img/000001.jpeg" alt="" />MySQL 4.0之前的复制与之后的版本相比改变很大，例如MySQL最初的复制功能没有使用中继日志，所以复制只用到了两个线程，而不是现在的三个线程。目前大部分人都是使用的最新版本，因此在本章我们不会去讨论关于老版本复制的更多细节。&lt;/p>
&lt;p>备库的SQL线程执行最后一步，该线程从中继日志中读取事件并在备库执行，从而实现备库数据的更新。当SQL线程追赶上I/O线程时，中继日志通常已经在系统缓存中，所以中继日志的开销很低。SQL线程执行的事件也可以通过配置选项来决定是否写入其自己的二进制日志中，它对于我们稍后提到的场景非常有用。&lt;/p>
&lt;p>图10-1显示了在备库有两个运行的线程，在主库上也有一个运行的线程：和其他普通连接一样，由备库发起的连接，在主库上同样拥有一个线程。&lt;/p>
&lt;p>这种复制架构实现了获取事件和重放事件的解耦，允许这两个过程异步进行。也就是说I/O线程能够独立于SQL线程之外工作。但这种架构也限制了复制的过程，其中最重要的一点是在主库上并发运行的查询在备库只能串行化执行，因为只有一个SQL线程来重放中继日志中的事件。后面我们将会看到，这是很多工作负载的性能瓶颈所在。虽然有一些针对该问题的解决方案，但大多数用户仍然受制于单线程。&lt;/p>
&lt;h1 id="102配置复制">10.2　配置复制&lt;a class="anchor" href="#102%e9%85%8d%e7%bd%ae%e5%a4%8d%e5%88%b6">#&lt;/a>&lt;/h1>
&lt;p>为MySQL服务器配置复制非常简单。但由于场景不同，基本的步骤还是有所差异。最基本的场景是新安装的主库和备库，总的来说分为以下几步：&lt;/p>
&lt;ol>
&lt;li>在每台(3)服务器上创建复制账号。&lt;/li>
&lt;li>配置主库和备库。&lt;/li>
&lt;li>通知备库连接到主库并从主库复制数据。&lt;/li>
&lt;/ol>
&lt;p>这里我们假定大部分配置采用默认值即可，在主库和备库都是全新安装并且拥有同样的数据（默认MySQL数据库）时这样的假设是合理的。接下来我们将展示如何一步步配置复制：假设有服务器server1（IP地址192.168.0.1）和服务器server2（IP地址192.168.0.2），我们将解释如何给一个已经运行的服务器配置备库，并探讨推荐的复制配置。&lt;/p>
&lt;h2 id="1021创建复制账号">10.2.1　创建复制账号&lt;a class="anchor" href="#1021%e5%88%9b%e5%bb%ba%e5%a4%8d%e5%88%b6%e8%b4%a6%e5%8f%b7">#&lt;/a>&lt;/h2>
&lt;p>MySQL会赋予一些特殊的权限给复制线程。在备库运行的I/O线程会建立一个到主库的TCP/IP连接，这意味着必须在主库创建一个用户，并赋予其合适的权限。备库I/O线程以该用户名连接到主库并读取其二进制日志。通过如下语句创建用户账号：&lt;/p>
&lt;pre>&lt;code> mysql&amp;gt; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.*
 -&amp;gt; TO repl@'192.168.0.%' IDENTIFIED BY 'p4ssword',;
&lt;/code>&lt;/pre>
&lt;p>我们在主库和备库都创建该账号。注意我们把这个账户限制在本地网络，因为这是一个特权账号（尽管该账号无法执行select或修改数据，但仍然能从二进制日志中获得一些数据）。&lt;/p></description></item></channel></rss>