<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>archive</title><link>https://archive-w.netlify.app/doc/framework/mysql/book/</link><description>Recent content on archive</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://archive-w.netlify.app/doc/framework/mysql/book/index.xml" rel="self" type="application/rss+xml"/><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/01_recognize/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/01_recognize/</guid><description>&lt;h2 id="bin目录可执行文件">
 BIN目录可执行文件
 &lt;a class="anchor" href="#bin%e7%9b%ae%e5%bd%95%e5%8f%af%e6%89%a7%e8%a1%8c%e6%96%87%e4%bb%b6">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="查看目录">
 查看目录
 &lt;a class="anchor" href="#%e6%9f%a5%e7%9c%8b%e7%9b%ae%e5%bd%95">#&lt;/a>
&lt;/h3>
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="shell" data-line="6,15-17" class="language-shell line-numbers" style="max-height: none">&lt;code class="language-shell"># tree -L 3 /usr/local/opt/mysql@5.7/bin/

/usr/local/opt/mysql@5.7/bin/
├── innochecksum
├── mysql
├── mysql.server -&amp;gt; ../support-files/mysql.server
├── mysql_client_test
├── mysql_client_test_embedded
├── mysql_install_db
├── mysql_plugin
├── mysql_secure_installation
├── mysql_upgrade
├── mysqladmin
├── mysqlbinlog
├── mysqld
├── mysqld_multi
├── mysqld_safe
├── mysqldump
├── mysqldumpslow
├── mysqlshow
├── mysqltest
└── ...
&lt;/code>&lt;/pre>&lt;/div>
&lt;/li>
&lt;/ul>
&lt;h2 id="启动mysql服务器程序">
 启动Mysql服务器程序
 &lt;a class="anchor" href="#%e5%90%af%e5%8a%a8mysql%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%a8%8b%e5%ba%8f">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="unix里启动服务器程序">
 Unix里启动服务器程序
 &lt;a class="anchor" href="#unix%e9%87%8c%e5%90%af%e5%8a%a8%e6%9c%8d%e5%8a%a1%e5%99%a8%e7%a8%8b%e5%ba%8f">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>mysqld&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>mysqld这个可执行文件代表mysql服务器程序，运行这个可执行文件就可以直接启动一个服务进程，但是这个命令不常用，一般会结合其他脚本使用。 比如下面这个。&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>mysqld_safe&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>mysqld_safe是一个可执行脚本，它会嫁接调用mysqld。而且还顺带启动了一个监控进程。这个监控进程会在服务器挂了的时候重新启动它。另外，使用脚本启动的时候，它会将服务器的出错信息和其他诊断信息重新定向到某个文件中，产生错误日志，这样可以方便我们找出发生错误的原因。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/02_cmd-and-system-variables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/02_cmd-and-system-variables/</guid><description>&lt;p>hello&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/03_character_and_collation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/03_character_and_collation/</guid><description>&lt;h2 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;/h2>
&lt;h3 id="字符集简介">
 字符集简介
 &lt;a class="anchor" href="#%e5%ad%97%e7%ac%a6%e9%9b%86%e7%ae%80%e4%bb%8b">#&lt;/a>
&lt;/h3>
&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>的概念来描述某个字符范围的编码规则。比方说我们来自定义一个名称为 xiaohaizi的字符集，它包括的字符范围和编码规则如下：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>包含的字符&amp;rsquo;a&amp;rsquo;、&amp;lsquo;b&amp;rsquo;、&amp;lsquo;A&amp;rsquo;、&amp;lsquo;B&amp;rsquo;&lt;/p>
&lt;/li>
&lt;li>
&lt;p>编码的规则如下：&lt;/p>
&lt;p>采用一个字节编码一个字符的形式，字符和字节的映射关系如下：
&amp;lsquo;a&amp;rsquo; -&amp;gt; 00000001 (十六进制：0x01)
&amp;lsquo;b&amp;rsquo; -&amp;gt; 00000010 (十六进制：0x02)
&amp;lsquo;A&amp;rsquo; -&amp;gt; 00000011 (十六进制：0x03)
&amp;lsquo;B&amp;rsquo; -&amp;gt; 00000100 (十六进制：0x04)&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>有了xiaohaizi 字符集，我们就可以用二进制形式表示一些字符串了，下边是一些字符串用 xiaohaizi字符编码后的二进制表示:&lt;/p>
&lt;pre>&lt;code>'bA' -&amp;gt; 0000001000000011 (十六进制：0x0203)
'baB' -&amp;gt; 000000100000000100000100 (十六进制：0x020104)
'cd' -&amp;gt; 无法表示，字符集xiaohaizi不包含字符'c'和'd'
&lt;/code>&lt;/pre>
&lt;h3 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;/h3>
&lt;p>在我们确定了 xiaohaizi 字符集表示字符的范围以及编码规则后，怎么比较两个字符的大小呢？最容易想到的就是直接比较这两个字符对应的二进制编码的大小，比方说字符 &amp;lsquo;a&amp;rsquo; 的编码为 0x01 ，字符 &amp;lsquo;b&amp;rsquo; 的编码为 0x02 ，所以 &amp;lsquo;a&amp;rsquo; 小于 &amp;lsquo;b&amp;rsquo; ，这种简单的比较规则也可以被称为二进制比较规则，英文名为&lt;code>binary collation&lt;/code> 。二进制比较规则是简单，但有时候并不符合现实需求，比如在很多场合对于英文字符我们都是不区分大小写的，也就是说 &amp;lsquo;a&amp;rsquo; 和 &amp;lsquo;A&amp;rsquo; 是相等的，在这种场合下就不能简单粗暴的使用二进制比较规则了，这时候我们可以这样指定比较规则：&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/04_innodb-record-struct/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/04_innodb-record-struct/</guid><description>&lt;ul>
&lt;li>
&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;div class="alert callout note">&lt;p class="title">&lt;span class="icon fas fa-comment">&lt;/span> Comment &lt;/p>&lt;p>到现在为止， MySQL 对于我们来说还是一个黑盒，我们只负责使用客户端发送请求并等待服务器返回结果，表中的数据到底存到了哪里？以什么格式存放的？ MySQL 是以什么方式来访问的这些数据？我们前边唠叨请求处理过程的时候提到过， MySQL 服务器上负责对表中数据的读取和写入工作的部分是 &lt;code>存储引擎&lt;/code> ，而服务器又支持不同类型的存储引擎，比如 &lt;code>InnoDB&lt;/code> 、 &lt;code>MyISAM&lt;/code> 、 &lt;code>Memory&lt;/code> 啥的，不同的存储引擎一般是由不同的人为实现不同的特性而开发的，真实数据在不同存储引擎中存放的格式一般是不同的，甚至有的存储引擎比如 Memory 都不用磁盘来存储数据，也就是说关闭服务器后表中的数据就消失了。&lt;strong>由于 InnoDB 是 MySQL 默认的存储引擎，也是我们最常用到的存储引擎&lt;/strong>，我们也没有那么多时间去把各个存储引擎的内部实现都看一遍，所以本集要唠叨的是使用 InnoDB 作为存储引擎的数据存储结构。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 id="innodb架构">
 InnoDB架构
 &lt;a class="anchor" href="#innodb%e6%9e%b6%e6%9e%84">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon-attention">&lt;/span> Attention &lt;/p>&lt;p> 


 &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html" rel="noopener" target="_blank">https://dev.mysql.com/doc/refman/5.7/en/innodb-architecture.html&lt;/a>&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 id="innodb页简介">
 InnoDB页简介
 &lt;a class="anchor" href="#innodb%e9%a1%b5%e7%ae%80%e4%bb%8b">#&lt;/a>
&lt;/h2>
&lt;p class="warn">InnoDB 是一个将表中的数据存储到磁盘上的存储引擎，所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的，所以需要把磁盘中的数据加载到内存中，如果是处理写入或修改请求的话，还需要把内存中的内容刷新到磁盘上。而我们知道读写磁盘的速度非常慢，和内存读写差了几个数量级，所以当我们想从表中获取某些记录时， InnoDB 存储引擎需要一条一条的把记录从磁盘上读出来么？不，那样会慢死，InnoDB 采取的方式是：&lt;span style="color: blue">将数据划分为若干个页，以页作为磁盘和内存之间交互的基本单位，InnoDB中页的大小一般为 16KB: &lt;code>show variables like 'innodb_page_size';&lt;/code>&lt;/span> 也就是在一般情况下，一次最少从磁盘中读取16KB的内容到内存中，一次最少把内存中的16KB内容刷新到磁盘中。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="innodb行格式">
 InnoDB行格式
 &lt;a class="anchor" href="#innodb%e8%a1%8c%e6%a0%bc%e5%bc%8f">#&lt;/a>
&lt;/h2>
&lt;p class="warn">我们平时是以记录为单位来向表中插入数据的，这些记录在磁盘上的存放方式(&lt;code>ROW_FORMAT&lt;/code>)也被称为 &lt;code>行格式&lt;/code> 或者 &lt;code>记录格式&lt;/code> 。设计 InnoDB 存储引擎的大叔们到现在为止设计了4种不同类型的 行格式 ，分别是:
&lt;br> &lt;code>Compact&lt;/code> 、 &lt;code>Redundant&lt;/code>(MySQL5.0 之前用的一种行格式) 、&lt;code>Dynamic&lt;/code> 和 &lt;code>Compressed&lt;/code> 行格式。
&lt;br>随着时间的推移，他们可能会设计出更多的行格式，但是不管怎么变，在原理上大体都是相同的。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/05_innodb-page-struct/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/05_innodb-page-struct/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">前边我们简单提了一下 &lt;strong>页&lt;/strong> 的概念，它是 InnoDB 管理存储空间的基本单位，一个页的大小一般是 &lt;em>&lt;strong>16KB&lt;/strong>&lt;/em> 。InnoDB 为了不同的目的而设计了许多种 


 
 

 
 
 
 
 
 
 
 &lt;a href='#fil_page_type' rel="noopener" class="internal-link" data-src="#fil_page_type">不同类型的页&lt;/a> ，比如存放&lt;code>表空间头部信息&lt;/code>的页，存放&lt;code>Insert Buffer信息&lt;/code>的页，存放&lt;code>INODE 信息&lt;/code>的页，存放&lt;code>undo 日志信息&lt;/code>的页等等等等。当然了，如果我说的这些名词你一个都没有听过，就当我放了个屁吧～ 不过这没有一毛钱关系，我们今儿个也不准备说这些类型的页，我们聚焦的是那些存放我们表中记录的那种类型的页，官方称这种存放记录的页为 &lt;strong>&lt;code>索引（ INDEX ）页&lt;/code>&lt;/strong> ，鉴于我们还没有了解过索引是个什么东西，而这些表中的记录就是我们日常口中所称的数据 ，所以目前还是叫这种存放记录的页为 &lt;strong>数据页&lt;/strong> 吧。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 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%e9%a2%84%e8%a7%88">#&lt;/a>
&lt;/h2> &lt;div class="docsify-example-panels"> &lt;div class="docsify-example-panel left-panel"style="max-width: 40%; width: 40%;">
&lt;p class="warn">数据页代表的这块 16KB 大小的存储空间可以被划分为多个部分，不同部分有不同的功能，各个部分如图所示：&lt;/p>
&lt;p>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/05_innodb_page_struct/ips-01.png" alt="" width="65%">&lt;/p>&lt;/div>
 &lt;div class="docsify-example-panel right-panel"style="max-width: 60%; width: 60%;">
&lt;p class="warn">从图中可以看出，一个 InnoDB 数据页的存储空间大致被划分成了 7 个部分，有的部分占用的字节数是确定的，有的部分占用的字节数是不确定的。下边我们用表格的方式来大致描述一下这7个部分都存储一些啥内容（快速的瞅一眼就行了，后边会详细唠叨的）：&lt;/p>
&lt;p>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/05_innodb_page_struct/ips-02.png" alt="" width="100%">&lt;/p>&lt;/div>&lt;/div>

&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">在页的7个组成部分中，我们自己存储的记录会按照我们指定的 &lt;strong>行格式&lt;/strong> 存储到 User Records 部分。但是在一开始生成页的时候，其实并没有 User Records 这个部分，每当我们插入一条记录，都会从 Free Space 部分，也就是尚未使用的存储空间中申请一个记录大小的空间划分到 User Records 部分，当 Free Space 部分的空间全部被 User Records 部分替代掉之后，也就意味着这个页使用完了，如果还有新的记录插入的话，就需要去申请新的页了，这个过程的图示如下：&lt;/p>
&lt;p style="text-align: center;">&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/05_innodb_page_struct/ips-03.png" alt="" width="90%">&lt;/p>
&lt;div class="alert callout warning">&lt;p class="title">&lt;span class="icon icon-warning">&lt;/span> Warning &lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/06_B+tree_index/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/06_B+tree_index/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="前情提要">
 前情提要
 &lt;a class="anchor" href="#%e5%89%8d%e6%83%85%e6%8f%90%e8%a6%81">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> prerequirement &lt;/p>&lt;p> 前边我们详细唠叨了 InnoDB 数据页的 7 个组成部分，知道了各个数据页可以组成一个 &lt;em>双向链表&lt;/em> ，而每个数据页中的记录会按照主键值从小到大的顺序组成一个 &lt;em>单向链表&lt;/em> ，每个数据页都会为存储在它里边儿的记录生成一个 


 
 

 
 
 
 
 
 
 
 &lt;a href='../05_innodb-page-struct/#page-directory%e9%a1%b5%e7%9b%ae%e5%bd%95' rel="noopener" class="internal-link" data-src="../05_innodb-page-struct/#page-directory%e9%a1%b5%e7%9b%ae%e5%bd%95">页目录&lt;/a> ，在&lt;code>单页&lt;/code>中通过主键查找某条记录的时候可以 &lt;strong>页目录(page-directory)&lt;/strong> 中使用二分法快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录（如果你对这段话有一丁点儿疑惑，那么接下来的部分不适合你，返回去看一下 


 
 

 
 
 
 
 
 
 
 &lt;a href='../05_innodb-page-struct/#%e6%95%b0%e6%8d%ae%e9%a1%b5%e7%bb%93%e6%9e%84%e7%9a%84%e5%bf%ab%e9%80%9f%e9%a2%84%e8%a7%88' rel="noopener" class="internal-link" data-src="../05_innodb-page-struct/#%e6%95%b0%e6%8d%ae%e9%a1%b5%e7%bb%93%e6%9e%84%e7%9a%84%e5%bf%ab%e9%80%9f%e9%a2%84%e8%a7%88">数据页结构&lt;/a> 吧）。页和记录的关系示意图如下：
&lt;br>&lt;br>其中页a、页b、页c &amp;hellip; 页n 这些页可以不在物理结构上相连，只要通过双向链表相关联即可。&lt;/p>
&lt;/p>&lt;/div>
&lt;p style="text-align: center;">&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/06_B+tree_index/bti-01.png" alt="" width="90%">&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">本集的主题是 &lt;strong>索引&lt;/strong>，在正式介绍 索引 之前，我们需要了解一下没有索引的时候是怎么查找记录的。为了方便大家理解，我们下边先只唠叨搜索条件为对某个列精确匹配的情况，所谓精确匹配，就是搜索条件中用等于 = 连接起的表达式，比如这样：
&lt;br>&lt;code>SELECT [列名列表] FROM 表名 WHERE 列名 = xxx;&lt;/code>&lt;/p>
&lt;ul>
&lt;li>
&lt;h3 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;/h3>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p>假设目前表中的记录比较少，所有的记录都可以被存放到一个页中，在查找记录的时候可以根据搜索条件的不同分为两种情况：
&lt;br>&lt;span style='padding-left:2.0em'>1. &lt;code>以主键为搜索条件&lt;/code>: 这个查找过程我们已经很熟悉了，可以在 页目录 中使用二分法快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
&lt;br>&lt;span style='padding-left:2.0em'>2. &lt;code>以其他列作为搜索条件&lt;/code>: 对非主键列的查找的过程可就不这么幸运了，因为在数据页中并没有对非主键列建立所谓的 页目录 ，所以我们无法通过二分法快速定位相应的 槽 。这种情况下只能从 最小记录 开始依次遍历单链表中的每条记录，然后对比每条记录是不是符合搜索条件。很显然，这种查找的效率是非常低的。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h3 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;/h3>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/07_B+tree_index_use/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/07_B+tree_index_use/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="书接上文">
 书接上文
 &lt;a class="anchor" href="#%e4%b9%a6%e6%8e%a5%e4%b8%8a%e6%96%87">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> prerequirement &lt;/p>&lt;p> 我们前边详细、详细又详细的唠叨了 


 
 

 
 
 
 
 
 
 
 &lt;a href='../06_B&amp;#43;tree_index/' rel="noopener" class="internal-link" data-src="../06_B&amp;#43;tree_index/">InnoDB 存储引擎的 B+ 树索引&lt;/a>，我们必须熟悉下边这些结论：
&lt;br>&lt;code>1).&lt;/code> 每个索引都对应一棵 B+ 树，B+ 树分为好多层，最下边一层是叶子节点，其余的是内节点。所有&lt;strong>用户记录&lt;/strong>都存储在 B+ 树的叶子节点，所有&lt;strong>目录项记录&lt;/strong>都存储在内节点。
&lt;br>&lt;code>2).&lt;/code> InnoDB 存储引擎会自动为主键（如果没有它会自动帮我们添加）建立 


 
 

 
 
 
 
 
 
 
 &lt;a href='../06_B&amp;#43;tree_index/#%e8%81%9a%e7%b0%87%e7%b4%a2%e5%bc%95' rel="noopener" class="internal-link" data-src="../06_B&amp;#43;tree_index/#%e8%81%9a%e7%b0%87%e7%b4%a2%e5%bc%95">聚簇索引&lt;/a>，聚簇索引的叶子节点包含完整的用户记录。
&lt;br>&lt;code>3).&lt;/code> 我们可以为自己感兴趣的列建立 


 
 

 
 
 
 
 
 
 
 &lt;a href='../06_B&amp;#43;tree_index/#%e4%ba%8c%e7%ba%a7%e7%b4%a2%e5%bc%95%e8%be%85%e5%8a%a9%e7%b4%a2%e5%bc%95' rel="noopener" class="internal-link" data-src="../06_B&amp;#43;tree_index/#%e4%ba%8c%e7%ba%a7%e7%b4%a2%e5%bc%95%e8%be%85%e5%8a%a9%e7%b4%a2%e5%bc%95">二级索引&lt;/a>，二级索引的叶子节点包含的用户记录由&lt;strong>索引列+主键&lt;/strong>组成，所以如果想通过&lt;strong>二级索引&lt;/strong>来查找完整的用户记录的话，需要通过 


 
 

 
 
 
 
 
 
 
 &lt;a href='../06_B&amp;#43;tree_index/#:~:text=%e6%88%91%e4%bb%ac%e6%a0%b9%e6%8d%ae%e8%bf%99%e4%b8%aa%e4%bb%a5,%e8%a2%ab%e7%a7%b0%e4%b8%ba%e5%9b%9e%e8%a1%a8' rel="noopener" class="internal-link" data-src="../06_B&amp;#43;tree_index/#:~:text=%e6%88%91%e4%bb%ac%e6%a0%b9%e6%8d%ae%e8%bf%99%e4%b8%aa%e4%bb%a5,%e8%a2%ab%e7%a7%b0%e4%b8%ba%e5%9b%9e%e8%a1%a8">回表&lt;/a> 操作，也就是在通过二级索引找到主键值之后再到&lt;strong>聚簇索引&lt;/strong>中查找完整的用户记录。
&lt;br>&lt;code>4).&lt;/code> B+ 树中每层节点都是按照索引列值从小到大的顺序排序而组成了双向链表，而且每个页内的记录（不论是用户记录还是目录项记录）都是按照索引列的值从小到大的顺序而形成了一个单链表。如果是 


 
 

 
 
 
 
 
 
 
 &lt;a href='../06_B&amp;#43;tree_index/#%e8%81%94%e5%90%88%e7%b4%a2%e5%bc%95' rel="noopener" class="internal-link" data-src="../06_B&amp;#43;tree_index/#%e8%81%94%e5%90%88%e7%b4%a2%e5%bc%95">联合索引&lt;/a> 的话，则页面和记录先按照&lt;strong>联合索引&lt;/strong>前边的列排序，如果该列值相同，再按照&lt;strong>联合索引&lt;/strong>后边的列排序。
&lt;br>&lt;code>5).&lt;/code> 通过索引查找记录是从 B+ 树的根节点开始，一层一层向下搜索。由于每个页面都按照索引列的值建立了 


 
 

 
 
 
 
 
 
 
 &lt;a href='../05_innodb-page-struct/#page-directory%e9%a1%b5%e7%9b%ae%e5%bd%95' rel="noopener" class="internal-link" data-src="../05_innodb-page-struct/#page-directory%e9%a1%b5%e7%9b%ae%e5%bd%95">Page Directory（页目录）&lt;/a>，所以在这些页面中的查找非常快。
&lt;br>&lt;br>如果你读上边的几点结论有些任何一点点疑惑的话，那下边的内容不适合你，回过头先去看前边的内容去。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 id="索引的代价">
 索引的代价
 &lt;a class="anchor" href="#%e7%b4%a2%e5%bc%95%e7%9a%84%e4%bb%a3%e4%bb%b7">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout tip">&lt;p class="title">&lt;span class="icon icon-tip">&lt;/span> Tip &lt;/p>&lt;p> 在熟悉了 B+ 树索引原理之后，本篇文章的主题是唠叨如何更好的使用索引，虽然索引是个好东西，可不能乱建，在介绍如何更好的使用索引之前先要了解一下使用这玩意儿的代价，它在空间和时间上都会拖后腿：
&lt;br>&lt;code>空间上的代价&lt;/code>：这个是显而易见的，每建立一个索引都要为它建立一棵 B+ 树，每一棵 B+ 树的每一个节点都是一个数据页，一个页默认会占用 16KB 的存储空间，一棵很大的 B+ 树由许多数据页组成，那可是很大的一片存储空间呢。
&lt;br>&lt;code>时间上的代价&lt;/code>：每次对表中的数据进行增、删、改操作时，都需要去修改各个 B+ 树索引。而且我们讲过， B+ 树每层节点都是按照索引列的值从小到大的顺序排序而组成了双向链表。不论是叶子节点中的记录，还是内节点中的记录（也就是不论是用户记录还是目录项记录）都是按照索引列的值从小到大的顺序而形成了一个单向链表。而增、删、改操作可能会对节点和记录的排序造成破坏，所以存储引擎需要额外的时间进行一些记录移位，页面分裂、页面回收啥的操作来维护好节点和记录的排序。如果我们建了许多索引，每个索引对应的 B+ 树都要进行相关的维护操作，这还能不给性能拖后腿么？
&lt;br>&lt;br>所以说，一个表上索引建的越多，就会占用越多的存储空间，在增删改记录的时候性能就越差。为了能建立又好又少的索引，我们先得学学这些索引在哪些条件下起作用的。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/08_data_home_with_datadir/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/08_data_home_with_datadir/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">我们知道像 InnoDB 、 MyISAM 这样的存储引擎都是把表存储在磁盘上的，而操作系统用来管理磁盘的那个东东又被称为 &lt;strong>文件系统&lt;/strong> ，所以用专业一点的话来表述就是：&lt;span style='color: blue'>像 InnoDB 、 MyISAM 这样的存储引擎都是把表存储在文件系统上的&lt;/span>。当我们想读取数据的时候，这些存储引擎会从文件系统中把数据读出来返回给我们，当我们想写入数据的时候，这些存储引擎会把这些数据又写回文件系统。本章就是要唠叨一下 InnoDB 和 MyISAM 这两个存储引擎的数据如何在文件系统中存储的。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="mysql数据目录">
 MySQL数据目录
 &lt;a class="anchor" href="#mysql%e6%95%b0%e6%8d%ae%e7%9b%ae%e5%bd%95">#&lt;/a>
&lt;/h2>
&lt;p class="warn">MySQL服务器程序在启动时会到文件系统的某个目录下加载一些文件，之后在运行过程中产生的数据也都会存储到这个目录下的某些文件中，这个目录就称为 数据目录 ，我们下边就要详细唠唠这个目录下具体都有哪些重要的东西。&lt;/p>
&lt;ul>
&lt;li>
&lt;h3 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;/h3>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> Caution &lt;/p>&lt;p> 我们之前只接触过 MySQL 的安装目录（在安装 MySQL 的时候我们可以自己指定），我们重点强调过这个 安装目录 下非常重要的 bin 目录，它里边存储了许多关于控制客户端程序和服务器程序的命令（许多可执行文件，比如 mysql ， mysqld ， mysqld_safe 等等等等好几十个）。而 数据目录 是用来存储 MySQL 在运行过程中产生的数据，一定要和本章要讨论的 安装目录 区别开！&lt;span style='color: blue'>一定要区分开！一定要区分开！一定要区分开&lt;/span>！&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h3 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;/h3>
&lt;div class="alert callout tip">&lt;p class="title">&lt;span class="icon icon-tip">&lt;/span> Tip &lt;/p>&lt;p> 那说了半天，到底 MySQL 把数据都存到哪个路径下呢？其实 数据目录 对应着一个系统变量 &lt;strong>datadir&lt;/strong> ，我们在使用客户端与服务器建立连接之后查看这个系统变量的值就可以了：
&lt;br>&lt;code>show variables like 'datadir';&lt;/code>
&lt;br>&lt;br>从结果中可以看出，&lt;strong>在我的计算机上&lt;/strong>MySQL 的数据目录就是&lt;code>/data/mysql/&lt;/code>，你用你的计算机试试呗～&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/09_innodb_table-space/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/09_innodb_table-space/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="书接上文">
 书接上文
 &lt;a class="anchor" href="#%e4%b9%a6%e6%8e%a5%e4%b8%8a%e6%96%87">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> prerequirement &lt;/p>&lt;p> 通过前边儿的内容大家知道， 


 
 

 
 
 
 
 
 
 
 &lt;a href='../08_data_home_with_datadir/#:~:text=%e4%b8%ba%e4%ba%86%e6%9b%b4%e5%a5%bd%e7%9a%84%e7%ae%a1%e7%90%86%e8%bf%99%e4%ba%9b%e9%a1%b5,%e7%9a%84%e6%a6%82%e5%bf%b5' rel="noopener" class="internal-link" data-src="../08_data_home_with_datadir/#:~:text=%e4%b8%ba%e4%ba%86%e6%9b%b4%e5%a5%bd%e7%9a%84%e7%ae%a1%e7%90%86%e8%bf%99%e4%ba%9b%e9%a1%b5,%e7%9a%84%e6%a6%82%e5%bf%b5">表空间&lt;/a> 是一个抽象的概念，对于 


 
 

 
 
 
 
 
 
 
 &lt;a href='../08_data_home_with_datadir/#%e7%b3%bb%e7%bb%9f%e8%a1%a8%e7%a9%ba%e9%97%b4system-tablespace' rel="noopener" class="internal-link" data-src="../08_data_home_with_datadir/#%e7%b3%bb%e7%bb%9f%e8%a1%a8%e7%a9%ba%e9%97%b4system-tablespace">系统表空间&lt;/a> 来说，对应着文件系统中一个或多个实际文件；对于每个 


 
 

 
 
 
 
 
 
 
 &lt;a href='../08_data_home_with_datadir/#%e7%8b%ac%e7%ab%8b%e8%a1%a8%e7%a9%ba%e9%97%b4file-per-table-tablespace' rel="noopener" class="internal-link" data-src="../08_data_home_with_datadir/#%e7%8b%ac%e7%ab%8b%e8%a1%a8%e7%a9%ba%e9%97%b4file-per-table-tablespace">独立表空间&lt;/a> 来说，对应着文件系统中一个名为 表名.ibd 的实际文件。大家可以把表空间想象成被切分为许许多多个 &lt;strong>页&lt;/strong> 的池子，当我们想为某个表插入一条记录的时候，就从池子中捞出一个对应的页来把数据写进去。本章内容会深入到表空间的各个细节中，带领大家在 InnoDB 存储结构的池子中畅游。由于本章中将会涉及比较多的概念，虽然这些概念都不难，但是却相互依赖，所以奉劝大家在看的时候：&lt;span style='color:blue'>不要跳着看! 不要跳着看! 不要跳着看!&lt;/span>&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="页面类型">
 页面类型
 &lt;a class="anchor" href="#%e9%a1%b5%e9%9d%a2%e7%b1%bb%e5%9e%8b">#&lt;/a>
&lt;/h3>
&lt;p class="warn">再一次强调，InnoDB是以页为单位管理存储空间的，我们的聚簇索引（也就是完整的表数据）和其他的二级索引都是以 B+ 树的形式保存到表空间的，而 B+ 树的节点就是数据页。我们前边说过，这个数据页的类型名其实是： FIL_PAGE_INDEX ，除了这种存放索引数据的页面类型之外，InnoDB也为了不同的目的设计了若干种不同类型的页面，为了唤醒大家的记忆，我们再一次把各种常用的页面类型提出来：
&lt;br>&lt;br>因为页面类型前边都有个 FIL_PAGE 或者 FIL_PAGE_TYPE 的前缀，为简便起见我们后边唠叨页面类型的时候就把这些前缀省略掉了，比方说 FIL_PAGE_TYPE_ALLOCATED 类型称为 ALLOCATED 类型， FIL_PAGE_INDEX 类型称为INDEX 类型。&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th align="left">类型名称&lt;/th>
&lt;th align="center">十六进制&lt;/th>
&lt;th align="center">描述&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_ALLOCATED&lt;/td>
&lt;td align="center">0x0000&lt;/td>
&lt;td align="center">最新分配，还没使用&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_UNDO_LOG&lt;/td>
&lt;td align="center">0x0002&lt;/td>
&lt;td align="center">Undo日志页&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_INODE&lt;/td>
&lt;td align="center">0x0003&lt;/td>
&lt;td align="center">段信息节点&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_IBUF_FREE_LIST&lt;/td>
&lt;td align="center">0x0004&lt;/td>
&lt;td align="center">Insert Buffer空闲列表&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_IBUF_BITMAP&lt;/td>
&lt;td align="center">0x0005&lt;/td>
&lt;td align="center">Insert Buffer位图&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_SYS&lt;/td>
&lt;td align="center">0x0006&lt;/td>
&lt;td align="center">系统页&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_TRX_SYS&lt;/td>
&lt;td align="center">0x0007&lt;/td>
&lt;td align="center">事务系统数据&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_FSP_HDR&lt;/td>
&lt;td align="center">0x0008&lt;/td>
&lt;td align="center">表空间头部信息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_XDES&lt;/td>
&lt;td align="center">0x0009&lt;/td>
&lt;td align="center">扩展描述页&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_TYPE_BLOB&lt;/td>
&lt;td align="center">0x000A&lt;/td>
&lt;td align="center">BLOB页&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td align="left">FIL_PAGE_INDEX&lt;/td>
&lt;td align="center">0x45BF&lt;/td>
&lt;td align="center">索引页，也就是我们所说的 数据页&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/li>
&lt;li>
&lt;h3 id="页面通用部分">
 页面通用部分
 &lt;a class="anchor" href="#%e9%a1%b5%e9%9d%a2%e9%80%9a%e7%94%a8%e9%83%a8%e5%88%86">#&lt;/a>
&lt;/h3>
&lt;p class="warn">我们前边说过数据页，也就是 INDEX 类型的页由7个部分组成，其中的两个部分是所有类型的页面都通用的。当然我不能寄希望于你把我说的话都记住，所以在这里重新强调一遍，任何类型的页面都有下边这种通用的结构：
&lt;br>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/09_innodb_table_space/its-01.png" alt="" width="60%">
&lt;br>从上图中可以看出，任何类型的页都会包含这两个部分：
&lt;br>&lt;code>1).&lt;/code> File Header ：记录页面的一些通用信息
&lt;br>&lt;code>2).&lt;/code> File Trailer ：校验页是否完整，保证从内存到磁盘刷新时内容的一致性。
&lt;br>&lt;br>对于 File Trailer 我们不再做过多强调，全部忘记了的话可以到将数据页的那一章回顾一下。我们这里再强调一遍 File Header 的各个组成部分：&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/10_single-table-access/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/10_single-table-access/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="开场序言">
 开场序言
 &lt;a class="anchor" href="#%e5%bc%80%e5%9c%ba%e5%ba%8f%e8%a8%80">#&lt;/a>
&lt;/h2>
&lt;div class="alert flat note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p> 对于我们这些 MySQL 的使用者来说， MySQL 其实就是一个软件，平时用的最多的就是查询功能。DBA 时不时丢过来一些慢查询语句让优化，我们如果连查询是怎么执行的都不清楚还优化个毛线，所以是时候掌握真正的技术了。我们在第一章的时候就曾说过， MySQL Server 有一个称为 &lt;strong>查询优化器&lt;/strong> 的模块，一条查询语句进行语法解析之后就会被交给查询优化器来进行优化，优化的结果就是生成一个所谓的 &lt;strong>执行计划&lt;/strong> ，这个执行计划表明了应该使用哪些索引进行查询，表之间的连接顺序是啥样的，最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询，并将查询结果返回给用户。不过查询优化这个主题有点儿大，在学会跑之前还得先学会走，所以本章先来瞅瞅 MySQL 怎么执行单表查询（就是 FROM 子句后边只有一个表，最简单的那种查询～）。不过需要强调的一点是，在学习本章前务必看过前边关于记录结构、数据页结构以及索引的部分，如果你不能保证这些东西已经完全掌握，那么本章不适合你。
&lt;br>&lt;br>为了故事的顺利发展，我们先得建个如下的表：
&lt;br>我们为这个 &lt;em>single_table&lt;/em> 表建立了1个聚簇索引和4个二级索引，分别是：
&lt;br>&lt;span style='padding-left:2em'>&lt;code>1.&lt;/code> 为 id 列建立的聚簇索引。
&lt;br>&lt;span style='padding-left:2em'>&lt;code>2.&lt;/code> 为 key1 列建立的 idx_key1 二级索引。
&lt;br>&lt;span style='padding-left:2em'>&lt;code>3.&lt;/code> 为 key2 列建立的 idx_key2 二级索引，而且该索引是唯一二级索引。
&lt;br>&lt;span style='padding-left:2em'>&lt;code>4.&lt;/code> 为 key3 列建立的 idx_key3 二级索引。
&lt;br>&lt;span style='padding-left:2em'>&lt;code>5.&lt;/code> 为 key_part1 、 key_part2 、 key_part3 列建立的 idx_key_part 二级索引，这也是一个联合索引。
&lt;br>&lt;br>然后我们需要为这个表插入10000行记录，除 id 列外其余的列都插入随机值就好了，具体的插入语句我就不写了，自己写个程序插入吧（id列是自增主键列，不需要我们手动插入）。&lt;/p>
&lt;/p>&lt;/div>
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">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;/pre>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">想必各位都用过高德地图来查找到某个地方的路线吧（此处没有为高德地图打广告的意思，他们没给我钱，大家用百度地图也可以啊），如果我们搜西安钟楼到大雁塔之间的路线的话，地图软件会给出n种路线供我们选择，如果我们实在闲的没事儿干并且足够有钱的话，还可以用南辕北辙的方式绕地球一圈到达目的地。也就是说，不论采用哪一种方式，我们最终的目标就是到达大雁塔这个地方。回到 MySQL 中来，我们平时所写的那些查询语句本质上只是一种声明式的语法，只是告诉 MySQL 我们要获取的数据符合哪些规则，至于 MySQL 背地里是怎么把查询结果搞出来的那是 MySQL 自己的事儿。对于单个表的查询来说，设计MySQL的大叔把查询的执行方式大致分为下边两种：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;code>使用全表扫描进行查询&lt;/code>: 这种执行方式很好理解，就是把表的每一行记录都扫一遍嘛，把符合搜索条件的记录加入到结果集就完了。不管是啥查询都可以使用这种方式执行，当然，这种也是最笨的执行方式。
&lt;br>&lt;span style='padding-left:1.2em'>&lt;code>使用索引进行查询&lt;/code>: 因为直接使用全表扫描的方式执行查询要遍历好多记录，所以代价可能太大了。如果查询语句中的搜索条件可以使用到某个索引，那直接使用索引来执行查询可能会加快查询执行的时间。使用索引来执行查询的方式五花八门，又可以细分为许多种类：
&lt;br>&lt;span style='padding-left:3em'>&lt;code>1.&lt;/code> 针对主键或唯一二级索引的等值查询
&lt;br>&lt;span style='padding-left:3em'>&lt;code>2.&lt;/code> 针对普通二级索引的等值查询
&lt;br>&lt;span style='padding-left:3em'>&lt;code>3.&lt;/code> 针对索引列的范围查询
&lt;br>&lt;span style='padding-left:3em'>&lt;code>4.&lt;/code> 直接扫描整个索引
&lt;br>&lt;br>设计 MySQL 的大叔把 MySQL 执行查询语句的方式称之为 &lt;strong>访问方法&lt;/strong> 或者 &lt;strong>访问类型&lt;/strong> 。同一个查询语句可能可以使用多种不同的访问方法来执行，虽然最后的查询结果都是一样的，但是执行的时间可能差老鼻子远了，就像是从钟楼到大雁塔，你可以坐火箭去，也可以坐飞机去，当然也可以坐乌龟去。下边细细道来各种 访问方法 的具体内容。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/11_multi-table-join/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/11_multi-table-join/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="开场序言">
 开场序言
 &lt;a class="anchor" href="#%e5%bc%80%e5%9c%ba%e5%ba%8f%e8%a8%80">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> Caution &lt;/p>&lt;p> 搞数据库一个避不开的概念就是 Join ，翻译成中文就是 连接 。相信很多小伙伴在初学连接的时候有些一脸懵逼，理解了连接的语义之后又可能不明白各个表中的记录到底是怎么连起来的，以至于在使用的时候常常陷入下边两种误区：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;code>误区一&lt;/code>：业务至上，管他三七二十一，再复杂的查询也用在一个连接语句中搞定。
&lt;br>&lt;span style='padding-left:1.2em'>&lt;code>误区二&lt;/code>：敬而远之，上次 DBA 那给报过来的慢查询就是因为使用了连接导致的，以后再也不敢用了。
&lt;br>所以本章就来扒一扒连接的原理。考虑到一部分小伙伴可能忘了连接是个啥或者压根儿就不知道，为了节省他们百度或者看其他书的宝贵时间以及为了我的书凑字数，我们先来介绍一下 MySQL 中支持的一些连接语法。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 id="连接简介">
 连接简介
 &lt;a class="anchor" href="#%e8%bf%9e%e6%8e%a5%e7%ae%80%e4%bb%8b">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="连接的本质">
 连接的本质
 &lt;a class="anchor" href="#%e8%bf%9e%e6%8e%a5%e7%9a%84%e6%9c%ac%e8%b4%a8">#&lt;/a>
&lt;/h3>
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">create schema multi_table_join collate utf8mb4_general_ci;
use multi_table_join;

CREATE TABLE t1 (m1 int, n1 char(1));
CREATE TABLE t2 (m2 int, n2 char(1));
INSERT INTO t1 VALUES(1, 'a'), (2, 'b'), (3, 'c');
INSERT INTO t2 VALUES(2, 'b'), (3, 'c'), (4, 'd');
&lt;/code>&lt;/pre>&lt;/div>
&lt;p class="warn"> 为了故事的顺利发展，我们先建立两个简单的表并给它们填充一点数据：
&lt;br>&lt;br>我们成功建立了 t1 、 t2 两个表，这两个表都有两个列，一个是 INT 类型的，一个是 CHAR(1) 类型的，填充好数据的两个表长这样：
&lt;br>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/11_multi-table-join/mtj-01.png" alt="" width="60%">
&lt;br>&lt;strong>连接&lt;/strong> 的本质就是把各个连接表中的记录都取出来依次匹配的组合加入结果集并返回给用户。所以我们把 t1 和 t2 两个表连接起来的过程如下左所示：
&lt;br>&lt;br>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/11_multi-table-join/mtj-02.png" alt="" width="60%"> &lt;span style='padding-left:1em'/>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/11_multi-table-join/mtj-03.png" alt="" width="20%">
&lt;br>&lt;br>这个过程看起来就是把 t1 表的记录和 t2 的记录连起来组成新的更大的记录，所以这个查询过程称之为 &lt;strong>连接查询&lt;/strong>。连接查询的结果集中包含一个表中的每一条记录与另一个表中的每一条记录相互匹配的组合，像这样的结果集就可以称之为 &lt;strong>笛卡尔积&lt;/strong>。因为表 t1 中有3条记录，表 t2 中也有3条记录，所以这两个表连接之后的笛卡尔积就有 3×3=9 行记录。在 MySQL 中，连接查询的语法也很随意，只要在 FROM 语句后边跟多个表名就好了，比如我们把 t1 表和 t2 表连接起来的查询语句可以写成如上右那样。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/12_optimize-selection/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/12_optimize-selection/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="什么是成本">
 什么是成本
 &lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e6%88%90%e6%9c%ac">#&lt;/a>
&lt;/h2>
&lt;p class="warn">我们之前老说 MySQL 执行一个查询可以有不同的执行方案，它会选择其中成本最低，或者说代价最低的那种方案去真正的执行查询。不过我们之前对 成本 的描述是非常模糊的，其实在 MySQL 中一条查询语句的执行成本是由下边这两个方面组成的：
&lt;br>&lt;code>1.&lt;/code>: I/O 成本
&lt;br>我们的表经常使用的 MyISAM 、 InnoDB 存储引擎都是将数据和索引都存储到磁盘上的，当我们想查询表中的记录时，需要先把数据或者索引加载到内存中然后再操作。这个从磁盘到内存这个加载的过程损耗的时间称之为 I/O 成本。
&lt;br>&lt;br>&lt;code>2.&lt;/code>: CPU 成本
&lt;br>读取以及检测记录是否满足对应的搜索条件、对结果集进行排序等这些操作损耗的时间称之为 CPU 成本。对于 InnoDB 存储引擎来说，页是磁盘和内存之间交互的基本单位，设计 MySQL 的大叔规定读取一个页面花费的成本默认是 1.0 ，读取以及检测一条记录是否符合搜索条件的成本默认是 0.2 。 1.0 、 0.2 这些数字称之为 成本常数 ，这两个成本常数我们最常用到，其余的成本常数我们后边再说哈。&lt;/p>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> 小贴士 &lt;/p>&lt;p>
需要注意的是，不管读取记录时需不需要检测是否满足搜索条件，其成本都算是0.2。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="准备工作">
 准备工作
 &lt;a class="anchor" href="#%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c">#&lt;/a>
&lt;/h3>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p> 为了故事的顺利发展，我们还得把之前用到的 &lt;code>single_table&lt;/code> 表搬来，怕大家忘了这个表长啥样，再给大家抄一遍如下：
&lt;br>还是假设这个表里边儿有 10000 条记录，除 id 列外其余的列都插入随机值。下边正式开始我们的表演。
&lt;br>&lt;br> 为了方便我们使用 python 进行插入。
&lt;br>安装依赖 &lt;code>pip install faker pymysql&lt;/code>。
&lt;br>编写代码如下执行 &lt;code>python single_table_faker.py&lt;/code>&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/13_innodb-data-collection/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/13_innodb-data-collection/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="开场序言">
 开场序言
 &lt;a class="anchor" href="#%e5%bc%80%e5%9c%ba%e5%ba%8f%e8%a8%80">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> Caution &lt;/p>&lt;p> 我们前边唠叨查询成本的时候经常用到一些统计数据，比如通过 &lt;code>SHOW TABLE STATUS&lt;/code> 可以看到关于表的统计数据，通过 &lt;code>SHOW INDEX&lt;/code> 可以看到关于索引的统计数据，那么这些统计数据是怎么来的呢？它们是以什么方式收集的呢？本章将聚焦于 InnoDB 存储引擎的统计数据收集策略，看完本章大家就会明白为啥前边老说 InnoDB 的统计信息是不精确的估计值了（言下之意就是我们不打算介绍 MyISAM 存储引擎统计数据的收集和存储方式，有想了解的同学自己个儿看看文档哈）。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="innodb_table_stats">
 innodb_table_stats
 &lt;a class="anchor" href="#innodb_table_stats">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>
&lt;h4 id="n_rows统计项的收集">
 n_rows统计项的收集
 &lt;a class="anchor" href="#n_rows%e7%bb%9f%e8%ae%a1%e9%a1%b9%e7%9a%84%e6%94%b6%e9%9b%86">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="clustered_index_size和sum_of_other_index_sizes统计项的收集">
 clustered_index_size和sum_of_other_index_sizes统计项的收集
 &lt;a class="anchor" href="#clustered_index_size%e5%92%8csum_of_other_index_sizes%e7%bb%9f%e8%ae%a1%e9%a1%b9%e7%9a%84%e6%94%b6%e9%9b%86">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;h3 id="innodb_index_stats">
 innodb_index_stats
 &lt;a class="anchor" href="#innodb_index_stats">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="定期更新统计数据">
 定期更新统计数据
 &lt;a class="anchor" href="#%e5%ae%9a%e6%9c%9f%e6%9b%b4%e6%96%b0%e7%bb%9f%e8%ae%a1%e6%95%b0%e6%8d%ae">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="手动更新-innodb_table_stats-和-innodb_index_stats-表">
 手动更新 innodb_table_stats 和 innodb_index_stats 表
 &lt;a class="anchor" href="#%e6%89%8b%e5%8a%a8%e6%9b%b4%e6%96%b0-innodb_table_stats-%e5%92%8c-innodb_index_stats-%e8%a1%a8">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;h2 id="基于内存的非永久性统计数据">
 基于内存的非永久性统计数据
 &lt;a class="anchor" href="#%e5%9f%ba%e4%ba%8e%e5%86%85%e5%ad%98%e7%9a%84%e9%9d%9e%e6%b0%b8%e4%b9%85%e6%80%a7%e7%bb%9f%e8%ae%a1%e6%95%b0%e6%8d%ae">#&lt;/a>
&lt;/h2>
&lt;/li>
&lt;li>
&lt;h2 id="innodb_stats_method的使用">
 innodb_stats_method的使用
 &lt;a class="anchor" href="#innodb_stats_method%e7%9a%84%e4%bd%bf%e7%94%a8">#&lt;/a>
&lt;/h2>
&lt;/li>
&lt;li>
&lt;h2 id="总结">
 总结
 &lt;a class="anchor" href="#%e6%80%bb%e7%bb%93">#&lt;/a>
&lt;/h2>
&lt;/li>
&lt;/ul></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/14_rule-based-optimization/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/14_rule-based-optimization/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="开场序言">
 开场序言
 &lt;a class="anchor" href="#%e5%bc%80%e5%9c%ba%e5%ba%8f%e8%a8%80">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> Caution &lt;/p>&lt;p> 大家别忘了 MySQL 本质上是一个软件，设计 MySQL 的大叔并不能要求使用这个软件的人个个都是数据库高高手，就像我写这本书的时候并不能要求各位在学之前就会了里边儿的知识。(吐槽一下：都会了的人谁还看呢，难道是为了精神上受感化？)
&lt;br>也就是说我们无法避免某些同学写一些执行起来十分耗费性能的语句。即使是这样，设计 MySQL 的大叔还是依据一些规则，竭尽全力的把这个很糟糕的语句转换成某种可以比较高效执行的形式，这个过程也可以被称作 &lt;strong>查询重写&lt;/strong> （就是人家觉得你写的语句不好，自己再重写一遍）。本章详细唠叨一下一些比较重要的重写规则。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 id="条件化简">
 条件化简
 &lt;a class="anchor" href="#%e6%9d%a1%e4%bb%b6%e5%8c%96%e7%ae%80">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 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;/h3>
&lt;/li>
&lt;li>
&lt;h3 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;/h3>
&lt;/li>
&lt;li>
&lt;h3 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;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="移除没用的条件trivial_condition_removal">
 移除没用的条件（trivial_condition_removal）
 &lt;a class="anchor" href="#%e7%a7%bb%e9%99%a4%e6%b2%a1%e7%94%a8%e7%9a%84%e6%9d%a1%e4%bb%b6trivial_condition_removal">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="表达式计算">
 表达式计算
 &lt;a class="anchor" href="#%e8%a1%a8%e8%be%be%e5%bc%8f%e8%ae%a1%e7%ae%97">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="having子句和where子句的合并">
 HAVING子句和WHERE子句的合并
 &lt;a class="anchor" href="#having%e5%ad%90%e5%8f%a5%e5%92%8cwhere%e5%ad%90%e5%8f%a5%e7%9a%84%e5%90%88%e5%b9%b6">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;li>
&lt;h3 id="常量表检测">
 常量表检测
 &lt;a class="anchor" href="#%e5%b8%b8%e9%87%8f%e8%a1%a8%e6%a3%80%e6%b5%8b">#&lt;/a>
&lt;/h3>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;h2 id="外连接消除">
 外连接消除
 &lt;a class="anchor" href="#%e5%a4%96%e8%bf%9e%e6%8e%a5%e6%b6%88%e9%99%a4">#&lt;/a>
&lt;/h2>
&lt;/li>
&lt;li>
&lt;h2 id="子查询优化">
 子查询优化
 &lt;a class="anchor" href="#%e5%ad%90%e6%9f%a5%e8%af%a2%e4%bc%98%e5%8c%96">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="子查询语法">
 子查询语法
 &lt;a class="anchor" href="#%e5%ad%90%e6%9f%a5%e8%af%a2%e8%af%ad%e6%b3%95">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>
&lt;h4 id="按返回的结果集区分子查询">
 按返回的结果集区分子查询
 &lt;a class="anchor" href="#%e6%8c%89%e8%bf%94%e5%9b%9e%e7%9a%84%e7%bb%93%e6%9e%9c%e9%9b%86%e5%8c%ba%e5%88%86%e5%ad%90%e6%9f%a5%e8%af%a2">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="按与外层查询关系来区分子查询">
 按与外层查询关系来区分子查询
 &lt;a class="anchor" href="#%e6%8c%89%e4%b8%8e%e5%a4%96%e5%b1%82%e6%9f%a5%e8%af%a2%e5%85%b3%e7%b3%bb%e6%9d%a5%e5%8c%ba%e5%88%86%e5%ad%90%e6%9f%a5%e8%af%a2">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="子查询在布尔表达式中的使用">
 子查询在布尔表达式中的使用
 &lt;a class="anchor" href="#%e5%ad%90%e6%9f%a5%e8%af%a2%e5%9c%a8%e5%b8%83%e5%b0%94%e8%a1%a8%e8%be%be%e5%bc%8f%e4%b8%ad%e7%9a%84%e4%bd%bf%e7%94%a8">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="子查询语法注意事项">
 子查询语法注意事项
 &lt;a class="anchor" href="#%e5%ad%90%e6%9f%a5%e8%af%a2%e8%af%ad%e6%b3%95%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;h3 id="子查询在mysql中是怎么执行的">
 子查询在MySQL中是怎么执行的
 &lt;a class="anchor" href="#%e5%ad%90%e6%9f%a5%e8%af%a2%e5%9c%a8mysql%e4%b8%ad%e6%98%af%e6%80%8e%e4%b9%88%e6%89%a7%e8%a1%8c%e7%9a%84">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>
&lt;h4 id="小白们眼中子查询的执行方式">
 小白们眼中子查询的执行方式
 &lt;a class="anchor" href="#%e5%b0%8f%e7%99%bd%e4%bb%ac%e7%9c%bc%e4%b8%ad%e5%ad%90%e6%9f%a5%e8%af%a2%e7%9a%84%e6%89%a7%e8%a1%8c%e6%96%b9%e5%bc%8f">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="标量子查询行子查询的执行方式">
 标量子查询、行子查询的执行方式
 &lt;a class="anchor" href="#%e6%a0%87%e9%87%8f%e5%ad%90%e6%9f%a5%e8%af%a2%e8%a1%8c%e5%ad%90%e6%9f%a5%e8%af%a2%e7%9a%84%e6%89%a7%e8%a1%8c%e6%96%b9%e5%bc%8f">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="in子查询优化">
 IN子查询优化
 &lt;a class="anchor" href="#in%e5%ad%90%e6%9f%a5%e8%af%a2%e4%bc%98%e5%8c%96">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="anyall子查询优化">
 ANY/ALL子查询优化
 &lt;a class="anchor" href="#anyall%e5%ad%90%e6%9f%a5%e8%af%a2%e4%bc%98%e5%8c%96">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="not-exists子查询的执行">
 [NOT] EXISTS子查询的执行
 &lt;a class="anchor" href="#not-exists%e5%ad%90%e6%9f%a5%e8%af%a2%e7%9a%84%e6%89%a7%e8%a1%8c">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;li>
&lt;h4 id="对于派生表的优化">
 对于派生表的优化
 &lt;a class="anchor" href="#%e5%af%b9%e4%ba%8e%e6%b4%be%e7%94%9f%e8%a1%a8%e7%9a%84%e4%bc%98%e5%8c%96">#&lt;/a>
&lt;/h4>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/15_explain-key-01/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/15_explain-key-01/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="开场序言">
 开场序言
 &lt;a class="anchor" href="#%e5%bc%80%e5%9c%ba%e5%ba%8f%e8%a8%80">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> Caution &lt;/p>&lt;p> 一条查询语句在经过 MySQL 查询优化器的各种基于成本和规则的优化会后生成一个所谓的 执行计划 ，这个执行计划展示了接下来具体执行查询的方式，比如多表连接的顺序是什么，对于每个表采用什么访问方法来具体执行查询等等。设计 MySQL 的大叔贴心的为我们提供了 &lt;strong>EXPLAIN 语句&lt;/strong> 来帮助我们查看某个查询语句的具体执行计划，本章的内容就是为了帮助大家看懂 EXPLAIN 语句的各个输出项都是干嘛使的，从而可以有针对性的提升我们查询语句的性能。
&lt;br>&lt;br>如果我们想看看某个查询的执行计划的话，可以在具体的查询语句前边加一个 EXPLAIN ，就像这样：&lt;code>EXPLAIN SELECT 1;&lt;/code>
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/15_explain-key-01/ek-01.png" alt="" width="80%">
&lt;br>&lt;br>然后这输出的一大坨东西就是所谓的 &lt;strong>执行计划&lt;/strong> ，我的任务就是带领大家看懂这一大坨东西里边的每个列都是干啥用的，以及在这个 执行计划 的辅助下，我们应该怎样改进自己的查询语句以使查询执行起来更高效。其实除了以 SELECT 开头的查询语句，其余的 DELETE 、 INSERT 、 REPLACE 以及 UPDATE 语句前边都可以加上 EXPLAIN 这个词儿，用来查看这些语句的执行计划，不过我们这里对 SELECT 语句更感兴趣，所以后边只会以 SELECT 语句为例来描述 EXPLAIN 语句的用法。为了让大家先有一个感性的认识，我们把 EXPLAIN 语句输出的各个列的作用先大致罗列如下左：
&lt;br>&lt;br>需要注意的是，&lt;span style='color:red'>大家如果看不懂上边输出列含义，那是正常的，千万不要纠结～&lt;/span>。我在这里把它们都列出来只是为了描述一个轮廓，让大家有一个大致的印象，下边会细细道来，等会儿说完了不信你不会～ 为了故事的顺利发展，我们还是要请出我们前边已经用了n遍的 single_table 表，为了防止大家忘了，再把它的结构描述一遍 如下右：
&lt;br>&lt;br>我们仍然假设有两个和 single_table 表构造一模一样的 s1 、 s2 表，而且这两个表里边儿有10000条记录，除id列外其余的列都插入随机值。为了让大家有比较好的阅读体验，我们下边并不准备严格按照 EXPLAIN 输出列的顺序来介绍这些列分别是干嘛的，大家注意一下就好了。&lt;/p>
&lt;/p>&lt;/div> &lt;div class="docsify-example-panels"> &lt;div class="docsify-example-panel left-panel"style="max-width: 50%; width: 50%;">
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>列名&lt;/th>
&lt;th>描述&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>id&lt;/td>
&lt;td>在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>select_type&lt;/td>
&lt;td>SELECT 关键字对应的那个查询的类型&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>table&lt;/td>
&lt;td>表名&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>partitions&lt;/td>
&lt;td>匹配的分区信息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>type&lt;/td>
&lt;td>针对单表的访问方法&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>possible_keys&lt;/td>
&lt;td>可能用到的索引&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>key&lt;/td>
&lt;td>实际上使用的索引&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>key_len&lt;/td>
&lt;td>实际使用到的索引长度&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ref&lt;/td>
&lt;td>当使用索引列等值查询时，与索引列进行等值匹配的对象信息&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>rows&lt;/td>
&lt;td>预估的需要读取的记录条数&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>filtered&lt;/td>
&lt;td>某个表经过搜索条件过滤后剩余记录条数的百分比&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Extra&lt;/td>
&lt;td>一些额外的信息&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>&lt;/div>
 &lt;div class="docsify-example-panel right-panel"style="max-width: 50%; width: 50%;">
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="16" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">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;
CREATE TABLE s1 LIKE single_table; insert into s1 select * from single_table; CREATE TABLE s2 LIKE single_table; insert into s2 select * from single_table;

// CREATE TABLE s1 AS SELECT * FROM single_table;

// 完整复制
-- 第一步：创建表结构
-- CREATE TABLE new_table LIKE original_table;
-- 第二步：复制数据
-- INSERT INTO new_table SELECT * FROM original_table;
&lt;/code>&lt;/pre>&lt;/div>&lt;/div>&lt;/div>

&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="table">
 table
 &lt;a class="anchor" href="#table">#&lt;/a>
&lt;/h3>
&lt;p class="warn">不论我们的查询语句有多复杂，里边儿包含了多少个表，到最后也是需要对每个表进行单表访问的，所以设计 MySQL 的大叔规定 EXPLAIN 语句输出的每条记录都对应着某个单表的访问方法，该条记录的 table 列代表着该表的表名。所以我们看一条比较简单的查询语句：&lt;code>EXPLAIN SELECT * FROM s1;&lt;/code>
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/15_explain-key-01/ek-02.png" alt="" width="80%">
&lt;br>&lt;br>这个查询语句只涉及对 s1 表的单表查询，所以 EXPLAIN 输出中只有一条记录，其中的 table 列的值是 s1 ，表明这条记录是用来说明对 s1 表的单表访问方法的。
&lt;br>下边我们看一下一个连接查询的执行计划：&lt;code>EXPLAIN SELECT * FROM s1 INNER JOIN s2;&lt;/code>
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/15_explain-key-01/ek-03.png" alt="" width="99%">
&lt;br>&lt;br>可以看到这个连接查询的执行计划中有两条记录，这两条记录的 table 列分别是 s1 和 s2 ，这两条记录用来分别说明对 s1 表和 s2 表的访问方法是什么。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/16_explain-key-02/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/16_explain-key-02/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">本章紧接着上一节的内容，继续唠叨 EXPLAIN 语句输出的各个列的意思。&lt;/p>
&lt;ul>
&lt;li>
&lt;h3 id="extra">
 Extra
 &lt;a class="anchor" href="#extra">#&lt;/a>
&lt;/h3>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p> 顾名思义， Extra 列是用来说明一些额外信息的，我们可以通过这些额外信息来更准确的理解 MySQL 到底将如何执行给定的查询语句。 MySQL 提供的额外信息有好几十个，我们就不一个一个介绍了（都介绍了感觉我们的文章就跟文档差不多了～），所以我们只挑一些平时常见的或者比较重要的额外信息介绍给大家哈。&lt;/p>
&lt;/p>&lt;/div>
&lt;ul>
&lt;li>
&lt;h4 id="no-tables-used">
 No tables used
 &lt;a class="anchor" href="#no-tables-used">#&lt;/a>
&lt;/h4>
&lt;div class="alert callout tip">&lt;p class="title">&lt;span class="icon icon-tip">&lt;/span> Tip &lt;/p>&lt;p> 当查询语句的没有 FROM 子句时将会提示该额外信息，比如：
&lt;br>&lt;code>EXPLAIN SELECT 1;&lt;/code>
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/16_explain-key-02/ek-01.png" alt="" width="80%">&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h4 id="impossible-where">
 Impossible WHERE
 &lt;a class="anchor" href="#impossible-where">#&lt;/a>
&lt;/h4>
&lt;div class="alert callout tip">&lt;p class="title">&lt;span class="icon icon-tip">&lt;/span> Tip &lt;/p>&lt;p> 查询语句的 WHERE 子句永远为 FALSE 时将会提示该额外信息，比方说：
&lt;br>&lt;code>EXPLAIN SELECT * FROM s1 WHERE 1 != 1;&lt;/code>
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/16_explain-key-02/ek-02.png" alt="" width="80%">&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h4 id="no-matching-minmax-row">
 No matching min/max row
 &lt;a class="anchor" href="#no-matching-minmax-row">#&lt;/a>
&lt;/h4>
&lt;div class="alert callout tip">&lt;p class="title">&lt;span class="icon icon-tip">&lt;/span> Tip &lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/17_optimizer-trace/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/17_optimizer-trace/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="一镜到底">
 一镜到底
 &lt;a class="anchor" href="#%e4%b8%80%e9%95%9c%e5%88%b0%e5%ba%95">#&lt;/a>
&lt;/h2>
&lt;p>对于 MySQL 5.6 以及之前的版本来说，查询优化器就像是一个黑盒子一样，你只能通过 EXPLAIN 语句查看到最后优化器决定使用的执行计划，却无法知道它为什么做这个决策。这对于一部分喜欢刨根问底的小伙伴来说简直是灾难：“我就觉得使用其他的执行方案比 EXPLAIN 输出的这种方案强，凭什么优化器做的决定和我想的不一样呢？”&lt;/p>
&lt;p>在 MySQL 5.6 以及之后的版本中，设计 MySQL 的大叔贴心的为这部分小伙伴提出了一个 &lt;strong>optimizer trace&lt;/strong> 的功能，这个功能可以让我们方便的查看优化器生成执行计划的整个过程，这个功能的开启与关闭由 &lt;strong>系统变量 optimizer_trace&lt;/strong> 决定，我们看一下：&lt;code>SHOW VARIABLES LIKE 'optimizer_trace';&lt;/code>
&lt;br>&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/17_optimizer-trace/ot-01.png" alt="" width="35%">&lt;/p>
&lt;p>可以看到 enabled 值为 off ，表明这个功能默认是关闭的。&lt;/p>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> 小贴士 &lt;/p>&lt;p> one_line 的值是控制输出格式的，如果为 on 那么所有输出都将在一行中展示，不适合人阅读，所以我们就保持其默认值为 off 吧。&lt;/p>
&lt;/p>&lt;/div>
&lt;p>如果想打开这个功能，必须首先把 enabled 的值改为 on ，就像这样：
&lt;br>&lt;code>SET optimizer_trace=&amp;quot;enabled=on&amp;quot;;&lt;/code>&lt;/p>
&lt;p>然后我们就可以输入我们想要查看优化过程的查询语句，当该查询语句执行完成后，就可以到 information_schema 数据库下的 &lt;strong>OPTIMIZER_TRACE 表&lt;/strong> 中查看完整的优化过程。这个 OPTIMIZER_TRACE 表有4个列，分别是：&lt;/p>
&lt;ol>
&lt;li>QUERY ：表示我们的查询语句。&lt;/li>
&lt;li>TRACE ：表示优化过程的JSON格式文本。&lt;/li>
&lt;li>MISSING_BYTES_BEYOND_MAX_MEM_SIZE ：由于优化过程可能会输出很多，如果超过某个限制时，多余的文本将不会被显示，这个字段展示了被忽略的文本字节数&lt;/li>
&lt;li>INSUFFICIENT_PRIVILEGES ：表示是否没有权限查看优化过程，默认值是0，只有某些特殊情况下才会是 1 ，我们暂时不关心这个字段的值。&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>完整的使用 optimizer trace 功能的步骤总结如下：&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/18_buffer-pool/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/18_buffer-pool/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p class="warn">通过前边的唠叨我们知道，对于使用 InnoDB 作为存储引擎的表来说，不管是用于存储用户数据的索引（包括聚簇索引和二级索引），还是各种系统数据，都是以 &lt;strong>页&lt;/strong> 的形式存放在 &lt;strong>表空间&lt;/strong> 中的，而所谓的 &lt;strong>表空间&lt;/strong> 只不过是 InnoDB 对文件系统上一个或几个实际文件的抽象，也就是说我们的数据说到底还是存储在磁盘上的。但是各位也都知道，磁盘的速度慢的跟乌龟一样，怎么能配得上“快如风，疾如电”的 CPU 呢？所以 InnoDB 存储引擎在处理客户端的请求时，当需要访问某个页的数据时，就会把完整的页的数据全部加载到内存中，也就是说&lt;span style='color:red'>即使我们只需要访问一个页的一条记录，那也需要先把整个页的数据加载到内存中&lt;/span>。将整个页加载到内存中后就可以进行读写访问了，在进行完读写访问之后并不着急把该页对应的内存空间释放掉，而是将其 缓存 起来，这样将来有请求再次访问该页面时，就可以省去磁盘 IO 的开销了。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="innodb的buffer-pool">
 InnoDB的Buffer Pool
 &lt;a class="anchor" href="#innodb%e7%9a%84buffer-pool">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 id="啥是个buffer-pool">
 啥是个Buffer Pool
 &lt;a class="anchor" href="#%e5%95%a5%e6%98%af%e4%b8%aabuffer-pool">#&lt;/a>
&lt;/h3>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p> 设计 InnoDB 的大叔为了缓存磁盘中的页，在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存，他们给这片内存起了个名，叫做 Buffer Pool （中文名是 缓冲池 ）。那它有多大呢？这个其实看我们机器的配置，如果你是土豪，你有 512G 内存，你分配个几百 G 作为 Buffer Pool 也可以啊，当然你要是没那么有钱，设置小点也行呀～ 默认情况下 Buffer Pool 只有 128M 大小。当然如果你嫌弃这个 128M 太大或者太小，可以在启动服务器的时候配置 innodb_buffer_pool_size 参数的值，它表示 Buffer Pool 的大小，就像这样：
&lt;br>&lt;br>其中， 268435456 的单位是字节，也就是我指定 Buffer Pool 的大小为 256M 。需要注意的是， Buffer Pool 也不能太小，最小值为 5M (当小于该值时会自动设置成 5M )。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/19_transaction-intro/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/19_transaction-intro/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="事务的起源">
 事务的起源
 &lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1%e7%9a%84%e8%b5%b7%e6%ba%90">#&lt;/a>
&lt;/h2>
&lt;p class="warn">对于大部分程序员来说，他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个 account 表：
&lt;br>&lt;br>狗哥和猫爷是一对好基友，他们都到银行开一个账户，他们在现实世界中拥有的资产就会体现在数据库世界的 account 表中。比如现在狗哥有 11 元，猫爷只有 2 元，那么现实中的这个情况映射到数据库的 account 表就是这样：&lt;/p> &lt;div class="docsify-example-panels"> &lt;div class="docsify-example-panel left-panel"style="max-width: 60%; width: 60%;">
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">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;

-- 插入数据
INSERT INTO `account` (`id`, `name`, `balance`) VALUES (1,'狗哥',11),(2,'猫爷',2);
&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
 &lt;div class="docsify-example-panel right-panel"style="max-width: 40%; width: 40%;">
&lt;p>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/19_transaction-intro/ti-01.png" alt="" width="78%">&lt;/p>&lt;/div>&lt;/div>

&lt;p>在某个特定的时刻，狗哥猫爷这些家伙在银行所拥有的资产是一个特定的值，这些特定的值也可以被描述为账户在这个特定的时刻现实世界的一个状态。随着时间的流逝，狗哥和猫爷可能陆续进行向账户中存钱、取钱或者向别人转账等操作，这样他们账户中的余额就可能发生变动，&lt;span style='color:red'>每一个操作都相当于现实世界中账户的一次状态转换&lt;/span>。数据库世界作为现实世界的一个映射，自然也要进行相应的变动。不变不知道，一变吓一跳，现实世界中一些看似很简单的状态转换，映射到数据库世界却不是那么容易的。比方说有一次猫爷在赌场赌博输了钱，急忙打电话给狗哥要借10块钱，不然那些看场子的就会把自己剁了。现实世界中的狗哥走向了ATM机，输入了猫爷的账号以及10元的转账金额，然后按下确认，狗哥就拔卡走人了。对于数据库世界来说，相当于执行了下边这两条语句：&lt;/p>
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;
&lt;/code>&lt;/pre>&lt;/div>
&lt;p>但是这里头有个问题，上述两条语句只执行了一条时忽然服务器断电了咋办？把狗哥的钱扣了，但是没给猫爷转过去，那猫爷还是逃脱不了被砍死的噩运～ 即使对于单独的一条语句，我们前边唠叨 Buffer Pool 时也说过，在对某个页面进行读写访问时，都会先把这个页面加载到 Buffer Pool 中，之后如果修改了某个页面，也不会立即把修改同步到磁盘，而只是把这个修改了的页面加到 Buffer Pool 的 flush链表 中，在之后的某个时间点才会刷新到磁盘。如果在将修改过的页刷新到磁盘之前系统崩溃了那岂不是猫爷还是要被砍死？或者在刷新磁盘的过程中（只刷新部分数据到磁盘上）系统奔溃了猫爷也会被砍死？怎么才能保证让可怜的猫爷不被砍死呢？其实再仔细想想，我们只是想&lt;span style='color:red'>让某些数据库操作符合现实世界中状态转换的规则&lt;/span>而已，设计数据库的大叔们仔细盘算了盘算，现实世界中状态转换的规则有好几条，待我们慢慢道来。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/20_redo-01/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/20_redo-01/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="事先说明">
 事先说明
 &lt;a class="anchor" href="#%e4%ba%8b%e5%85%88%e8%af%b4%e6%98%8e">#&lt;/a>
&lt;/h2>
&lt;p class="tip">本文以及接下来的几篇文章将会频繁的使用到我们前边唠叨的 InnoDB 记录行格式、页面格式、索引原理、表空间的组成等各种基础知识，如果大家对这些东西理解的不透彻，那么阅读下边的文字可能会有些吃力，为保证您的阅读体验，请确保自己已经掌握了我前边唠叨的这些知识。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="redo日志是个啥">
 redo日志是个啥
 &lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%98%af%e4%b8%aa%e5%95%a5">#&lt;/a>
&lt;/h2>
&lt;p class="warn">我们知道 InnoDB 存储引擎是以页为单位来管理存储空间的，我们进行的增删改查操作其实本质上都是在访问页面（包括读页面、写页面、创建新页面等操作）。我们前边唠叨 Buffer Pool 的时候说过，在真正访问页面之前，需要把在磁盘上的页缓存到内存中的 Buffer Pool 之后才可以访问。但是在唠叨事务的时候又强调过一个称之为 持久性 的特性，就是说对于一个已经提交的事务，在事务提交后即使系统发生了崩溃，这个事务对数据库中所做的更改也不能丢失。但是如果我们只在内存的 Buffer Pool 中修改了页面，假设在事务提交后突然发生了某个故障，导致内存中的数据都失效了，那么这个已经提交了的事务对数据库中所做的更改也就跟着丢失了，这是我们所不能忍受的（想想ATM机已经提示狗哥转账成功，但之后由于服务器出现故障，重启之后猫爷发现自己没收到钱，猫爷就被砍死了）。那么如何保证这个 持久性 呢？一个很简单的做法就是&lt;span style='color:red'>在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘&lt;/span>，但是这个简单粗暴的做法有些问题：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;em>刷新一个完整的数据页太浪费了&lt;/em>：有时候我们仅仅修改了某个页面中的一个字节，但是我们知道在 InnoDB 中是以页为单位来进行磁盘IO的，也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘，我们又知道一个页面默认是16KB大小，只修改一个字节就要刷新16KB的数据到磁盘上显然是太浪费了。
&lt;br>&lt;span style='padding-left:1.2em'>&lt;em>随机IO刷起来比较慢&lt;/em>：一个事务可能包含很多语句，即使是一条语句也可能修改许多页面，倒霉催的是该事务修改的这些页面可能并不相邻，这就意味着在将某个事务修改的 Buffer Pool 中的页面刷新到磁盘时，需要进行很多的随机IO，随机IO比顺序IO要慢，尤其对于传统的机械硬盘来说。
&lt;br>&lt;br>咋办呢？再次回到我们的初心：&lt;span style='color:red'>我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效，即使后来系统崩溃，在重启后也能把这种修改恢复出来&lt;/span>。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘，只需要&lt;span style='color:red'>把修改了哪些东西记录一下就好&lt;/span>，比方说某个事务将系统表空间中的第100 号页面中偏移量为1000处的那个字节的值 1 改成 2 我们只需要记录一下：
&lt;br>&lt;code>将第0号表空间的100号页面的偏移量为1000处的值更新为 2&lt;/code>。
&lt;br>&lt;br>这样我们在事务提交时，把上述内容刷新到磁盘中，即使之后系统崩溃了，重启之后只要按照上述内容所记录的步骤重新更新一下数据页，那么该事务对数据库中所做的修改又可以被恢复出来，也就意味着满足 持久性 的要求。因为在系统奔溃重启时需要按照上述内容所记录的步骤重新更新数据页，所以上述内容也被称之为 &lt;strong>重做日志&lt;/strong> ，英文名为 &lt;strong>redo log&lt;/strong> ，我们也可以土洋结合，称之为 &lt;strong>redo日志&lt;/strong> 。与在事务提交时将所有修改过的内存中的页面刷新到磁盘中相比，只将该事务执行过程中产生的 redo 日志刷新到磁盘的好处如下：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;em>redo 日志占用的空间非常小&lt;/em>：存储表空间ID、页号、偏移量以及需要更新的值所需的存储空间是很小的，关于 redo 日志的格式我们稍后会详细唠叨，现在只要知道一条 redo 日志占用的空间不是很大就好了。
&lt;br>&lt;span style='padding-left:1.2em'>&lt;em>redo 日志是顺序写入磁盘的&lt;/em>：在执行事务的过程中，每执行一条语句，就可能产生若干条 redo 日志，这些日志是按照产生的顺序写入磁盘的，也就是使用顺序IO。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="redo日志格式">
 redo日志格式
 &lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%a0%bc%e5%bc%8f">#&lt;/a>
&lt;/h2>
&lt;p class="warn">通过上边的内容我们知道， redo 日志本质上只是记录了一下事务对数据库做了哪些修改。 设计 InnoDB 的大叔们针对事务对数据库的不同修改场景定义了多种类型的 redo 日志，但是绝大部分类型的 redo 日志都有下边这种通用的结构：
&lt;br>&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/20_redo-01/rd-01.png" alt="" width="70%">
&lt;br>&lt;br>各个部分的详细释义如下：
&lt;br>&lt;span style='padding-left:1.2em'>type ：该条 redo 日志的类型。（在 MySQL 5.7.21 这个版本中，设计 InnoDB 的大叔一共为 redo 日志设计了53种不同的类型，稍后会详细介绍不同类型的 redo 日志。)
&lt;br>&lt;span style='padding-left:1.2em'>space ID ：表空间ID。
&lt;br>&lt;span style='padding-left:1.2em'>page number ：页号。
&lt;br>&lt;span style='padding-left:1.2em'>data ：该条 redo 日志的具体内容。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/21_redo-02/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/21_redo-02/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="redo日志文件">
 redo日志文件
 &lt;a class="anchor" href="#redo%e6%97%a5%e5%bf%97%e6%96%87%e4%bb%b6">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 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;/h3>
&lt;p>我们前边说 mtr 运行过程中产生的一组 redo 日志在 mtr 结束时会被复制到 log buffer 中，可是这些日志总在内存里呆着也不是个办法，在一些情况下它们会被刷新到磁盘里，比如：&lt;/p>
&lt;ul>
&lt;li>
&lt;p>log buffer 空间不足时&lt;/p>
&lt;p>log buffer 的大小是有限的（通过系统变量 innodb_log_buffer_size 指定），如果不停的往这个有限大小的 log buffer 里塞入日志，很快它就会被填满。设计 InnoDB 的大叔认为如果当前写入 log buffer 的redo 日志量已经占满了 log buffer 总容量的大约一半左右，就需要把这些日志刷新到磁盘上。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>事务提交时&lt;/p>
&lt;p>我们前边说过之所以使用 redo 日志主要是因为它占用的空间少，还是顺序写，在事务提交时可以不把修改过的 Buffer Pool 页面刷新到磁盘，但是为了保证持久性，必须要把修改这些页面对应的 redo 日志刷新到磁盘。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>后台线程不停的刷刷刷&lt;/p>
&lt;p>后台有一个线程，大约每秒都会刷新一次 log buffer 中的 redo 日志到磁盘。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>正常关闭服务器时&lt;/p>
&lt;p>做所谓的 checkpoint 时（我们现在没介绍过 checkpoint 的概念，稍后会仔细唠叨，稍安勿躁）&lt;/p>
&lt;/li>
&lt;li>
&lt;p>其他的一些情况&amp;hellip;&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;h3 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;/h3>
&lt;p>MySQL 的数据目录（使用 SHOW VARIABLES LIKE &amp;lsquo;datadir&amp;rsquo; 查看）下默认有两个名为 ib_logfile0 和 ib_logfile1 的文件， log buffer 中的日志默认情况下就是刷新到这两个磁盘文件中。如果我们对默认的 redo 日志文件不满意，可以通过下边几个启动参数来调节：&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/22_undo-01/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/22_undo-01/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p>我们说过 事务 需要保证 原子性 ，也就是事务中的操作要么全部完成，要么什么也不做。但是偏偏有时候事务执行到一半会出现一些情况，比如：&lt;/p>
&lt;ol>
&lt;li>情况一：事务执行过程中可能遇到各种错误，比如服务器本身的错误，操作系统错误，甚至是突然断电导致的错误。&lt;/li>
&lt;li>情况二：程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前的事务的执行。&lt;/li>
&lt;/ol>
&lt;p>这两种情况都会导致事务执行到一半就结束，但是事务执行过程中可能已经修改了很多东西，为了保证事务的原子性，我们需要把东西改回原先的样子，这个过程就称之为 回滚 （英文名： rollback ），这样就可以造成一个假象：&lt;span style='color:red'>这个事务看起来什么都没做&lt;/span>，所以符合 原子性 要求。&lt;/p>
&lt;p>小时候我非常痴迷于象棋，总是想找厉害的大人下棋，赢棋是不可能赢棋的，这辈子都不可能赢棋的，又不想认输，只能偷偷的悔棋才能勉强玩的下去。 悔棋 就是一种非常典型的 回滚 操作，比如棋子往前走两步， 悔棋 对应的操作就是向后走两步；比如棋子往左走一步， 悔棋 对应的操作就是向右走一步。数据库中的回滚跟 悔棋 差不多，你插入了一条记录， 回滚 操作对应的就是把这条记录删除掉；你更新了一条记录， 回滚 操作对应的就是把该记录更新为旧值；你删除了一条记录， 回滚 操作对应的自然就是把该记录再插进去。说的貌似很简单的样子[手动偷笑😏]。&lt;/p>
&lt;p>从上边的描述中我们已经能隐约感觉到，每当我们要对一条记录做改动时（这里的 改动 可以指 INSERT 、DELETE 、 UPDATE ），都需要留一手 —— &lt;span style='color:red'>把回滚时所需的东西都给记下来&lt;/span>。比方说：&lt;/p>
&lt;ul>
&lt;li>你插入一条记录时，至少要把这条记录的主键值记下来，之后回滚的时候只需要把这个主键值对应的记录删掉就好了。&lt;/li>
&lt;li>你删除了一条记录，至少要把这条记录中的内容都记下来，这样之后回滚时再把由这些内容组成的记录插入到表中就好了。&lt;/li>
&lt;li>你修改了一条记录，至少要把修改这条记录前的旧值都记录下来，这样之后回滚时再把这条记录更新为旧值就好了。&lt;/li>
&lt;/ul>
&lt;p>设计数据库的大叔把这些为了回滚而记录的这些东东称之为撤销日志，英文名为 undo log ，我们也可以土洋结合，称之为 undo日志 。这里需要注意的一点是，由于查询操作（ SELECT ）并不会修改任何用户记录，所以在查询操作执行时，并不需要记录相应的 undo日志 。在真实的 InnoDB 中， undo日志 其实并不像我们上边所说的那么简单，不同类型的操作产生的 undo日志 的格式也是不同的，不过先暂时把这些容易让人脑子糊的具体细节放一放，我们先回过头来看看 事务id 是个神马玩意儿。&lt;/p>
&lt;/li>
&lt;li>
&lt;h2 id="事务id">
 事务id
 &lt;a class="anchor" href="#%e4%ba%8b%e5%8a%a1id">#&lt;/a>
&lt;/h2>
&lt;ul>
&lt;li>
&lt;h3 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;/h3>
&lt;p>我们前边在唠叨 事务简介 时说过，一个事务可以是一个只读事务，或者是一个读写事务：&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/23_undo-02/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/23_undo-02/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="书接上文">
 书接上文
 &lt;a class="anchor" href="#%e4%b9%a6%e6%8e%a5%e4%b8%8a%e6%96%87">#&lt;/a>
&lt;/h2>
&lt;div class="alert callout attention">&lt;p class="title">&lt;span class="icon icon icon-attention">&lt;/span> prerequirement &lt;/p>&lt;p> 上一章我们主要唠叨了为什么需要 &lt;strong>undo日志&lt;/strong> ，以及 INSERT 、 DELETE 、 UPDATE 这些会对数据做改动的语句都会产生什么类型的 undo日志 ，还有不同类型的 undo日志 的具体格式是什么。本章会继续唠叨这些 undo日志 会被具体写到什么地方，以及在写入过程中需要注意的一些问题。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;h2 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;/h2>
&lt;p>在写入 undo日志 的过程中会使用到多个链表，很多链表都有同样的节点结构，如图所示：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/23_undo-02/ud-01.png" alt="" width="60%">&lt;/p>
&lt;p>在某个表空间内，我们可以通过一个页的页号和在页内的偏移量来唯一定位一个节点的位置，这两个信息也就相当于指向这个节点的一个指针。所以：&lt;/p>
&lt;ul>
&lt;li>Pre Node Page Number 和 Pre Node Offset 的组合就是指向前一个节点的指针&lt;/li>
&lt;li>Next Node Page Number 和 Next Node Offset 的组合就是指向后一个节点的指针。&lt;/li>
&lt;/ul>
&lt;p>整个 List Node 占用 12 个字节的存储空间。&lt;/p>
&lt;p>为了更好的管理链表，设计 InnoDB 的大叔还提出了一个基节点的结构，里边存储了这个链表的 头节点 、 尾节点 以及链表长度信息，基节点的结构示意图如下：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/23_undo-02/ud-02.png" alt="" width="60%">&lt;/p>
&lt;p>其中：&lt;/p>
&lt;ul>
&lt;li>List Length 表明该链表一共有多少节点。&lt;/li>
&lt;li>First Node Page Number 和 First Node Offset 的组合就是指向链表头节点的指针。&lt;/li>
&lt;li>Last Node Page Number 和 Last Node Offset 的组合就是指向链表尾节点的指针。&lt;/li>
&lt;/ul>
&lt;p>整个 List Base Node 占用 16 个字节的存储空间。&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/24_transaction-isolate-and-mvcc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/24_transaction-isolate-and-mvcc/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="事前准备">
 事前准备
 &lt;a class="anchor" href="#%e4%ba%8b%e5%89%8d%e5%87%86%e5%a4%87">#&lt;/a>
&lt;/h2>
&lt;p>为了故事的顺利发展，我们需要创建一个表：&lt;/p> &lt;div class="docsify-example-panels"> &lt;div class="docsify-example-panel left-panel"style="max-width: 50%; width: 50%;">
&lt;div class="outer yosemite">&lt;div class="dot red">&lt;/div>&lt;div class="dot amber">&lt;/div>&lt;div class="dot green">&lt;/div>&lt;/div>
&lt;div class="code-toolbar">&lt;pre data-lang="sql" data-line="" class="language-sql line-numbers" style="max-height: none">&lt;code class="language-sql">CREATE TABLE hero (
 number INT,
 name VARCHAR(100),
 country varchar(100),
 PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;

-- 然后向这个表里插入一条数据：
INSERT INTO hero VALUES(1, '刘备', '蜀');

-- 现在表里的数据就是这样的：
mysql&amp;gt; SELECT * FROM hero;
+--------+--------+---------+
| number | name | country |
+--------+--------+---------+
| 1 | 刘备 | 蜀 |
+--------+--------+---------+
1 row in set (0.00 sec)
&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
 &lt;div class="docsify-example-panel left-panel"style="max-width: 50%; width: 50%;">
&lt;p>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/24_transaction-isolate-and-mvcc/tiam-01.png" alt="" width="66%">&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/25_lock/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/25_lock/</guid><description>&lt;ul>
&lt;li>
&lt;h2 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;/h2>
&lt;p>上一章唠叨了事务并发执行时可能带来的各种问题，并发事务访问相同记录的情况大致可以划分为3种：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>读-读 情况：即并发事务相继读取相同的记录。&lt;/p>
&lt;p>读取操作本身不会对记录有一毛钱影响，并不会引起什么问题，所以允许这种情况的发生。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>写-写 情况：即并发事务相继对相同的记录做出改动。&lt;/p>
&lt;p>我们前边说过，在这种情况下会发生 脏写 的问题，任何一种隔离级别都不允许这种问题的发生。所以在多个未提交事务相继对一条记录做改动时，需要让它们排队执行，这个排队的过程其实是通过 锁 来实现的。这个所谓的 锁 其实是一个内存中的结构，在事务执行前本来是没有锁的，也就是说一开始是没有 锁结构 和记录进行关联的，如图所示：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/25_lock/lock-01.png" alt="" width="40%">&lt;/p>
&lt;p>当一个事务想对这条记录做改动时，首先会看看内存中有没有与这条记录关联的 锁结构 ，当没有的时候就会在内存中生成一个 锁结构 与之关联。比方说事务 T1 要对这条记录做改动，就需要生成一个 锁结构 与之关联：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/25_lock/lock-02.png" alt="" width="80%">&lt;/p>
&lt;p>其实在 锁结构 里有很多信息，不过为了简化理解，我们现在只把两个比较重要的属性拿了出来：&lt;/p>
&lt;ul>
&lt;li>trx信息 ：代表这个锁结构是哪个事务生成的。&lt;/li>
&lt;li>is_waiting ：代表当前事务是否在等待。&lt;/li>
&lt;/ul>
&lt;p>如图所示，当事务 T1 改动了这条记录后，就生成了一个 锁结构 与该记录关联，因为之前没有别的事务为这条记录加锁，所以 is_waiting 属性就是 false ，我们把这个场景就称之为&lt;span style='color:red'>获取锁成功，或者加锁成功&lt;/span>，然后就可以继续执行操作了。&lt;/p>
&lt;p>在事务 T1 提交之前，另一个事务 T2 也想对该记录做改动，那么先去看看有没有 锁结构 与这条记录关联，发现有一个 锁结构 与之关联后，然后也生成了一个 锁结构 与这条记录关联，不过 锁结构 的is_waiting 属性值为 true ，表示当前事务需要等待，我们把这个场景就称之为&lt;span style='color:red'>获取锁失败，或者加锁失败，或者没有成功的获取到锁&lt;/span>，画个图表示就是这样：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/25_lock/lock-03.png" alt="" width="80%">&lt;/p>
&lt;p>在事务 T1 提交之后，就会把该事务生成的 锁结构 释放掉，然后看看还有没有别的事务在等待获取锁，发现了事务 T2 还在等待获取锁，所以把事务 T2 对应的锁结构的 is_waiting 属性设置为 false ，然后把该事务对应的线程唤醒，让它继续执行，此时事务 T2 就算获取到锁了。效果图就是这样：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;img src="https://archive-w.netlify.app/.images/doc/framework/mysql/book/25_lock/lock-04.png" alt="" width="80%">&lt;/p></description></item><item><title/><link>https://archive-w.netlify.app/doc/framework/mysql/book/26_reference/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://archive-w.netlify.app/doc/framework/mysql/book/26_reference/</guid><description>&lt;ul>
&lt;li>
&lt;h2 id="感谢">
 感谢
 &lt;a class="anchor" href="#%e6%84%9f%e8%b0%a2">#&lt;/a>
&lt;/h2>
&lt;p class="warn">我不生产知识，只是知识的搬运工。写作本小册的时间主要用在了两个方面：
&lt;br>&lt;span style='padding-left:1.2em'>&lt;strong>搞清楚事情的本质是什么&lt;/strong>。这个过程就是研究源码、书籍和资料。
&lt;br>&lt;br>&lt;span style='padding-left:1.2em'>&lt;strong>如何把我已经知道的知识表达出来&lt;/strong>。这个过程就是我不停的在地上走过来走过去，梳理知识结构，斟酌用词用句，不停的将已经写好的文章推倒重来，只是想给大家一个不错的用户体验。
&lt;br>&lt;br>这两个方面用的时间基本上是一半一半吧，在搞清楚事情的本质是什么阶段，除了直接阅读 MySQL 的源码之外，查看参考资料也是一种比较偷懒的学习方式。本书只是 MySQL 进阶的一个入门，想了解更多关于 MySQL 的知识，大家可以从下边这些资料里找点灵感。&lt;/p>
&lt;ul>
&lt;li>
&lt;h3 id="一些链接">
 一些链接
 &lt;a class="anchor" href="#%e4%b8%80%e4%ba%9b%e9%93%be%e6%8e%a5">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>
&lt;p>MySQL官方文档：


 &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/" rel="noopener" target="_blank">https://dev.mysql.com/doc/refman/5.7/en/&lt;/a>&lt;/p>
&lt;p>MySQL 官方文档是写作本书时参考最多的一个资料。说实话，文档写的非常通俗易懂，唯一的缺点就是太长了，导致大家看的时候无从下手。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>MySQL Internals Manual：


 &lt;a href="https://dev.mysql.com/doc/internals/en/" rel="noopener" target="_blank">https://dev.mysql.com/doc/internals/en/&lt;/a>&lt;/p>
&lt;p>介绍MySQL如何实现各种功能的文档，写的比较好，但是太少了，有很多章节直接跳过了。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>何登成的github：


 &lt;a href="https://github.com/hedengcheng/tech" rel="noopener" target="_blank">https://github.com/hedengcheng/tech&lt;/a>&lt;/p>
&lt;p>登博的博客非常好，对事务、优化这讨论的细节也非常多，不过由于大多是PPT结构，字太少，对上下文不清楚的同学可能会一脸懵逼。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>orczhou的博客：


 &lt;a href="http://www.orczhou.com/" rel="noopener" target="_blank">http://www.orczhou.com/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Jeremy Cole的博客：


 &lt;a href="https://blog.jcole.us/innodb/" rel="noopener" target="_blank">https://blog.jcole.us/innodb/&lt;/a>&lt;/p>
&lt;p>Jeremy Cole大神不仅写作了 innodb_ruby 这个非常棒的解析 InnoDB 存储结构的工具，还对这些存储结构写了一系列的博客，在我几乎要放弃深入研究表空间结构的时候，是他老人家的博客把我又从深渊里拉了回来。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>那海蓝蓝（李海翔）的博客：


 &lt;a href="https://blog.csdn.net/fly2nn" rel="noopener" target="_blank">https://blog.csdn.net/fly2nn&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>taobao月报：


 &lt;a href="http://mysql.taobao.org/monthly/" rel="noopener" target="_blank">http://mysql.taobao.org/monthly/&lt;/a>&lt;/p>
&lt;p>因为MySQL的源码非常多，经常让大家无从下手，而taobao月报就是一个非常好的源码阅读指南。&lt;/p>
&lt;div class="alert callout note">&lt;p class="title">&lt;span class="icon icon-note">&lt;/span> Note &lt;/p>&lt;p> 吐槽一下，这个taobao月报也只能当作源码阅读指南看，如果真的不看源码光看月报，那只能当作天书看，十有八九被绕进去出不来了。&lt;/p>
&lt;/p>&lt;/div>
&lt;/li>
&lt;li>
&lt;p>MySQL Server Blog：


 &lt;a href="http://mysqlserverteam.com/" rel="noopener" target="_blank">http://mysqlserverteam.com/&lt;/a>&lt;/p>
&lt;p>MySQL team的博客，一手资料，在我不知道看什么的时候给了很多启示。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>mysql_lover的博客：


 &lt;a href="https://blog.csdn.net/mysql_lover/" rel="noopener" target="_blank">https://blog.csdn.net/mysql_lover/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Jørgen&amp;rsquo;s point of view：


 &lt;a href="https://jorgenloland.blogspot.com/" rel="noopener" target="_blank">https://jorgenloland.blogspot.com/&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>mariadb的关于查询优化的文档：


 &lt;a href="https://mariadb.com/kb/en/library/query-optimizations/" rel="noopener" target="_blank">https://mariadb.com/kb/en/library/query-optimizations/&lt;/a>&lt;/p>
&lt;p>不得不说mariadb的文档相比MySQL的来说就非常有艺术性了（里边儿有很多漂亮的插图），我很怀疑 MySQL 文档是程序员直接写的，mariadb的文档是产品经理写的。当我们想研究某个功能的原理，在MySQL 文档干巴巴的说明中找不到头脑时，可以参考一下 mariadb 娓娓道来的风格。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Reconstructing Data Manipulation Queries from Redo Logs：


 &lt;a href="https://www.sba-research.org/wpcontent/uploads/publications/WSDF2012_InnoDB.pdf" rel="noopener" target="_blank">https://www.sba-research.org/wpcontent/uploads/publications/WSDF2012_InnoDB.pdf&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>关于InnoDB事务的一个PPT：


 &lt;a href="https://mariadb.org/wp-content/uploads/2018/02/Deep-Dive_-InnoDBTransactions-and-Write-Paths.pdf" rel="noopener" target="_blank">https://mariadb.org/wp-content/uploads/2018/02/Deep-Dive_-InnoDBTransactions-and-Write-Paths.pdf&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>非官方优化文档：


 &lt;a href="http://www.unofficialmysqlguide.com/optimizer-trace.html" rel="noopener" target="_blank">http://www.unofficialmysqlguide.com/optimizer-trace.html&lt;/a>&lt;/p>
&lt;p>这个文档非常好，非常非常好～&lt;/p></description></item></channel></rss>