<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>MySQL归档 - 枫阿雨&#039;s blog</title>
	<atom:link href="https://www.crazyfay.com/tag/mysql/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.crazyfay.com/tag/mysql/</link>
	<description>CrazyFay</description>
	<lastBuildDate>Tue, 04 Apr 2023 06:13:13 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.2</generator>

<image>
	<url>https://www.crazyfay.com/wp-content/uploads/2023/04/cropped-DockerGopher-32x32.png</url>
	<title>MySQL归档 - 枫阿雨&#039;s blog</title>
	<link>https://www.crazyfay.com/tag/mysql/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>MySQL索引及其优化总结 &#8211; CFC例会2022.4.17</title>
		<link>https://www.crazyfay.com/2022/04/17/mysql%e7%b4%a2%e5%bc%95%e5%8f%8a%e5%85%b6%e4%bc%98%e5%8c%96%e6%80%bb%e7%bb%93-cfc%e4%be%8b%e4%bc%9a2022-4-17/</link>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sun, 17 Apr 2022 09:56:30 +0000</pubDate>
				<category><![CDATA[CFC例会]]></category>
		<category><![CDATA[CFC]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[数据库]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=116</guid>

					<description><![CDATA[<p>MySQL索引及其优化 MySQL的基础架构 Server层 ：核心服务功能与跨引擎功能的实现（如所有的内置函 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/04/17/mysql%e7%b4%a2%e5%bc%95%e5%8f%8a%e5%85%b6%e4%bc%98%e5%8c%96%e6%80%bb%e7%bb%93-cfc%e4%be%8b%e4%bc%9a2022-4-17/">MySQL索引及其优化总结 &#8211; CFC例会2022.4.17</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>MySQL索引及其优化</h1>
<h2>MySQL的基础架构</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/image-20220407190405002.png" alt="image-20220407190405002" /></p>
<ul>
<li>Server层 ：核心服务功能与跨引擎功能的实现（如所有的内置函数、存储过程、触发器等）
<ul>
<li>连接器：管理连接，权限验证</li>
<li>命中缓存：命中缓存则直接返回结果（8.0版本后删除）</li>
<li>分析器：词法分析，语法分析</li>
<li>优化器：执行计划生成，索引选择</li>
<li>执行器：操作引擎返回结果</li>
</ul>
</li>
<li>存储引擎：数据的存储与提取，插件式的架构模式
<ul>
<li>InnoDB *</li>
<li>MyISMA</li>
</ul>
</li>
</ul>
<h2>SQL语句的执行流程</h2>
<blockquote>
<p>一条SQL查询语句是怎么执行的</p>
</blockquote>
<pre><code class="language-sql">SELECT * FROM student WHERE id = 12004030124;</code></pre>
<p>在整体架构层面分析此条语句的执行过程</p>
<p><code>连接器</code> </p>
<p>连接器负责跟客户端建立连接、获取权限、维持和管理连接。并负责校验用户身份。</p>
<pre><code class="language-sql">mysql [-h$ip -p$port] -u$user -p [password 不建议把密码写在此条语句]</code></pre>
<p>命令行中的mysql是客户端工具，在执行完词条语句之后将会与服务器进行连接，即通过TCP三次握手建立连接之后会校验用户身份</p>
<p>连接器会验证用户输入的用户名和密码，若存在问题则会返回错误并且客户端程序结束执行</p>
<p>在通过身份登录验证之后，连接器会查询该用户的权限并保存，接下来所有的操作权限都基于此次查询，即使用户权限发生了更改，在此次连接没有重新的建立的情况下，用户的权限不会直接更改。</p>
<p><code>查询缓存</code></p>
<p>在建立好连接之后就可以进行sql语句的执行了。</p>
<p>执行逻辑将会来到第二部：查询缓存</p>
<p>在拿到查询请求的适合，会先去查询缓存，缓存中以key-value的形式存储查询操作和查询结果，如果缓存中记录了执行过此查询操作，则直接返回该查询操作所对用的结果，即不需要再向下执行复杂的查询工作，可以直接返回查询操作的结果大大的简化了执行时间，如果说没有命中缓存，则向下执行通过IO过程查询，在返回时将查询结果返回到查询缓存中存储。使用缓存是需要代价的，而且往往弊大于利，因为每次表更新都会将查询结果清空，所以对于更新压力大的业务数据库来说，缓存命中率极低，在很多情况下查询缓存甚至是一种性能负担，所以在非表更新极少的静态表之外，不建议使用查询缓存。</p>
<blockquote>
<p>注：在mysql8.0之后查询缓存被彻底的删除</p>
</blockquote>
<p><code>分析器</code></p>
<p>在没有命中缓存的情况下，就到了分析器</p>
<p>先进行 &quot;词法分析&quot;，mysql需要知道sql语句中的内容，识别关键字，例如识别到select关键字，并分析出student为表名，id为列名。在完成&quot;词法分析&quot;后，分析器将进行&quot;语法分析&quot;，分析sql语句是否存在语法问题，若存在则返回错误提示并终止执行。</p>
<p><code>优化器</code></p>
<p>在分析完执行内容之后，优化器会进行相应的优化</p>
<p>优化器会选择如何使用索引和如何进行表连接顺序，并选择执行效率最高的方案执行语句。</p>
<p><code>执行器</code></p>
<p>在优化器选择好执行方案之后，执行器就开始执行sql语句</p>
<p>首先执行器会判断当前用户是否拥有查询此表的权限啊，若无权限在返回错误并终止执行。若有权限打开表使用存储引擎的接口进行操作。例如在此条语句中，因为表中没有定义索引，执行过程大致如下：</p>
<ol>
<li>调用存储引擎接口读取表的第一行，判断是否与条件匹配，即id是否等于0</li>
<li>调用存储引擎接口读取下一行，判断是否匹配，重复执行直到读取完最后一行</li>
<li>将查询到的结果集返回给客户端</li>
</ol>
<p>如果表中存在索引，查询方式也大致相同，调用的是满足条件的第一行和下一行</p>
<h2>索引</h2>
<h3>什么是索引</h3>
<p>在关系数据库中，索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构，它是表中一列或多列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。</p>
<p>索引（Index）是帮助MySQL⾼效获取数据的数据结构。提取句⼦ 主⼲，就可以得到索引的本质：索引是数据结构</p>
<h3>索引的作用</h3>
<ul>
<li>保证数据的准确性</li>
<li>提高检索速度</li>
<li>提高系统性能</li>
</ul>
<h3>索引的类型</h3>
<p>system &gt; const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; all</p>
<h2>InnoDB索引的数据结构</h2>
<p>⼀般来说，索引本身也很⼤，不可能全部存储在内存中，因此索引往往以索引⽂件的形式存储的 磁盘上。这样的话，索引查找过程中就要产⽣磁盘I/O消耗，相对于内存存取，I/O存取的消耗要⾼⼏个 数量级，所以评价⼀个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进 复杂度。换句话说，索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。</p>
<p><font color=blue>分析: </font>有哪些数据结构可以用来作为索引的存储容器</p>
<h3>HashTable</h3>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/src=http___nimg.ws.126.net__url=http___dingyue.ws.126.net_2020_1223_2dd7c986j00qlrut10012c000rq00eam.jpg&amp;thumbnail=650x2147483647&amp;quality=80&amp;type=jpg&amp;refer=http___nimg.ws.126.jpg" alt="" /></p>
<ul>
<li>
<p><strong>优点：</strong>HashTable是字典（dict）的一种经典实现，通过对Key进行散列值计算，我们可以直接得到对应数据的存放位置，可以实现时间复杂度为O(1)的极快的查找速度。</p>
</li>
<li>
<p><strong>缺点：</strong></p>
<ol>
<li>
<p>不支持模糊匹配，由于哈希计算没有局部特性，例如hash(枫阿雨)跟hash(枫阿)没有关系</p>
<ol start="2">
<li>不支持范围匹配，没有顺序性</li>
<li>不支持组合索引，因为hash值需要合在一起计算，所以不能支持最左匹配原则</li>
<li>哈希冲突问题</li>
</ol>
</li>
</ol>
</li>
</ul>
<h3>跳表</h3>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/2d61084503f683b17305049a5eedf60e.png" alt="" /></p>
<ul>
<li>
<p><strong>优点：</strong>跳表基于链式结构，可以多点之间的直接连接，每个节点可以有多个指针指向不同的next节点，查询的时间复杂度为O(n)，但如果节点指针设计的好，可以跳过某些不需要查询的节点，直接定位到数据，可以让n的值变小，从而带来比较大的性能提升</p>
</li>
<li>
<p><strong>缺点：</strong></p>
<ol>
<li>MySQL数据库的存储介质在磁盘当中，而链式结构的结构体存放在内存当中，而且MySLQL进行数据索引的时候是以块的形式，即每个块为16KB的内存页，然后在内存页中进行数据的定位，而跳表所使用的是我们所谓的链表中的Node节点，而且指针管理非常复杂，不适用于磁盘存储介质</li>
<li>MySQL中涉及的查询较多且复杂，如果使用联合索引在跳表的数据结构下，假设有两个字段做联合索引，首先我们需要按首字段进行排序，基于此基础上再对第二个字段进行排序，如果在查询过程中使用跳表，除了要维护第一个的多个节点的跳跃指针，还要想办法维护第二个节点的跳跃指针，指针的管理将会非常的困难，而且还要分别进行不同列的标记。如果多个字段的联合索引则更复杂。</li>
<li>客观上，跳表这种数据结构的出现并应用的时间较晚，此时MySQL已有了自己的实现方式 </li>
</ol>
</li>
</ul>
<h3>红黑树</h3>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/15811884567814.png" alt="" /></p>
<ul>
<li>
<p><strong>优点：</strong>红黑树是一种近似平衡（不完全平衡），结点非黑即红的树，它的树高最高不会超过 2logn，因此查找的时间复杂度为 O(logn)，无论是增删改查，它的性能都十分稳定。</p>
</li>
<li>
<p><strong>缺点：</strong></p>
<ol>
<li>因为二叉树的只有两个子节点，相同存储容量时，树的高度太高，每次节点的访问都对应着一次磁盘IO，红黑树属于一种二叉树，虽然拥有稳定平衡的功能，但是大量的磁盘IO在应用程序中是灾难性的，即若红黑树的高度为20，那么最坏情况下读取一个数据需要进行20次磁盘IO这显然是无法接受的</li>
</ol>
</li>
</ul>
<h3>B-树</h3>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/12058546-44a71668594a77d9.png" alt="" /></p>
<ul>
<li><strong>优点：</strong>B-树是一种专门为磁盘数据读取设计的一种度为n的多路平衡查找树。既然二叉树因为每个结点最多只有两个子结点，最终在存储大量数据时导致树高太高，因此不适合当做 MySQL 的索引，那么让树的每个结点尽可能多的拥有多个子结点，这样在大量储存数据时，树高就相对低很多了，磁盘IO的次数也就大大减少</li>
<li><strong>缺点：</strong>
<ol>
<li>每个节点中既要存索引信息，又要存其对应的数据，如果数据很大，那么当树的体量很大时，每次读到内存中的树的信息就会不太够。</li>
<li>B树遍历整个树的过程和二叉树本质上是一样的，仍需要中序遍历，B树相对二叉树虽然提高了磁盘IO性能，但并没有解决遍历元素效率低下的问题。</li>
</ol>
</li>
</ul>
<h3><strong>B+树</strong></h3>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/12058546-2ae10c0ddc8ac9ea.png" alt="" /></p>
<ul>
<li>
<p><strong>优点：</strong>B+树相比B树，本质上是一样的，区别就在与B+树的所有根节点都不带有任何数据信息，只有索引信息，所有数据信息全部存储在叶子节点里，这样，整个树的每个节点所占的内存空间就变小了，读到内存中的索引信息就会更多一些，相当于减少了磁盘IO次数，且所有叶子节点都会在同一层，B+树会以一个链表的形式将所有叶子节点的信息全部串联起来，这样，遍历所有数据信息只需要顺序遍历叶子节点就可以了。不仅如此，B+树还有一个相应的优质特性，就是B+树的查询效率是非常稳定的，因为所有信息都存储在了叶子节点里面，从根节点到所有叶子节点的路径是相同的。</p>
</li>
<li>
<p><strong>缺点：</strong>B+树在非叶子节点上不存储数据，有些时候会相对B树有更多的磁盘IO</p>
</li>
</ul>
<h2>索引组织表</h2>
<p>相比于正常存储方式。优化了遍历搜索的性能损失。</p>
<h2>聚集索引</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/12058546-0da96cb9de1ff1c3.png" alt="" /></p>
<h2>辅助索引（非聚集索引）</h2>
<p><img decoding="async" src="https://img-1307890592.cos.ap-chengdu.myqcloud.com/typroa/12058546-8cb0dbfd433253b4.png" alt="" /></p>
<p>回表</p>
<p>通过辅助索引定位到聚集索引</p>
<h2>索引的优化</h2>
<h3>使用层面</h3>
<h4>索引列选择</h4>
<p>选择适当的索引，索引应有区分度。如果某个字段的取值范围很广，几乎没有重复，即属于<strong>高选择性</strong> 的字段适合作为索引</p>
<h4>最左前缀原则</h4>
<p>如果进行模糊查询，查找 name 的第一个字为”孙“开头的所有人的id，即SQL语句为</p>
<pre><code class="language-SQL">SELECT id FROM student WHERE name like &#039;孙%&#039;;</code></pre>
<p>由于在B+ 树结构的索引中，索引项时按照索引定义里面出现的字段顺序排序的，索引在查找的时候，可以快速定位到ID为 100 的 “孙a” ，然后直接向右遍历所有姓名为 “孙” 开头的人，直到条件不满足位置。也就是说，我们找到第一个满足条件的之后，直接向右遍历就可以了，由于索引是有序的，所有满足条件的人都会聚集到一起。</p>
<p>而这种定位到最左边，然后向右遍历寻找，就是我们所说的最左前缀原则。</p>
<h4>联合索引</h4>
<p>联合索引时指对表上的多个列进行索引</p>
<pre><code class="language-sql">ALTER TABLE buy_log ADD KEY(user_id);
ALTER TABLE buy_log ADD KEY(user_id, buy_date);
ALTER TABLE buy_log ADD KEY(user_id, buy_date, price);</code></pre>
<p><strong>情况1：</strong>如果只对于userid进行查询</p>
<pre><code class="language-SQL">SELECT * FROM buy_log WHERE user_id = 2;</code></pre>
<p>索引选择：优化器的最终选择是索引userid，因为该索引的叶子节点包含单个键值，所以理论上一个页能存放的记录应该更多。</p>
<p><strong>情况2：</strong>对于userid查询并根据buy_date排序，或 对于userid和buy_date查询并根据price排序</p>
<pre><code class="language-sql">SELECT * FROM buy_log WHERE user_id = 1 ORDER BY buy_date DESC LIMIT 3;
SELECT * FROM buy_log WHERE user_id = 1 AND buy_date = 2020 ORDER BY price DESC LIMIT 3;</code></pre>
<p>索引选择：优化器最终选择的是联合索引(user_id, buy_date)，因为在联合索引中buy_date已经排序好了。根据该联合索引去除数据，无须再对buy_date做一次额外的排序操作。</p>
<p><strong>情况三：</strong>对于userid查询并根据price排序</p>
<pre><code class="language-SQL">SELECT * FROM buy_log WHERE user_id = 1 ORDER BY price DESC LIMIT 3;</code></pre>
<p>此时联合索引不能直接得到结果，其还需要执行一次排序操作，因为索引（user_id，price）并未排序</p>
<h4>覆盖索引</h4>
<p>即从辅助索引中就可以得到查询的记录（此时不能够使用select * 操作，只能对特定的索引字段进行select），而不需要查询聚集索引的记录。使用覆盖索引的一个好处是辅助索引不包含整行记录的所有信息，故其大小要远小于聚集索引，因此可以<strong>减少大量的IO操作</strong>。</p>
<pre><code class="language-SQL">SELECT COUNT(*) FROM buy_log;</code></pre>
<p><strong>InnoDB存储引擎并不会选择通过查询聚集索引来进行统计。由于buy_log表上还有辅助索引，而辅助索引远小于聚集索引，而辅助索引远小于聚集索引，选择辅助索引可以减少IO操作。</strong></p>
<blockquote>
<p>用一句人话概括：直接从辅助索引拿数据</p>
</blockquote>
<h4>避免索引失效</h4>
<ol>
<li>
<p>Like 开头以%开头 导致失效</p>
<pre><code class="language-SQL">SELECT id FROM student WHERE name = ‘%三’;</code></pre>
</li>
<li>
<p>OR 前后有任意一项不是索引字段则失效</p>
<pre><code class="language-SQL">-- age 为索引字段
SELECT id FROM student WHERE age > 10 OR age < 20；-- 索引正常使用
SELECT id FROM student WHERE age > 10 OR score > 80；-- 索引失效
SELECT id FROM student WHERE score > 80 OR age > 10；-- 索引失效</code></pre>
</li>
<li>
<p>联合查询  </p>
<pre><code class="language-sql">-- 联合索引（age，score）id为主键
SELECT id FROM student WHERE age > 10 AND score > 80; -- 索引正常使用
SELECT id FROM student WHERE score > 80; -- 索引失效
-- 没有从联合索引的首元素开始进行索引，则索引失效</code></pre>
</li>
<li>
<p>索引字段进行算数运算 导致索引失效</p>
<pre><code class="language-sql">SELECT id FROM student WHERE age = 10; -- 索引正常使用
SELECT id FROM student WHERE age - 1 = 10; -- 索引失效</code></pre>
</li>
<li>
<p>NOT 取负面（取非）的结果集 导致索引失效</p>
<pre><code class="language-sql">SELECT id FROM student WHERE age != 10; -- 索引失效
SELECT id FROM student WHERE age <> 10; -- 索引失效
SELECT id FROM student WHERE age IS NOT 10; -- 索引失效</code></pre>
</li>
<li>
<p>NULL 可能 导致索引失效</p>
<pre><code class="language-SQL">-- 并不会百分百造成索引失效，
-- MySQL不会对NULL值创建索引，即NULL值在创建索引时会被抛弃
-- （所以逻辑上永远不会为空的字段应加上非空约束，如有逻辑上为空的情况，建议设置默认值约束）
SELECT id FROM student WHERE age IS NULL; -- 索引失效
SELECT id FROM student WHERE age IS NOT NULL; -- 索引失效</code></pre>
</li>
<li>
<p>方法函数 索引列使用内置函数时 可能 导致索引失效</p>
<pre><code class="language-sql">--
SELECT birth_date FROM student WHERE DATE_ADD(birth_date, -1) = CURRENT_DATE(); -- 索引失效
SELECT birth_date FROM student WHERE birth_date = CURRENT_DATE() + 1; -- 正常使用</code></pre>
</li>
<li>
<p>类型转换 导致索引失效</p>
<pre><code class="language-sql">-- phone varchar(11)
-- MySQL中的内置函数默认自动把字段值数字类型转换为字符，以匹配phone的类型
-- 因为字符串转整型会出现很多种情况，如"111" " 111" "111a"都会转换为整型的 111，故此时不使用索引
SELECT phone FROM student WHERE phone = 12345678901; -- 索引失效
SELECT phone FROM student WHERE phone = '12345678901'; -- 索引正常使用
-- 注：当隐式转换时 整型 转 字符串 则不受影响，因为整型转字符串的结果是唯一的</code></pre>
</li>
<li>
<p>同一语句在某些版本 可能 导致索引失效</p>
<pre><code class="language-SQL">-- age 为索引字段
SELECT * FROM student age > 3;</code></pre>
<p>是否使用索引？</p>
<p>首先，age是辅助索引，根据age查找到的是主键，仍需回表去查询聚簇索引来获得整行的数据。所以有两种可能。</p>
<ol>
<li>因为age已经排好序，通过索引查找 age &gt; 3 效率很高。所以有使用age索引的必要。</li>
<li>因为有回表的过程，排好序对应的主键id未必是排好序的，仍需多次额外的io查找，不如直接遍历聚簇索引</li>
</ol>
<p>两种理由都有道理。MySQL在 5.6版本 前后有不同的选择。</p>
<ul>
<li>
<p>5.6版本 之前，无论是否有age的辅助索引，都要走全表扫描，即遍历聚簇索引，不会使用辅助索引。</p>
<p>如果直接回表的话会有3次IO多次查询，如果有n条数据需要回表，即额外需要 n* 3 = 3n 次IO，代价较大（这其实就是离散读的概念），</p>
<p>如果直接全表扫描的话就是聚簇索引3次IO定位到叶子节点，然后根据叶子节点的链表遍历，可能也有多次IO，也并非轻松</p>
<p>所以MySQL设置了一个阈值，当需要查询的数据占到总数据的一定量的时候就会全表扫描，没有到达阈值的时候就根据辅助索引回表多次查询。所以可能失效也可能不失效。</p>
</li>
<li>
<p>5.6版本 之后，引入了 <code>Multi-Range Read (MRR)</code>优化，专门为解决离散读的问题。</p>
<p>执行上述查询语句的时候会进行3次IO使用辅助索引找到所有 age &gt; 3 的主键id，然后将这些数据放在<strong>缓存</strong>中，并将这些id进行了<strong>排序</strong>（在InnoDB引擎层面进行这些操作）。然后根据这些排序的主键id进行查询，省略了多次回表的过程。在支持MRR优化后，针对离散读的场景，能够优化10倍以上的效率。</p>
</li>
</ul>
</li>
</ol>
<h3>存储引擎层面</h3>
<h4>MRR优化（针对离散读）</h4>
<p><strong>离散读</strong>：</p>
<p>假设表：t_index。其中 id 为主键；c1 与 c2 组成了联合索引（c1，c2）；此外 c1 还是一个 <strong>单独索引</strong>。</p>
<p>​   进行如下查找操作：</p>
<pre><code class="language-sql">SELECT * FROM t_index WHERE c1 &gt; 1 AND c1 &lt; 100000;</code></pre>
<p>在最后的索引使用中，优化器选择了 PRIMARY id 聚集索引，也就是表扫描（table scan），而非 c1 辅助索引扫描（index scan）。</p>
<p>因为如果强制使用c1索引，就会造成<strong>离散读</strong>。具体原因在于用户要选取的数据是整行信息，而c1作为辅助索引不能覆盖到我们要查询的信息，因此在对c1索引查询到指定数据后，还需要一次书签访问来查找整行数据的信息。虽然c1索引中数据是顺序存放的，但是再进行聚簇索引查找的数据是无序的，因此变味了磁盘上的<strong>离散读</strong>操作。如果要求访问的数据量很小，则优化器还会选择辅助索引，但是当访问的数据占整个表中数据的蛮大一部分时（一般是20%左右），优化器会选择通过聚簇索引来查找数据。</p>
<p><strong>MRR</strong>：</p>
<p>在MySQL 5.6之后开始支持Multi-Range Read（MRR）优化。MRR 优化的目的就是为了减少磁盘的随机访问，并且将随机访问转化为较为顺序的数据访问，这对于IO-bound类型的SQL查询语句可带来性能极大的提升。<strong>MRR可适用于eq_ref、ref、range级别的索引</strong></p>
<blockquote>
<p>索引的级别：system &gt; const &gt; <strong>eq_ref</strong> &gt; <strong>ref</strong> &gt; <strong>range</strong> &gt; index &gt; all</p>
</blockquote>
<p>MRR优化的执行过程：</p>
<ol>
<li>将查询得到的辅助索引键值存放于一个缓存中，这时缓存中的数据时根据辅助索引键值排序的。</li>
<li>将缓存中的键值根据RowID进行排序</li>
<li>根据RowID的排序顺序来访问实际的数据文件</li>
</ol>
<p>MRR 还可以将某些范围查询拆分为键值对，以此来进行批量的数据查询。这样的好处是可以在拆分过程中，直接过过滤一些不符合条件的数据</p>
<pre><code class="language-sql">SELECT * FROM student WHERE id &gt;= 1000 AND id &lt; 2000 AND age = 1000;</code></pre>
<p>表 student 有 （id, age）的联合索引，因此索引根据id、age的位置关系进行排序。若没有MRR，此时查询类型为Range，SQL优化器会先将id大于1000且小于2000的数据都去除，即使age不等于1000。待取出行数据后再根据age条件进行过滤。这会导致无用的数据被取出。如果有大量的数据且其age不等于1000，则启用MRR优化会使性能有巨大的提升。</p>
<p>若启用MRR优化，优化器会先将查询条件进行拆分，然后再进行数据查询。久上述查询语句而言。优化器会将查询条件拆分为 (1000, 1000), (1001, 1000), (1002, 1000) , ..., (1999, 1000)，最后再根据这些拆分出的条件进行数据的查询。</p>
<p><strong>优化策略：在非必要的情况，拒绝使用 select <em> ，在必须 select </em> 的情况下，尽量使用MySQL 5.6 以后的版本并开启MRR</strong></p>
<h4>ICP优化</h4>
<p>和MRR优化一样，Index Condition Pushdown (ICP) 同样是MySQL 5.6开始支持的一种根据索引进行查询的优化方式。在之前的版本中，当进行索引查询时，首先根据索引来查找记录，然后再根据WHERE条件来过滤记录。在支持ICP后。MySQL数据库会在取出索引的同时，判断是否可以进行WHERE条件的过滤，也就是将WHERE的部分过滤操作放在了存储引擎层。在某些查询下，可以大大减少上层SQL层对记录的索取（fetch），从而提高数据库的整体性能</p>
<h4>FIC（快速索引创建）优化</h4>
<p>在5.5版本之前，MySQL数据库对于索引的添加或者删除的DDL操作，MySQL数据库的操作过程为以下几步：</p>
<ol>
<li>首先<strong>创建</strong>一张新的<strong>临时表</strong>，表结构为通过命令ALTER TABLE新定义的结构</li>
<li>然后把原表中的<strong>数据导入到临时表</strong></li>
<li>接着<strong>删除原表</strong></li>
<li>最后把临时表<strong>重命名</strong>为原来的表名</li>
</ol>
<p>如果对于一张大表进行索引的添加和删除操作，那么会需要很长的时间。更关键的是若有大量事务需要访问正在被修改的表，这意味着数据库服务不可用。</p>
<p>从InnoDB 1.0.x 版本开始支持一宗称为 FIC （Fast Index Creation）的索引创建方式</p>
<p>对于辅助索引的创建，InnoDB存储引擎会对创建索引的表加上一个 <strong>S锁</strong> 。在创建的过程中，不需要重建表，因此速度较之前提高很多，并且数据库的可用性也得到了提高。删除辅助索引操作就更难了，InnoDB存储引擎只需要更新内部视图，并将辅助索引的空间标记为可用（不影响辅助索引的使用，因为可读），<strong>同时删除</strong>MySQL数据库内部视图上对该表的索引定义即可</p>
<p>但是，由于FIC在索引的创建过程中对表加上了S锁，因此在创建的过程中只能对该表进行读操作，若有大量的事务需要对目标表进行写操作，那么数据库的服务同样不可用。此外，FIC反射光hi只限定于辅助索引，对于主键的创建和删除通用需要重建一张表</p>
<h4>Online DDL（在线数据定义）</h4>
<p>在MySQL5.6版本开始支持Online DDL操作，其允许辅助索引创建的同时，还允许其他诸如INSERT、UPDATE、DELETE这类DML操作，极大地提高了MySQL数据库在生产环境中的可用性</p>
<p>不仅是辅助索引，以下几类DDL操作东可以通过“在线”的方式进行操作：</p>
<ul>
<li><strong>辅助索引的创建与删除</strong></li>
<li><strong>改变子增值</strong></li>
<li><strong>添加或删除外键约束</strong></li>
<li><strong>列的重命名</strong></li>
</ul>
<p>使用语法：</p>
<pre><code class="language-sql">ALTER TABLE tbl_name
| ADD{INDEX | KEY} [index_name]
[index_type] (index_col_name,...) [index_option]...
ALGORITHM [=] {DEFAULT | INPLACE | COPY}
LOCK [=] {DEFAULT | NONE |SHARED | EXCLUSIVE}</code></pre>
<p><strong>ALGORITHM</strong> 制定了创建或删除索引的算法，<strong>COPY</strong> 表示按照MySQL 5.1版本之前的工作方式，即创建临时表的方式。<strong>INPLACE</strong> 表示索引创建或删除操作不需要创建临时表。 <strong>DEFAULT</strong> 表示根据参数 old_alter_table 来判断是通过 INPLACE 还是 COPY的算法，该参数的默认值为OFF，表示采用个INPLACE的方式</p>
<p><strong>LOCK</strong> 部分索引创建或删除时对表添加锁的情况：</p>
<ol>
<li>
<p><strong>NONE</strong></p>
<p>执行索引创建或者删除操作时，对目标表不添加任何的锁，即事务仍然可以进行读写操作，不会受到阻塞。因此这种模式可以获得最大的并发度。</p>
</li>
<li>
<p><strong>SHARE</strong></p>
<p>和之前的FIC类似，执行索引创建或删除操作时，对目标表加上一个<strong>S锁</strong>，对于并发地读事务，依然可以执行，但是遇到写事务，就会发生等待操作。</p>
</li>
<li>
<p><strong>EXCULSIVE</strong></p>
<p>在EXCULSIVE模式下，执行索引创建或删除操作时，对目标表加上一个<strong>X锁</strong>。读写事务都不能进行，因此会阻塞所有的线程，这和COPY方式运行得到的状态类似，但是不需要像COPY方式那也创建一张临时表。</p>
</li>
<li>
<p><strong>DEFAULT</strong></p>
<p>DEFAULT模式会先判断当前操作是否可以使用NONE模式，若不能，则判断是否可以使用SHARE模式，最后判断是否可以使用EXCLUSIVE模式，也就是说DEFAULT会通过判断事务的最大并发性来判断执行DDL的模式。</p>
</li>
</ol>
<p><strong>InnoDB存储引擎实现Online DDL 的原理是在执行创建或者删除操作的同时，将INSERT、UPDATE、DELETE这类DML操作日志写入到一个缓存中，待完成索引创建后再将重做应用到表上，以此达到数据的一致性。</strong></p>
<p><strong>由于Online DDL 在创建索引完成后再通过重做日志达到数据库的最终一致性，这意味着再索引创建过程中，SQL优化器不会选择正在创建中的索引。</strong></p>
<h2>MySQL表设计与索引调优</h2>
<h3>表的设计阶段</h3>
<p>单行数据量的要求，MySQL底层的内存页的大小为16KB，所以一条数据如果是16KB，则一个内存页只能存储一条数据，这是不可接受的，如果1条数据为1KB则能存储16条数据。之所以提到内存页，因为MySQL的每次IO就是读取一个内存页的数据，所以要保证单行数据量的值要尽量的小。为此，在初期需求分析的时候就要做到数据尽量的小，比如存放一个UUID，如果要存放32位的UUID，则直接定死为32位，不要浪费存储空间，否则当数据达到一定规模的时候会影响到B+树的整体结构，B+树拥有高扇出性，每一个节点对应着16KB的内存页，对于3层高的B+树，根节点存放着16KB的主键与指针，假设主键为bigint8个字节，指针固定为6个字节，所有对应着有16KB / 14B 约等于 1170 个指针，每个指针指向一个节点，第二层的每个节点的结构与根节点均相似，所以第二层则总共扇出1170 <em> 1170 个页子节点，若每条数据长1KB，则一个三层高的B+树能够存储 1170 </em> 1170 * 16 条数据，约等于两千万多条，如果一个数据大小为16KB则存储容量就大打折扣了。所以在设计阶段就要保证数据大小。在做设计的时候也应该或用枚举，比如性别男女分别用0和1来表示，一可以保证检索快速二可以控制数据量的大小，就能增加相同高度的B+树的数据容纳量。</p>
<h3>数据操作时索引的设计</h3>
<p>MySQL中的三种索引，聚簇索引、辅助索引、覆盖索引。</p>
<p>聚簇索引，每张表只有一个，即 PRIMARY KEY；辅助索引，是开发时额外增加的索引，每个辅助索引对应着一颗B+树；覆盖索引，本质是没有树的。在SQL的设计的时候首先要确保一点，不要使用SELECT <em>，它必须要走聚集索引，也就是要全表扫描，只有在聚集索引中才有全部的数据。如果要使用的话尽量使用5.6以后的版本，因为5.6以后有了针对离散读的MRR优化和ICP优化，这样 SELECT </em> 的查询速度会更快。着重提一下覆盖索引，在对于索引进行设计的时候，如果需要查询n个字段，如果保证这个n个字段都可以作为索引的话就尽量设置索引，因为在辅助索引中储存索引的值和主键的值，就可以避免回表查询。在书写SQL的时候，多使用执行计划查看索引是否失效，因为在特定的场景和函数下，MySQL的索引可能会失效</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/04/17/mysql%e7%b4%a2%e5%bc%95%e5%8f%8a%e5%85%b6%e4%bc%98%e5%8c%96%e6%80%bb%e7%bb%93-cfc%e4%be%8b%e4%bc%9a2022-4-17/">MySQL索引及其优化总结 &#8211; CFC例会2022.4.17</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>从零开始学习MySQL(七) – 浅谈MySQL锁机制</title>
		<link>https://www.crazyfay.com/2021/10/30/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%83-%e6%b5%85%e8%b0%88mysql%e9%94%81%e6%9c%ba%e5%88%b6/</link>
					<comments>https://www.crazyfay.com/2021/10/30/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%83-%e6%b5%85%e8%b0%88mysql%e9%94%81%e6%9c%ba%e5%88%b6/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sat, 30 Oct 2021 09:23:25 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=189</guid>

					<description><![CDATA[<p>第七章 浅谈 MySQL 锁机制 * 在MySQL中，就很容易出现多线程同时操作表中数据的情况，如果要避免潜在 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/10/30/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%83-%e6%b5%85%e8%b0%88mysql%e9%94%81%e6%9c%ba%e5%88%b6/">从零开始学习MySQL(七) – 浅谈MySQL锁机制</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>第七章 浅谈 MySQL 锁机制 *</h1>
<p>在MySQL中，就很容易出现多线程同时操作表中数据的情况，如果要避免潜在的并发问题，那么我们可以使用之前讲解的事务隔离级别来处理，而事务隔离中利用了锁机制。</p>
<ul>
<li><strong>读未提交(Read Uncommitted)</strong>：能够读取到其他事务中未提交的内容，存在脏读问题。</li>
<li><strong>读已提交(Read Committed RC)</strong>：只能读取其他事务已经提交的内容，存在不可重复读问题。</li>
<li><strong>可重复读(Repeated Read RR)</strong>：在读取某行后不允许其他事务操作此行，直到事务结束，但是依然存在幻读问题。</li>
<li><strong>串行读(Serializable)</strong>：一个事务的开始必须等待另一个事务的完成。</li>
</ul>
<p>我们可以切换隔离级别分别演示一下：</p>
<pre><code class="language-sql">set session transaction isolation level read uncommitted;</code></pre>
<p>在RR级别下，MySQL在一定程度上解决了幻读问题：</p>
<ul>
<li>在快照读（不加锁）读情况下，MySQL 通过 MVCC 来避免幻读。</li>
<li>在当前读（加锁）读情况下，MySQL 通过next-key来避免幻读。</li>
</ul>
<blockquote>
<p><strong><code>MVCC</code></strong>，全称 <code>Multi-Version Concurrency Control</code> ，即多版本并发控制。MVCC 是一种并发控制的方法，一般在数据库管理系统中，实现对数据库的并发访问，在编程语言中实现事务内存。</p>
</blockquote>
<h2>第一节 读锁和写锁</h2>
<p>从对数据的操作类型上来说，锁分为读锁和写锁：</p>
<ul>
<li><strong>读锁：</strong>也叫共享锁，当一个事务添加了读锁后，其他的事务也可以添加读锁或是读取数据，但是不能进行写操作，只能等到所有的读锁全部释放。</li>
<li><strong>写锁：</strong>也叫排他锁，当一个事务添加了写锁后，其他事务不能读不能写也不能添加任何锁，只能等待当前事务释放锁。</li>
</ul>
<h2>第二节 全局锁、表锁和行锁</h2>
<p>从锁的作用范围上划分，分为全局锁、表锁和行锁：</p>
<ul>
<li><strong>全局锁：</strong>锁作用于全局，整个数据库的所有操作全部受到锁限制。</li>
<li><strong>表锁：</strong>锁作用于整个表，所有对表的操作都会收到锁限制。</li>
<li><strong>行锁：</strong>锁作用于表中的某一行，只会通过锁限制对某一行的操作（仅InnoDB支持）</li>
</ul>
<h4>1. 全局锁</h4>
<p>我们首先来看全局锁，它作用于整个数据库，我们可以使用以下命令来开启读全局锁：</p>
<pre><code class="language-sql">FLUSH TABLES WITH READ LOCK;</code></pre>
<p>开启后，整个数据库被上读锁，我们只能去读取数据，但是不允许进行写操作（包括更新、插入、删除等）一旦执行写操作，会被阻塞，直到锁被释放，我们可以使用以下命令来解锁：</p>
<pre><code class="language-sql">UNLOCK TABLES;</code></pre>
<p>除了手动释放锁之外，当我们的会话结束后，锁也会被自动释放。</p>
<h4>2. 表锁</h4>
<p>表锁作用于某一张表，也是MyISAM和InnoDB存储引擎支持的方式，我们可以使用以下命令来为表添加锁：</p>
<pre><code class="language-sql">LOCK TABLE 表名称 READ/WRITE;</code></pre>
<p>在我们为表添加写锁后，我们发现其他地方是无法访问此表的，一律都被阻塞。</p>
<h4>3. 行锁</h4>
<p>表锁的作用范围太广了，如果我们仅仅只是对某一行进行操作，那么大可不必对整个表进行加锁，因此<code>InnoDB</code>支持了行锁，我们可以使用以下命令来对某一行进行加锁：</p>
<pre><code class="language-sql">-- 添加读锁（共享锁）
SELECT * FROM ... LOCK IN SHARE MODE;
-- 添加写锁（排他锁）
SELECT * FROM ... FOR UPDATE;</code></pre>
<p>使用InnoDB的情况下，在执行更新、删除、插入操作时，数据库也会自动为所<strong>涉及的行</strong>添加写锁（排他锁），直到事务提交时，才会释放锁，执行普通的查询操作时，不会添加任何锁。使用MyISAM的情况下，在执行更新、删除、插入操作时，数据库会对<strong>涉及的表</strong>添加写锁，在执行查询操作时，数据库会对涉及的表添加读锁。</p>
<p><strong>提问：</strong>当我们不使用索引列进行选择，行锁会发生什么变化？（行锁升级）</p>
<p>如果没有走索引，引擎就无法得知SELECT语句到查询的是哪一行不，行锁就会升级为全表锁（本质为记录锁）。</p>
<h2>第三节 记录锁、间隙锁和临键锁</h2>
<p>我们知道InnoDB支持使用行锁，但是行锁比较复杂，它可以继续分为多个类型。</p>
<h4>1. 记录锁</h4>
<p>（Record Locks）记录锁, 仅仅锁住索引记录的一行，在单条索引记录上加锁。Record lock锁住的永远是索引，而非记录本身，即使该表上没有任何索引，那么 InnodDB 会在后台创建一个隐藏的聚集主键索引，那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时，那么将会在每一条聚合索引后面加写锁，这个类似于表锁，但原理上和表锁应该是完全不同的。</p>
<h4>2. 间隙锁</h4>
<p>（Gap Locks）仅仅锁住一个索引区间（开区间，不包括双端端点）。在索引记录之间的间隙中加锁，或者是在某一条索引记录之前或者之后加锁，并不包括该索引记录本身。比如在 1、2中，间隙锁的可能值有 (-∞, 1)，(1, 2)，(2, +∞)，间隙锁可用于防止幻读，保证索引间的不会被插入数据。</p>
<h4>3. 临键锁</h4>
<p>（Next-Key Locks）Record lock + Gap lock，左开右闭区间。默认情况下，<code>InnoDB</code>正是使用Next-key Locks来锁定记录（如select … for update语句）它还会根据场景进行灵活变换：</p>
<table>
<thead>
<tr>
<th style="text-align: left;">场景</th>
<th>转换</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">使用唯一索引进行精确匹配，但表中不存在记录</td>
<td>自动转换为 Gap Locks</td>
</tr>
<tr>
<td style="text-align: left;">使用唯一索引进行精确匹配，且表中存在记录</td>
<td>自动转换为 Record Locks</td>
</tr>
<tr>
<td style="text-align: left;">使用非唯一索引进行精确匹配</td>
<td>不转换</td>
</tr>
<tr>
<td style="text-align: left;">使用唯一索引进行范围匹配</td>
<td>不转换，但是只锁上界，不锁下界</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注：关于更多临键锁机制可以阅读此文章 <a href="https://zhuanlan.zhihu.com/p/48269420">MySQL的锁机制 - 记录锁、间隙锁、临键锁 - 知乎 </a></p>
</blockquote>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/10/30/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%83-%e6%b5%85%e8%b0%88mysql%e9%94%81%e6%9c%ba%e5%88%b6/">从零开始学习MySQL(七) – 浅谈MySQL锁机制</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/10/30/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%83-%e6%b5%85%e8%b0%88mysql%e9%94%81%e6%9c%ba%e5%88%b6/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>从零开始学习MySQL(五) – 存储过程、函数、触发器和视图</title>
		<link>https://www.crazyfay.com/2021/10/02/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%94-%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b%e3%80%81%e5%87%bd%e6%95%b0%e3%80%81%e8%a7%a6%e5%8f%91%e5%99%a8%e5%92%8c%e8%a7%86/</link>
					<comments>https://www.crazyfay.com/2021/10/02/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%94-%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b%e3%80%81%e5%87%bd%e6%95%b0%e3%80%81%e8%a7%a6%e5%8f%91%e5%99%a8%e5%92%8c%e8%a7%86/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Sat, 02 Oct 2021 02:40:02 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=184</guid>

					<description><![CDATA[<p>第五章 存储过程、函数、触发器和视图 第一节 变量 在 MySQL中，变量分为四种类型，即局部变量、用户变量、 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/10/02/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%94-%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b%e3%80%81%e5%87%bd%e6%95%b0%e3%80%81%e8%a7%a6%e5%8f%91%e5%99%a8%e5%92%8c%e8%a7%86/">从零开始学习MySQL(五) – 存储过程、函数、触发器和视图</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>第五章 存储过程、函数、触发器和视图</h1>
<h2>第一节 变量</h2>
<p>在 <code>MySQL</code>中，变量分为四种类型，即局部变量、用户变量、会话变量和全局变量。其中局部变量和用户变量在实际应用中使用较多，会话变量和全局变量使用较少，因此作为了解即可。</p>
<h3>1. 全局变量</h3>
<p><code>MySQL</code>全局变量会影响服务器整体操作，当服务启动时，它将所有全局变量初始化为默认值。要想更改全局变量，必须具有管理员权限。其作用域为服务器的整个生命周期。</p>
<pre><code class="language-sql">-- 显示所有的全局变量
SHOW GLOBAL VARIABLES;

-- 设置全局变量的值的两种方式
SET GLOBAL sql_warnings = ON;
SET @@GLOBAL.sql_warnings = OFF;

-- 查询全局变量的值的两种方式
SELECT @@GLOBAL.sql_warnings;
SHOW GLOBAL VARIABLES LIKE &#039;%sql_warnings%&#039;;</code></pre>
<h3>2. 会话变量</h3>
<p><code>MySQL</code>会话变量是服务器为每个连接的客户端维护的一系列变量。其作用域仅限于当前连接，因此，会话变量是独立的。</p>
<pre><code class="language-sql">-- 显示所有的会话变量
SHOW SESSION VARIABLES;

-- 设置会话变量的值的三种方式
SET SESSION auto_increment_increment = 1;
SET @@SESSION.auto_increment_increment = 2;

-- 当省略SESSION关键字时，默认缺省为SESSION，即设置会话变量的值
SET auto_increment_increment = 3;

-- 查询会话变量的值的三种方式
SELECT @@auto_increment_increment;
SELECT @@SESSION.auto_increment_increment;
-- SESSION关键字可以省略，也可以用关键字LOCAL替代
SHOW SESSION VARIABLES LIKE &#039;%auto_increment_increment%&#039;;

SET @@LOCAL.auto_increment_increment = 1;
SELECT @@LOCAL.auto_increment_increment;</code></pre>
<h3>3. 用户变量</h3>
<p><code>MySQL</code>用户变量， <code>MySQL</code>中用户变量不用提前申明，在用的时候直接用“@变量名”使用就可以了。其作用域为当前连接。</p>
<pre><code class="language-sql">-- 第一种用法，使用SET时可以用“=”或“:=”两种赋值符号赋值
SET @age = 18;

-- 第二种用法，使用SELECT时必须用“:=”赋值符号赋值
SELECT @age := 19;
SELECT @age := age FROM stu WHERE `name` = &#039;枫阿雨&#039;;

-- 第三种用法，使用SELECT...INTO语句赋值
SELECT age INTO @age FROM stu WHERE `name` = &#039;枫阿雨&#039;;
SELECT @age;</code></pre>
<p><font color=blue>示例</font>：筛选所有id大于100的成绩信息，并引入用户变量作为递增序号<code>num</code></p>
<pre><code class="language-sql">SELECT
    (SELECT @index := @index + 1) num,
    a.*
FROM
    score a,
    (SELECT @index := 0) b
WHERE id &gt; 100;</code></pre>
<h3>4. 局部变量</h3>
<p><code>MySQL</code>局部变量，只能用在<code>BEGIN/END</code>语句块中，比如存储过程中的BEGIN/END语句块。</p>
<pre><code class="language-sql">-- 定义局部变量
DECLARE age INT(3) DEFAULT 0;
-- 为局部变量赋值
SET age = 10;
SELECT age := 10;
SELECT 10 INTO age;
SELECT age;</code></pre>
<h2>第二节 存储过程</h2>
<h3>1. 存储过程的概念</h3>
<p>在大型数据库系统中，存储过程是一组为了完成特定功能而存储在数据库中的 <code>SQL</code>语句集，一次编译后永久有效</p>
<h3>2. 为什么要使用存储过程</h3>
<ul>
<li><strong>运行速度快</strong>：<br />
在存储过程创建的时候，数据库已经对其进行了一次解析和优化。存储过程一旦执行，在内存中就会保留一份这个存储过程，下次再执行同样的存储过程时，可以从内存中直接调用，所以执行速度会比普通 <code>SQL</code>快。  </li>
<li><strong>减少网络传输：</strong><br />
存储过程直接就在数据库服务器上跑，所有的数据访问都在数据库服务器内部进行，不需要传输数据到其它服务器，所以会减少一定的网络传输。 </li>
<li><strong>增强安全性</strong>：<br />
提高代码安全，防止 <code>SQL</code>被截获、篡改。</li>
</ul>
<h3>3. 如何使用存储过程</h3>
<p><font color=blue>语法</font></p>
<pre><code class="language-sql">-- 声明分隔符
[DELIMITER $$]
CREATE PROCEDURE 存储过程名称 ([IN | OUT | INOUT] 参数名1 数据类型, [[IN | OUT | INOUT] 参数名2 数据类型, ..., [IN | OUT | INOUT] 参数名n 数据类型]) 
-- 语句块开始
BEGIN
-- SQL语句集
END[$$]
-- 还原分隔符
[DELIMITER ; ]

-- 调用存储过程
CALL 存储过程名(参数1, 参数2, ...);</code></pre>
<p><font color=blue>语法</font>：使用存储过程完成银行业务转账</p>
<pre><code class="language-sql">-- 创建存储过程
DELIMITER //
CREATE PROCEDURE transferMoney(IN transferFROM BIGINT, IN transferTo BIGINT, IN money DOUBLE(20, 3))
BEGIN
    UPDATE account SET balance = balance - money WHERE account = transferFrom;
    UPDATE account SET balance = balance + money WHERE account = transferTo;
END //
DELIMITER ;

-- 调用存储过程
CALL transferMoney(123456, 123456, 2000);</code></pre>
<p>如果转账账户余额不足，上面的 <code>SQL</code>代码依然可以正常执行，只是执行完后，转账账户的余额变为了负数。这显然不符合常理。因此需要修正。</p>
<pre><code class="language-sql">-- 创建存储过程
DELIMITER //
CREATE PROCEDURE transferMoney(IN transferFROM BIGINT, IN transferTo BIGINT, IN money DOUBLE(20, 3))
BEGIN
    -- 定义变量表示执行结果：0-失败，1-成功
    DECLARE result TINYINT(1) DEFAULT 0;
    -- 转账账户必须保证余额大于等于转账金额
    UPDATE account SET balance = balance - money WHERE account = transferFrom AND balance &gt;= money;
    -- 检测受影响的行数是否为1，为1表示更新成功
    IF ROW_COUNT() = 1 THEN
        UPDATE account SET balance = balance + money WHERE account = transferTo;
        -- 目标账号余额增加
        IF ROW_COUNT() = 1 THEN
            -- 更新执行结果为1
            SET result = 1;
        END IF;
    END IF;
    -- 查询结果
    SELECT result;
END //
DELIMITER ;

-- 调用存储过程
CALL transferMoney(123456, 123456, 2000);</code></pre>
<p>如果转账账户已经将钱转出去，而在执行目标账户增加余额的时候出现了异常或者目标账户输入错误，此时应该怎么办呢？<br />
<code>MySQL</code>对数据的操作提供了事务的支持，用来保证数据的一致性,可以有效的解决此类问题。</p>
<h3>4. 事务</h3>
<h4>4.1 什么是事务</h4>
<p>事务( Transaction)是访问并可能操作各种数据项的一个数据库操作序列，这些操作要么全部执行,要么全部不执行，是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。</p>
<h4>4.2 事务的特性(ACID)</h4>
<ul>
<li><strong>原子性（Atomicity）</strong><br />
事务的各元素是不可分的（原子的）,它们是一个整体。要么都执行，要么都不执行。</li>
<li><strong>一致性（Consistency）</strong><br />
当事务完成时，必须保证所有数据保持一致状态。当转账操作完成时，所有账户的总金额应该保持不变，此时数据处于一致性状态；如果总金额发生了改变，说明数据处于非一致性状态。</li>
<li><strong>隔离性（Isolation）</strong><br />
对数据操作的多个并发事务彼此独立，互不影响。比如张三和李四同时都在进行转账操作，但彼此都不影响对方。</li>
<li><strong>持久性（Durability）</strong><br />
对于已提交事务，系统必须保证该事务对数据库的改变不被丢失，即使数据库出现故障</li>
</ul>
<h4>4.3 事务解决银行转账问题</h4>
<pre><code class="language-sql">-- 创建存储过程
DELIMITER //
CREATE PROCEDURE transferMoney(IN transferFROM BIGINT, IN transferTo BIGINT, IN money DOUBLE(20, 3))
BEGIN
    -- 定义变量表示执行结果：0-失败，1-成功
    DECLARE result TINYINT(1) DEFAULT 0;
    -- 声明SQLEXCEPTION处理器，当有SQLEXCEPTION发生时，错误标识符的值设为0
    -- 发生SQLEXCEPTION时的处理方式：CONTINUE，EXIT
    -- CONTINUE表示即使有异常发生，也会执行后面的语句
    -- EXIT表示，有异常发生时，直接退出当前存储过程
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET result = 0;
    -- 开启事务
    START TRANSACTION;
    UPDATE account SET balance = balance - money WHERE account = transferFrom AND balance &gt;= money;
    IF ROW_COUNT() = 1 THEN
        UPDATE account SET balance = balance + money WHERE account = transferTo;
        IF ROW_COUNT() = 1 THEN
            SET result = 1;
        END IF;
    END IF;
    -- 如果result的值为0，表示操作存在失败的情况，事务回滚，数据恢复到更改之前的状态
    IF result = 0 THEN ROLLBACK;
    -- 否则，表示所有操作都成功，提交事务
    ELSE COMMIT;
    END IF;
    -- 查询结果
    SELECT result;
END //
DELIMITER ;</code></pre>
<h4>5. 存储过程输出</h4>
<pre><code class="language-sql">-- 创建存储过程
DELIMITER //
CREATE PROCEDURE transferMoney(IN transferFROM BIGINT, IN transferTo BIGINT, IN money DOUBLE(20, 3), OUT TINYINT(1))
BEGIN
    -- 为SQL异常声明一个持续处理的处理器，一旦出现异常，则将result的值更改为0
    DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET result = 0;
    -- 开启事务
    START TRANSACTION;
    UPDATE account SET balance = balance - money WHERE account = transferFrom AND balance &gt;= money;
    -- 检测受影响的行数是否为1，为1表示更新成功
    IF ROW_COUNT() = 1 THEN
        UPDATE account SET balance = balance + money WHERE account = transferTo;
        -- 执行成功则设置result值为1
        IF ROW_COUNT() = 1 THEN
            -- 更新执行结果为1
            SET result = 1;
        ELSE SET result = 0;
        END IF;
    ELSE SET result = 0;
    END IF;
    -- 如果result的值为0，表示操作存在失败的情况，事务回滚，数据恢复到更改之前的状态
    IF result = 0 THEN ROLLBACK;
    -- 否则，表示所有操作都成功，提交事务
    ELSE COMMIT;
    END IF;
END //
DELIMITER ;

CALL transferMoney(123456, 123458, 2000 , @result)

SELECT @result;</code></pre>
<h2>第三节 自定义函数</h2>
<h3>1.自定义函数的概念</h3>
<p>函数就是在大型数据库系统中，一组为了完成特定功能而存储在数据库中的 <code>SQL </code>语句集，一次编译后永久有效</p>
<h3>2. 自定义函数</h3>
<p><code>MySQL</code>本身提供了一些内置函数，这些函数给我们日常的开发和数据操作带来了很大的便利，比如聚合函数<code>SUM()</code>、<code>AVG()</code>以及日期时间函数等。但这并不能完全满足开发的需要，有时我们需要一个函数来完成一些复杂功能的实现，而 <code>MySQL</code>中又没有这样的函数，因此，我们需要自定义函数来实现。</p>
<h3>3. 如何使用自定义函数</h3>
<pre><code class="language-sql">CREATE FUNCTION 函数名称 (参数名1 数据类型, 参数名2 数据类型, ..., 参数名n 数据类型]) RETURNS 数据类型
-- 函数特征：
-- DETERMINISTIC: 不确定的
-- NO SQL:没有SQL语句，当然也不会修改数据
-- READS SQL DATA: 只是读取数据，不会修改数据
-- MODIFIES SQL DATA:要修改数据
-- CONTAINS SQL：包含了SQL语句
DETERMINISTIC | NO SQL | READS SQL DATA | MODIFIES SQL DATA | CONTAINS SQL 
-- 语句块开始
BEGIN
    -- SQL语句
    RETURN 结果;
-- 语句块结束
END</code></pre>
<p><font color=blue>示例</font>：使用函数实现求score表中的成绩最大差值</p>
<pre><code class="language-sql">CREATE FUNCTION getMaxDiff()
RETURNS DOUBLE(5, 2)
DETERMINISTIC
BEGIN
    RETURN (SELECT MAX(score) - MIN(score) FROM score);
END

-- 调用函数
SELECT getMaxDiff();</code></pre>
<h3>4. 循环结构</h3>
<pre><code class="language-sql">WHILE 循环条件 DO
    -- SQL语句集
END WHILE:

REPEAT
    -- SQL语句集
UNTIL 循环终止条件 END REPEAT;

标号: LOOP
    -- SQL语句集
            IF 循环终止条件 THEN LEAVE 标号;
            END IF;
        END LOOP;</code></pre>
<p><font color=blue>示例</font>：使用函数实现求0~给定的任意整数的累加和</p>
<pre><code class="language-sql">-- 使用WHILE 循环实现
CREATE FUNCTION getTotal(maxNum INT(11))
RETURNS INT(11)
NO SQL
BEGIN
    DECLARE total INT(11) DEFAULT 0;
    DECLARE i INT(11) DEFAULT 0;
    WHILE i &lt;= maxNum DO
        SET total = total + i;
        SET i = i + 1;
    END WHILE;
    RETURN total;
END

-- 使用REPEAT UNTIL实现
CREATE FUNCTION getTotal1(maxNum INT(11))
RETURNS INT(11)
NO SQL
BEGIN
    DECLARE total INT(11) DEFAULT 0;
    DECLARE i INT(11) DEFAULT 0;
    REPEAT
        SET total = total + i;
        SET i = i + 1;
    UNTIL i &gt; maxNum END REPEAT;
    RETURN total;
END

-- 使用LOOP实现
CREATE FUNCTION getTotal2(maxNum INT(11))
RETURNS INT(11)
NO SQL
BEGIN
    DECLARE total INT(11) DEFAULT 0;
    DECLARE i INT(11) DEFAULT 0;
    a: LOOP
        SET total = total + i;
        SET i = i + 1;
        IF i &gt; maxNum THEN LEAVE a;
        END IF;
    END LOOP;
    RETURN total;
END

-- 调用函数
SELECT getTotal(100);
SELECT getTotal1(100);
SELECT getTotal2(100);</code></pre>
<p><font color=blue>练习</font>：使用函数实现生成一个指定长度的随机字符串</p>
<p><font color=blue>思路</font>：</p>
<ol>
<li>定义变量保存字符和数字组成字符串</li>
<li>定义变量保存生成的字符串</li>
<li>循环获取随机数，使用字符串截取的方式获得随机字符，并使用字符串拼接函数完成组装</li>
</ol>
<pre><code class="language-sql">CREATE FUNCTION rodomString(stringLength INT(11))
RETURNS VARCHAR(255);
NO SQL
BEGIN
    DECLARE str VARCHAR(64) DEFAULT &#039;ABCDEFGHIJKLMOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#039;;
    DECLARE result VARCHAR(255) DEFAULT NULL;
    DECLARE position INT(11);
    DECLARE i INT(11) DEFAULT 0;
    WHILE i &lt; stringLength DO
        SELECT ROUND(RAND() * 62) INTO position;
        SET result = CONCAT(result, SUBSTRING(str, position, 1));
        SET i = i + 1;
    END WHILE;
    RETURN result;
END</code></pre>
<h3>5. 函数和存储过程的区别</h3>
<blockquote>
<p>注：此部分内容选自 <a href="https://www.cnblogs.com/shanzzs/p/10677810.html">函数和存储过程的区别 - 随情 - 博客园</a></p>
</blockquote>
<ul>
<li><strong>函数和存储过程的优点：</strong></li>
</ul>
<p>　　1、共同使用的代码可以只需要被编写一次，而被需要该代码的任何应用程序调用（.net, C++, Java，也可以使<code>DLL</code>库）。</p>
<p>　　2、这种几种编写、几种维护更新、大家共享的方法，简化了应用程序的开发维护，提高了效率和性能。</p>
<p>　　3、这种模块化的方法使得一个复杂的问题、大的程序逐步简化成几个简单的、小的程序部分，进行分别编写，因此程序的结构更加清晰，简单，也容易实现。</p>
<p>　　4、可以在各个开发者之间提供处理数据、控制流程、提示信息等方面的一致性。</p>
<p>　　5、节省内存空间。它们以一种压缩的形式被存储在外存中，当被调用时才被放入内存进行处理。而且多个用户在调用同一个存储过程或函数时，只需要加载一次即可。</p>
<p>　　6、提高数据的安全性和完整性。通过把一些对数据的操作方到存储过程或函数中，就可以通过是否授予用户有执行该语句的权限，来限制某些用户对数据库进行这些操作。</p>
<ul>
<li><strong>函数和存储过程的区别</strong>：</li>
</ul>
<p>　　1、存储过程用户在数据库中完成特定操作或者任务（如插入，删除等），函数用于返回特定的数据。</p>
<p>　　2、存储过程声明用<code>PROCEDURE</code>，函数用<code>FUNCTION</code>。</p>
<p>　　3、存储过程不需要返回类型，函数必须要返回类型。</p>
<p>　　4、存储过程可作为独立的pl-sql执行，函数不能作为独立的plsql执行，必须作为表达式的一部分。</p>
<p>　　5、存储过程只能通过out和in/out来返回值，函数除了可以使用out，in/out以外，还可以使用return返回值。</p>
<p>　　6、SQL语句（<code>DML</code>或SELECT)中不可用调用存储过程，而函数可以。</p>
<ul>
<li><strong>适用场合</strong>：</li>
</ul>
<p>　　1、如果需要返回多个值和不返回值，就使用存储过程；如果只需要返回一个值，就使用函数。</p>
<p>　　2、存储过程一般用于执行一个指定的动作，函数一般用于计算和返回一个值。</p>
<p>　　3、可以再SQL内部调用函数来完成复杂的计算问题，但不能调用存储过程。</p>
<ul>
<li><strong>存储过程与存储函数的区别和联系</strong></li>
</ul>
<p>　　相同点：1.创建语法结构相似，都可以携带多个传入参数和传出参数。</p>
<p>　　　　        2.都是一次编译，多次执行。</p>
<p>　　不同点：1.存储过程定义关键字用<code>PROCEDURE</code>，函数定义用<code>FUNCTION</code>。</p>
<p>　　　　　　2.存储过程中<strong>不能用</strong><code>RETURN</code>返回值，但函数中可以，而且函数中<strong>必须有</strong><code>RETURN</code>子句。</p>
<p>　　　　　　3.执行方式略有不同，存储过程的执行方式有两种（1.使用execute 2.使用begin和end），函数除了存储过程的两种方式                        外，还可以当做表达式使用，例如放在select中（ SELECT f1() FROM dual; ）。</p>
<ul>
<li><strong>总结：如果只有一个返回值，用存储函数，否则，一般用存储过程。</strong></li>
</ul>
<h2>第四节 触发器</h2>
<h3>1. 触发器概念</h3>
<p>触发器（trigger）是用来保证数据完整性的一种方法，由事件来触发，比如当对一个表进行增删改操作时就会被激活执行。经常用于加强数据的完整性约束和业务规则</p>
<h3>2. 如何定义触发器</h3>
<pre><code class="language-sql">-- 删除触发器
DROP TRIGGER [IF EXISTS] 触发器名称;
-- 创建触发器
-- 触发时机为BEFORE或者AFTER
-- 触发事件，为INSERT 、 UPDATE或者DELETE 
CREATE TRIGGER 触发器名称 {BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名 FOR EACH ROW 
BEGIN
-- 执行的SQL操作
END</code></pre>
<h3>3. 触发器类型</h3>
<table>
<thead>
<tr>
<th>触发器类型</th>
<th>NEW 和 OLD 的使用</th>
</tr>
</thead>
<tbody>
<tr>
<td>INSERT 触发器</td>
<td><code>NEW</code> 表示将要或者已经新增的数据</td>
</tr>
<tr>
<td>UPDATE 触发器</td>
<td><code>OLD</code> 表示将要或者已经修改的数据，<code>NEW</code> 表示将要修改的数据</td>
</tr>
<tr>
<td>DELETE 触发器</td>
<td><code>OLD</code> 表示将要或者已经删除的数据</td>
</tr>
</tbody>
</table>
<h3>4. 触发器使用场景</h3>
<p><font color=blue>场景一</font>：现有商品表goods和订单表order，每一个订单的生成都意味着商品数量的减少，请使用触发器完成这一过程。</p>
<pre><code class="language-sql">-- 表数据关系
-- order 订单 id goods_id sales_id sale_count created_time state
-- goods 商品 id name number price agent_id

-- 创建触发器
CREATE TRIGGER addOrder AFTER INSERT ON `order` FOR EACH ROW
BEGIN
    UPDATE goods SET number = number - NEW.sale_count WHERE id=NEW.goods_id; END

-- 测试代码
INSERT INTO `order` (`goods_id`, `sales_id`, `sale_count`, `created_time`, `state`)
VALUES(1, 1, 6, &#039;2021-08-16&#039;, 1);</code></pre>
<p><font color=blue>场景二</font>：现有商品表goods和订单order，每一个订单的取消都意味着商品数量的增加，请使用触发器完成这一过程。</p>
<pre><code class="language-sql">-- 创建触发器
CREATE TRIGGER deleteOrder AFTER DELETE ON `order` FOR EACH ROW
BEGIN
    UPDATE goods SET number = number + OLD.sale_count WHERE id=OLD.goods_id;
END

-- 测试代码
DELETE FROM `order` WHERE id =350001;</code></pre>
<p><font color=blue>场景三</font>：现有商品表goods和订单表order，每一个订单购买数量的更新都意味着商品数量的变动，请使用触发器完成这一过程。</p>
<pre><code class="language-sql">-- 创建触发器
CREATE TRIGGER updateOrder AFTER UPDATE ON `order` FOR EACH ROW
BEGIN
    DECLARE changeNum INT(11) DEFAULT 0;
    SET changeNum = NEW.sale_count - OLD.sale_count;
    UPDATE goods SET number = number - changeNum WHERE id = old.goods_id;
END

-- 测试代码
UPDATE `order` SET sale_count = sale_count + 2 WHERE id=20;
UPDATE `order` SET sale_count = sale_count - 4 WHERE id=20;</code></pre>
<h2>第五节 视图</h2>
<h3>1. 视图的概念</h3>
<p>视图是一张虚拟表，本身并不存储数据，当 <code>SQL</code>操作视图时所有数据都是从其他表中查出来，运用了封装的思想，实质类似于 子查询并重命名</p>
<h3>2. 如何使用视图</h3>
<ul>
<li>
<p>创建视图</p>
<pre><code class="language-sql">CREATE VIEW 视图名称 AS SELECT 列1[, 列2, ...] FROM 表名 WHERE 条件;</code></pre>
</li>
<li>
<p>更新视图</p>
<pre><code class="language-sql">CREATE OR REPLACE VIEW 视图名称 AS SELECT 列1[, 列2, ...] FROM 表名 WHERE 条件; </code></pre>
</li>
<li>
<p>删除视图</p>
<pre><code class="language-sql">DROP VIEW IF EXISTS 视图名称;</code></pre>
</li>
</ul>
<h3>3. 为什么使用视图</h3>
<ul>
<li><strong>定制用户数据，聚焦特定的数据</strong></li>
</ul>
<p><font color=blue>示例</font>：如果频繁获取销售人员编号、姓名和代理商名称，可以创建视图</p>
<pre><code class="language-sql">CREATE OR REPLACE VIEW salesInfo AS
SELECT
    a.id,
    a.`name` saleName,
    b.`name` agentName
FROM
    sales a,
    agent b
WHERE
    a.agent_id = b.id;

-- 测试代码
SELECT id, saleName FROM salesInfo;</code></pre>
<ul>
<li><strong>简化数据操作</strong></li>
</ul>
<p><font color=blue>示例</font>：进行关联查询时，涉及到的表可能会很多，这时写的 <code>SQL</code>语句可能会很长，如果这个动作频繁发生的话，可以创建视图</p>
<pre><code class="language-sql">CREATE OR REPLACE VIEW searchOrderDetail AS
SELECT
    a.id regionId,
    a.`name` regionName,
    b.id agentId,
    b.`name` agentName,
    c.id saleId,
    c.`name` saleName,
    d.sale_count saleCount,
    d.created_time createdTime,
    e.`name` goodsName
FROM
    region a,
    agent b,
    sales c,
    `order` d,
    goods e
WHERE
    a.id = b.region_id
AND b.id = c.agent_id
AND c.id = d.sales_id
AND d.goods_id = e.id;

-- 测试代码
SELECT * FROM searchOrderDetail;</code></pre>
<ul>
<li><strong>提高安全性能</strong></li>
</ul>
<p><font color=blue>示例</font>：例如：用户密码属于隐私数据，用户不能直接查看密码。可以使用视图过滤掉这一字段</p>
<pre><code class="language-sql">CREATE OR REPLACE VIEW userInfo AS
SELECT
     username, 
     salt,
     failure_times,
     last_log_time
FROM
    `user`;

SELECT username, salt FROM userInfo;</code></pre>
<blockquote>
<p>注：<font color=red><strong>视图并不能提升查询速度，只是方便了业务开发，但同时也加大了数据库服务器的压力，因此，需要合理的使用视图</strong></font></p>
</blockquote>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/10/02/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%94-%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b%e3%80%81%e5%87%bd%e6%95%b0%e3%80%81%e8%a7%a6%e5%8f%91%e5%99%a8%e5%92%8c%e8%a7%86/">从零开始学习MySQL(五) – 存储过程、函数、触发器和视图</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/10/02/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%94-%e5%ad%98%e5%82%a8%e8%bf%87%e7%a8%8b%e3%80%81%e5%87%bd%e6%95%b0%e3%80%81%e8%a7%a6%e5%8f%91%e5%99%a8%e5%92%8c%e8%a7%86/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>从零开始学习MySQL(四) – 联表查询与索引</title>
		<link>https://www.crazyfay.com/2021/09/21/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%89-mysql%e5%b8%b8%e7%94%a8%e5%87%bd%e6%95%b0/</link>
					<comments>https://www.crazyfay.com/2021/09/21/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%89-mysql%e5%b8%b8%e7%94%a8%e5%87%bd%e6%95%b0/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Tue, 21 Sep 2021 15:38:05 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=181</guid>

					<description><![CDATA[<p>第四章 联表查询 第一节 表与表之间的关系 1. 表与表之间的关系 数据表是用来描述实体信息的，比如可以使用数 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/21/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%89-mysql%e5%b8%b8%e7%94%a8%e5%87%bd%e6%95%b0/">从零开始学习MySQL(四) – 联表查询与索引</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>第四章 联表查询</h1>
<h2>第一节 表与表之间的关系</h2>
<h3>1. 表与表之间的关系</h3>
<p>数据表是用来描述实体信息的，比如可以使用数据表来描述学生信息，也可以用数据表来描述班级信息，这样就会存在学生表和班级表。而学生和班级显然存在着一种关系：</p>
<p><img decoding="async" src="./img/表关系4-1.png" alt="" /></p>
<p>这种关系在数据库中体现就称之为表与表之间的关系。数据库通过<strong>主外键关联关系</strong>来体现表与表之间的关联关系</p>
<h3>2. 主外键关联关系</h3>
<p><img decoding="async" src="./img/表关系4-2.png" alt="" /></p>
<p>如图所示，此时学生表和班级表并没有任何关系，然而实际上学生和班级是存在归属关系。可以在学生表中添加一个字段，表明该学生所属班级，该字段值使用的是班级表中的主键，在学生表中称之为外键。这样学生表中的所属班级（外键）与班级表中的编号（主键）就产生关联关系，这种关联关系称为主外键关联关系。</p>
<p><img decoding="async" src="./img/表关系4-3.png" alt="" /></p>
<h3>3. 主外键关联关系的对应</h3>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS clss(
    id INT(20) AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT &#039;学号&#039;,
    name VARCHAR(30) NOT NULL COMMENT &#039;班级名称&#039;,
    grade VARCHAR(30) NOT NULL COMMENT &#039;年级&#039;,
)ENGINE=InnoDB CHARSET=UTF8 COMMENT &#039;班级表&#039;;

CREATE TABLE IF NOT EXISTS stu(
    number BIGINT(20) AUTO_INCREMENT NOT NULL COMMENT &#039;学号&#039;,
    name VARCHAR(30) NOT NULL COMMENT &#039;姓名&#039;,
    sex VARCHAR(2) NOT NULL DEFAULT &#039;男&#039; COMMENT &#039;性别&#039;,
    age TINYINT(3) UNSIGNED DEFAULT 0 COMMENT &#039;年龄&#039;,
    class_id INT(11) NOT NULL COMMENT &#039;所属班级&#039;,
    -- 指定number为主键
    PRIMARY KEY(number),
    -- 字段class_id与cls表中的number字段相关联
    FOREIGN KEY(class_id) REFERENCES class(id)
)ENGINE=InnoDB CHARSET=UTF8 COMMENT &#039;学生表&#039;;</code></pre>
<h3>4. 约束</h3>
<h4>4.1 主键约束</h4>
<ul>
<li>
<p>添加主键约束，保证数据的唯一性</p>
<pre><code class="language-sql">ALTER TABLE 表名 ADD PRIMARY KEY(字段名1,字段名2, ..., 字段名n);</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu ADD PRIMARY KEY(number);</code></pre>
</li>
<li>
<p>删除主键约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 DROP PRIMARY KEY;</code></pre>
<blockquote>
<p>注：若主键自增则无法直接删除主键约束</p>
</blockquote>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu DROP PRIMARY KEY;</code></pre>
</li>
</ul>
<h4>4.2 外键约束</h4>
<ul>
<li>
<p>添加外键约束</p>
<pre><code class="language-sql">ALTER TABLE1 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (表名1的字段名) REFERENCES 表名2(表名2的字段名);</code></pre>
<blockquote>
<p>注：  <code>MyISAM</code>不支持外键索引</p>
</blockquote>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu ADD CONSTRAINT fk_class_id FOREIGN KEY (class_id) REFERENCES class(id);</code></pre>
</li>
<li>
<p>删除外键约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 DROP FOREIGN KEY 外键名称;</code></pre>
<blockquote>
<p>注：因外键可以多个，故需要声明外键名称</p>
</blockquote>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu DROP FOREIGN KEY fk_class_id;</code></pre>
</li>
</ul>
<h4>4.3 唯一约束</h4>
<ul>
<li>
<p>为字段添加唯一约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 ADD CONSTRAINT 约束名称 UNIQUE(字段名1, 字段名2, ..., 字段名n);</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu ADD CONSTRAINT un_name UNIQUE(name);</code></pre>
</li>
<li>
<p>删除字段的唯一约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 DROP KEY 约束名称;</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">ALTER TABLE stu ADD CONSTRAINT un_name UNIQUE(name);</code></pre>
</li>
</ul>
<h4>4.4 非空约束</h4>
<ul>
<li>
<p>为字段添加非空约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 MODIFY 字段名 列类型 NOT NULL;</code></pre>
</li>
<li>
<p>删除字段的非空约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 MODIFY 字段名 列类型 NULL;</code></pre>
</li>
</ul>
<h4>4.5 默认值约束</h4>
<ul>
<li>
<p>为字段添加默认值</p>
<pre><code class="language-sql">ALTER TABLE 表名 ALTER 字段名 SET DEFAULT 默认值;</code></pre>
</li>
<li>
<p>删除字段的非空约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 ALTER 字段名 DROP DEFAULT;</code></pre>
</li>
</ul>
<h4>4.6 自增约束</h4>
<ul>
<li>
<p>为字段添加自增约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 MODIFY 字段名 列类型 AUTO_INCREMENT;</code></pre>
</li>
<li>
<p>删除字段的自增约束</p>
<pre><code class="language-sql">ALTER TABLE 表名 MODIFY 字段名 列类型;</code></pre>
</li>
</ul>
<h2>第二节 索引</h2>
<h3>1. 什么是索引</h3>
<p>在关系数据库中，索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构，它是表中一列或多列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。</p>
<p><img decoding="async" src="./img/索引4-4.png" alt="" /></p>
<p>对比书籍目录理解</p>
<h3>2. 索引的作用</h3>
<ul>
<li>保证数据的准确性</li>
<li>提高检索速度</li>
<li>提高系统性能</li>
</ul>
<h3>3. 索引的类型</h3>
<ul>
<li>唯一索引（<code>UNIQUE</code>）：不可以出现相同的值，可以有NULL值</li>
<li>普通索引（<code>INDEX</code>）：允许出现相同的索引内容</li>
<li>主键索引（<code>PRIMARY KEY</code>）：不允许出现相同的值</li>
<li>全文索引（<code>FULLTEXT INDEX</code>）：可以针对值中的某个单词，但效率确实不敢恭维</li>
<li>组合索引：实质上是将多个字段建到一个索引里，列值的组合必须唯一。注意组合索引在进行匹配时，遵循最左原则。</li>
</ul>
<h3>4. 索引的创建、查看、删除</h3>
<ul>
<li>
<p>创建索引</p>
<pre><code class="language-sql">ALTER TABLE 表名 ADD INDEX 索引名称 (字段名1, 字段名2, ..., 字段名n);</code></pre>
</li>
<li>
<p>创建全文索引</p>
<pre><code class="language-sql">ALTER TABLE 表名 ADD FULLTEXT 索引名称 (字段名1, 字段名2, ..., 字段名n);</code></pre>
<blockquote>
<p>注：5.56版本之前的 <code>InnoDB</code> 不支持全文索引</p>
</blockquote>
</li>
<li>
<p>查看索引</p>
<pre><code class="language-sql">SHOW INDEX FROM 表名;</code></pre>
</li>
<li>
<p>删除索引</p>
<pre><code class="language-sql">ALTER TABLE 表名 DROP INNDEX 索引名称;</code></pre>
</li>
<li>
<p>使用全文索引示例</p>
<pre><code class="language-sql">-- 创建表单
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (body));

-- 插入数据
INSERT INTO articles VALUES
(NULL,'MySQL Tutorial', 'DBMS stands for DataBase ...'),
(NULL,'How To Use MySQL Efficiently', 'After you went through a ...'),
(NULL,'Optimising MySQL','In this tutorial we will show ...'),
(NULL,'1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
(NULL,'MySQL vs. YourSQL', 'In the following database comparison ...'),
(NULL,'MySQL Security', 'When configured properly, MySQL ...');</code></pre>
<p>查询语句</p>
<pre><code class="language-sql">SELECT * FROM articles WHERE MATCH (body) AGAINST ('database');</code></pre>
<p>其查询结果等同于</p>
<pre><code class="language-sql">SELECT * FROM articles WHERE body like '%database%';</code></pre>
<blockquote>
<p>注：注意全文索引如何定义字段的，match中就必须是哪些字段，against中定义需要模糊匹配的字符串，用作查找的字符串实际上是被分词之后的结果，如果进行模糊匹配的不是一个词语，那么会查找失败，但是它的效率远高于下面的这种写法</p>
</blockquote>
</li>
<li>
<p>查看索引的执行情况</p>
<p>可以使用<code>EXPLAIN</code>语句（它可以用于分析select语句的执行计划，也就是MySQL到底是如何在执行某条select语句的）来分析查询语句到底有没有通过索引进行匹配。</p>
<pre><code class="language-sql">EXPLAIN SELECT * FROM student WHERE name = '枫阿雨';</code></pre>
<p>得到的结果如下：</p>
<ul>
<li>select_type：查询类型，上面的就是简单查询（SIMPLE）</li>
<li>table：查询的表</li>
<li>type：MySQL决定如何查找对应的记录，效率从高到低：system &gt; const &gt; eq_ref &gt; ref &gt; range &gt; index &gt; all</li>
<li>possible_keys：执行查询时可能会用到的索引</li>
<li>key：实际使用的索引</li>
<li>key_len：Mysql在索引里使用的字节数，字段的最大可能长度</li>
<li>rows：扫描的行数</li>
<li>extra：附加说明</li>
</ul>
</li>
</ul>
<h3>5. 使用索引的注意事项</h3>
<ul>
<li>虽然索引大大提高了查询速度，但也会降低更新表的速度，比如对表进行<code>INSERT</code>、<code>UPDATE</code>和<code>DELETE</code>操作，此时，数据库不仅要保存数据，还要保存一下索引文件</li>
<li>建立索引会占用磁盘空间的索引文件。如果索引创建过多（尤其是在字段多、数据量大的表上创建索引），就会导致索引文件过大，这样反而会降低数据库性能。因此，索引要建立在经常进行查询操作的字段上</li>
<li>不要在列上进行运算（包括函数运算），这会忽略索引的使用</li>
<li>不建议使用<code>LIKE</code>操作，如果非使用不可，注意正确的使用方式。<code>LIKE &#039;%查询内容%&#039; </code>不会使用索引，而<code>LIKE &#039;查询内容%&#039;</code>可以使用索引</li>
<li>避免使用<code>IS NULL</code>、<code>NOT IN</code>、<code>&lt;&gt;</code>、<code>!=</code>、<code>OR</code>操作，这些操作都会忽略索引而进行全表扫描</li>
</ul>
<h3>6. 索引底层原理 *</h3>
<p>既然我们要通过索引来快速查找内容，那么如何设计索引就是我们的重点内容，因为索引是存储在硬盘上的，跟我们之前使用的HashMap之类的不同，它们都是在内存中的，但是硬盘的读取速度远小于内存的速度，每一次IO操作都会耗费大量的时间，我们也不可能把整个磁盘上的索引全部导入内存，因此我们需要考虑尽可能多的减少IO次数，索引的实现可以依靠两种数据结构，一种是我们在JavaSE阶段已经学习过的Hash表，还有一种就是B-Tree。</p>
<p>我们首先来看看哈希表，实际上就是计算Hash值来快速定位：</p>
<p><img decoding="async" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%3A%2F%2Fdingyue.ws.126.net%2F2020%2F1223%2F2dd7c986j00qlrut10012c000rq00eam.jpg%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&amp;refer=http%3A%2F%2Fnimg.ws.126.net&amp;app=2002&amp;size=f9999,10000&amp;q=a80&amp;n=0&amp;g=0n&amp;fmt=jpeg?sec=1643975564&amp;t=8c9eabd82da9ac2637b0bdfea57d99bf" alt="点击查看源网页" /></p>
<p>通过对Key进行散列值计算，我们可以直接得到对应数据的存放位置，它的查询效率能够达到O(1)，但是它也存在一定的缺陷：</p>
<ul>
<li>Hash索引仅仅能满足“=”，“in”查询条件，不能使用范围查询。 </li>
<li>Hash碰撞问题。</li>
<li>不能用部分索引键来搜索，因为组合索引在计算哈希值的时候是一起计算的。</li>
</ul>
<p>那么，既然要解决这些问题，我们还有一种方案就是使用类似于二叉树那样的数据结构来存储索引，但是这样相比使用Hash索引，会牺牲一定的读取速度。</p>
<p>但是这里并没有使用二叉树，而是使用了B-Tree，它是专门为磁盘数据读取设计的一种度为n的查找树：</p>
<ul>
<li>树中每个结点最多含有m个孩子（m &gt;= 2）</li>
<li>除根结点和叶子结点外，其它每个结点至少有[ceil(m / 2)]个孩子。</li>
<li>若根结点不是叶子结点，则至少有2个孩子。</li>
<li>所有叶子结点都出现在同一层。</li>
<li>
<p>每个非终端结点中包含有n个键值信息： (P1，K1，P2，K2，P3，......，Kn，Pn+1)。其中： </p>
<ol>
<li>Ki (i=1...n)为键值，且键值按顺序升序排序K(i-1)&lt; Ki。  </li>
<li>Pi为指向子树根的结点，且指针P(i)指向的子树中所有结点的键值均小于Ki，但都大于K(i-1)。  </li>
<li>键值的个数n必须满足： [ceil(m / 2)-1] &lt;= n &lt;= m-1。</li>
</ol>
</li>
</ul>
<p><img decoding="async" src="https://upload-images.jianshu.io/upload_images/12058546-44a71668594a77d9.png?imageMogr2/auto-orient/strip|imageView2/2/w/654" alt="img" /></p>
<p>比如现在我们要对键值为<strong>10</strong>的记录进行查找，过程如下：</p>
<ol>
<li>读取根节点数据（目前进行了一次IO操作）</li>
<li>根据根节点数据进行判断得到10&lt;17，因为P1指向的子树中所有值都是小于17的，所以这时我们将P1指向的节点读取（目前进行了两次IO操作）</li>
<li>再次进行判断，得到8&lt;10&lt;12，因为P2指向的子树中所有的值都是小于12大于8的，所以这时读取P2指向的节点（目前进行了三次IO操作）</li>
<li>成功找到。</li>
</ol>
<p>我们接着来看，虽然B-Tree能够很好地利用二叉查找树的思想大幅度减少查找次数，但是它的查找效率还是很低</p>
<p>因此它的优化版本 <strong>B+Tree</strong> 诞生了，它拥有更稳定的查询效率和更低的IO读取次数：</p>
<p><img decoding="async" src="https://upload-images.jianshu.io/upload_images/12058546-2ae10c0ddc8ac9ea.png?imageMogr2/auto-orient/strip|imageView2/2/w/646" alt="img" /></p>
<p>我们可以发现，它和BTree有一定的区别：</p>
<ul>
<li>有n棵子树的结点中含有n个键值，B-Tree只有n-1个。</li>
<li>所有的键值信息只在叶子节点中包含，非叶子节点仅仅保存子节点的最小（或最大）值，和指向叶子节点的指针，这样相比B-Tree每一个节点在硬盘中存放了更少的内容（没有键值信息了）</li>
<li>所有叶子节点都有一个根据大小顺序指向下一个叶子节点的指针Q，本质上数据就是一个链表。</li>
</ul>
<p>这样，读取IO的时间相比BTree就减少了很多，并且查询任何键值信息都需要完整地走到叶子节点，保证了查询的IO读取次数一致。因此MySQL默认选择B+Tree作为索引的存储数据结构。</p>
<p>这是MyISAM存储引擎下的B+Tree实现：</p>
<p><img decoding="async" src="https://upload-images.jianshu.io/upload_images/12058546-316168444236022b.png?imageMogr2/auto-orient/strip|imageView2/2/w/664" alt="img" /></p>
<p>这是InnoDB存储引擎下的B+Tree实现：</p>
<p><img decoding="async" src="https://upload-images.jianshu.io/upload_images/12058546-0da96cb9de1ff1c3.png?imageMogr2/auto-orient/strip|imageView2/2/w/543" alt="img" /></p>
<p><img decoding="async" src="https://upload-images.jianshu.io/upload_images/12058546-8cb0dbfd433253b4.png?imageMogr2/auto-orient/strip|imageView2/2/w/543" alt="img" /></p>
<p>InnoDB与MyISAM实现的不同之处：</p>
<ul>
<li>数据本身就是索引的一部分（所以这里建议主键使用自增）</li>
<li>非主键索引的数据实际上存储的是对应记录的主键值（因此InnoDB必须有主键，若没有也会自动查找替代）</li>
</ul>
<blockquote>
<p>注：可阅读一下文章 <a href="https://zhuanlan.zhihu.com/p/98084061">MySQL InnoDB数据表缺少主键会怎样 - 知乎 </a></p>
</blockquote>
<h2>第三节 多表查询</h2>
<h3>1. 笛卡尔积</h3>
<p>笛卡尔积又称为笛卡尔乘积，由笛卡尔提出，表示两个集合相乘的结果。</p>
<p><img decoding="async" src="./img/笛卡尔积4-5.png" alt="" /></p>
<p>笛卡尔积与多表查询有什么关系呢？每一张表可以看做是一个数据的集合，多表关联串时，这些表中的数据就会形成笛卡尔积。</p>
<h3>2. 内连接</h3>
<p>内连接相当于在笛卡尔积的基础上加上了连接条件。当没有连接条件时，内连接上升为笛卡尔积。</p>
<pre><code class="language-sql">SELECT 字段名1, 字段名2, ..., 字段名n FROM 表1 [INNER] JOIN 表2 [ON 链接条件];
-- 等价于
SELECT 字段名1, 字段名2, ..., 字段名n FROM 表1, 表2 [WHERE 关联条件 AND 查询条件];</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">SELECT COUNT(*) FROM stu INNER JOIN score ON stu.id=score.stu_id;
-- 等价于
SELECT COUNT(*) FROM stu, score WHERE stu.id=score.stu.id;</code></pre>
<blockquote>
<p>注：上方写法效率更高，下方写法为<code>ORACLE</code>的写法，<code>MySQL</code>仍支持</p>
</blockquote>
<h3>3. 外连接</h3>
<p>外连接涉及到两张表：主表和从表，要查询的信息主要来自于哪张表，哪张表就是主表。<br />
<strong><font color=red>外连接查询的结果为主表中所有的记录。如果从表中有和它匹配的，则显示匹配的值，这部分相当于内连接查询出来的结果；如果从表中没有和它匹配的，则显示null。</font></strong><br />
<strong><font color=red>外连接查询的结果 = 内连接的结果 + 主表中有的而内连接结果中没有的记录</font></strong><br />
外连接分为左外连接和右外连接两种。左外连接使用<code>LEFT JOIN</code>关键字，<code>LEFT JOIN</code>左边的是主表；右外连接使用<code>RIGHT JOIN</code>关键字，<code>RIGHT JOIN</code>右边的是主表。</p>
<h4>3.1 左外连接</h4>
<pre><code class="language-sql">SELECT 字段名1, 字段名2, ..., 字段名n FROM 主表 LEFT JOIN 从表 [ON 链接条件]</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">SELECT * FROM stu a LEFT JOIN score b ON a.id=b.stu_id;</code></pre>
<h4>3.2 右外连接</h4>
<pre><code class="language-sql">SELECT 字段名1, 字段名2, ..., 字段名n FROM 从表 RIGHT JOIN 主表 [ON 连接条件];</code></pre>
<p><font color=blue>示例</font>：</p>
<pre><code class="language-sql">SELECT * FROM stu a RIGHT JOIN score b ON a.id=b.stu_id;</code></pre>
<h2>第四节 子查询</h2>
<h3>1. 什么是子查询</h3>
<p>子查询就是嵌套在其他查询中的查询。因此，子查询出现的位置只有3种情况：在<code>SELECT ... FROM</code> 之间、在<code>FROM ... WHERE</code>之间、在<code>WHERE</code>之后</p>
<blockquote>
<p>注：下面示例中<code>stu</code>表中有学生信息，<code>score</code>表中有成绩对应信息</p>
</blockquote>
<h3>2. SELECT ... FROM 之间</h3>
<p><font color=blue>示例</font>：查询<code>stu</code>表所有学生信息，并将性别按男、女、其他展示</p>
<pre><code class="language-sql">-- 先创建字典表
CREATE TABLE IF NOT EXISTS dict(
    id  INT AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT &#039;学生id&#039;,
    type VARCHAR(50) NOT NULL COMMENT &#039;类型&#039;,
    `value` VARCHAR(50) NOT NULL COMMENT &#039;原始值&#039;,
    `text` VARCHAR(50) NOT NULL COMMENT &#039;转换值&#039;,
)ENGINE=InnoDB CHARSET=UTF8 COMMENT &#039;字典表&#039;;

-- 向字典表插入字典转换内容
INSERT INTO dict (1, sex, 0, &#039;男&#039;), (2, sex, 1, &#039;女&#039;), (3, sex, 2, &#039;其他&#039;);

-- 执行查询
SELECT
    id,
    `name`,
    (SELECT text FROM dict WHERE tYPE=&#039;sex&#039; ANDE value=sex) sex,
    birthday,
    class
FROM
    stu;</code></pre>
<blockquote>
<p>注：执行时机是在查询结果出来之后</p>
</blockquote>
<h3>3. FROM ... WHERE 之间</h3>
<p><font color=blue>示例</font>：查询年龄与Java成绩都与枫阿雨的年龄与Java成绩都相同的学生信息</p>
<pre><code class="language-sql">SELECT c.*, d.* FROM stu c
INNER JOIN 
    score d
    ON c.id=d.stu_id
INNER JOIN
    (SELECT
        TIMESTAMPDIFF(YEAR, a.birthday, NOW()) age,
        b.score 
    FROM stu a INNER JOIN score b ON a.id=b.stu_id 
    WHERE a.name=&#039;枫阿雨&#039; AND b.score=&#039;Java&#039;) e
ON TIMESTAMPDIFF(YEAR, a.birthday, NOW())=e.age AND d.score=e.score
WHERE d.course=&#039;Java&#039;;</code></pre>
<blockquote>
<p>注：执行时机是一开始就执行</p>
</blockquote>
<h3>4. WHERE 之后</h3>
<p><font color=blue>示例</font>：查询Java成绩最高的所有学生信息</p>
<pre><code class="language-sql">SELECT a.*, b.* FROM stu a INNER JOIN score b ON a.id=b.stu_id 
WHERE b.score=(SELECT MAX(score) FROM score WHERE course=&#039;Java&#039;)
AND b.course=&#039;Java&#039;;</code></pre>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/21/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%89-mysql%e5%b8%b8%e7%94%a8%e5%87%bd%e6%95%b0/">从零开始学习MySQL(四) – 联表查询与索引</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/09/21/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%89-mysql%e5%b8%b8%e7%94%a8%e5%87%bd%e6%95%b0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>从零开始学习MySQL(二) – MySQL的增删改查</title>
		<link>https://www.crazyfay.com/2021/09/10/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%8c-mysql%e7%9a%84%e5%a2%9e%e5%88%a0%e6%94%b9%e6%9f%a5/</link>
					<comments>https://www.crazyfay.com/2021/09/10/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%8c-mysql%e7%9a%84%e5%a2%9e%e5%88%a0%e6%94%b9%e6%9f%a5/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Fri, 10 Sep 2021 05:37:18 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=179</guid>

					<description><![CDATA[<p>第二章 MySQL数据库的增删改查 第一节 DML语句 1. 什么是DML DML为Data Manipula [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/10/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%8c-mysql%e7%9a%84%e5%a2%9e%e5%88%a0%e6%94%b9%e6%9f%a5/">从零开始学习MySQL(二) – MySQL的增删改查</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>第二章 <code>MySQL</code>数据库的增删改查</h1>
<h2>第一节 <code>DML</code>语句</h2>
<h3>1. 什么是<code>DML</code></h3>
<p><code>DML</code>为Data Manipulation Language，表示数据操作语言。主要体现于对表数据的增删改操作。因此<code>DML</code>仅包括<code>INSERT</code>、<code>UPDATE</code>和<code>DELEETE</code>语句。</p>
<h3>2. INSERT语句</h3>
<pre><code class="language-sql">-- 需要注意，VALUES后的字段值必须与表名后的字段名一一对应
INSERT INTO 表名(字段名1, 字段名2, ..., 字段名n) VALUES(字段值1, 字段值2, ..., 字段值n);
-- 需要注意，VALUES后的字段值必须与创建表时的字段顺序保持一一对应
INSERT INTO 表名 VALUES(字段值1, 字段值2, ..., 字段值n);
-- 一次性插入多条数据
INSERT INTO 表名(字段名1, 字段名2, ..., 字段名n) VALUES(字段值1, 字段值2, ..., 字段值n),(字段值1, 字段值2, ..., 字段值n), ... , (字段值1, 字段值2, ..., 字段值n);
INSERT INTO 表名 VALUES(字段值1, 字段值2, ..., 字段值n), (字段值1, 字段值2, ..., 字段值n), ..., (字段值1, 字段值2, ..., 字段值n);</code></pre>
<p><font color=blue>示例</font>：向课程表中插入数据</p>
<pre><code class="language-sql">INSERT INTO course (`number`, name, score, `time`) VALUES (1, &#039;Java&#039;, 8, 80);
INSERT INTO course VALUE (2, &#039;Golang&#039;, 6, 60);
INSERT INTO course (`number`, score, name, `time`) VALUES (3, 5, &#039;Python&#039;, 50);
INSERT INTO course (`number`, name, score, `time`) VALUES (4, &#039;C/Cpp&#039;, 3, 30),(5, &#039;Spring&#039;, 4, 40);
INSERT INTO course VALUE (6, &#039;SpringMVC&#039;, 4 ,40),(7, &#039;SpringBoot&#039;, 5, 50);</code></pre>
<h3>3. UPDATE语句</h3>
<pre><code class="language-sql">UPDATE 表名 SET 字段名1=字段值1[,字段名2=字段值2, ..., 字段名n=字段值n] [WHERE 修改条件];</code></pre>
<h4>3.1 WHERE条件子句</h4>
<p>3.1 <code>WHERE</code>条件子句<br />
在<code>Java</code>中，条件的表示通常都是使用关系运算符来表示，在<code>SQL</code>语句中也是一样，使用 &gt;, &lt;, &gt;=, &lt;=, != 来表示。不同的是，除此之外，<code>SQL</code>中还可以使用<code>SQL</code>专用的关键字来表示条件。这些将在后面的<code>DQL</code>语句中详细讲解。<br />
在<code>Java</code>中，条件之间的衔接通常都是使用逻辑运算符来表示，在<code>SQL</code>语句中也是一样，但通常使用<code>AND</code>来表示逻辑与(<code>&amp;&amp;</code>)，使用<code>OR</code>来表示逻辑或(<code>||</code>)<br />
<font color=blue>示例</font>：</p>
<pre><code class="language-sql">WHERE time &gt; 20 AND time &lt; 60; 
-- 等价于
WHERE time &gt; 20 &amp;&amp; time &lt; 60;</code></pre>
<h4>3.2 UPDATE语句使用</h4>
<p>将<code>C/Cpp</code>的学分更改为2，学时更改为20</p>
<pre><code class="language-sql">UPDATE course SET score=2, `time`=20 WHERE name=&#039;C/Cpp&#039;;</code></pre>
<h3>4. DELETE语句</h3>
<pre><code class="language-sql">DELETE FROM 表名 [WHERE 删除条件];</code></pre>
<p><font color=blue>示例</font>：删除课程表中课程编号为1的数据</p>
<pre><code class="language-sql">DELETE FROM course WHERE &#039;number&#039;=1;</code></pre>
<h3>5. TRUNCATE语句</h3>
<pre><code class="language-sql">-- 清空表中的数据
TRUNCATE [TABLE] 表名;</code></pre>
<p><font color=blue>示例</font>：清空表course中的数据</p>
<pre><code class="language-sql">TRUNCATE course;</code></pre>
<h3>6. DELETE与TRUNCATE区别</h3>
<ul>
<li>DELETE语句根据条件删除表中数据，而TRUNCATE语句则是将表中数据全部清空；如果DELETE语句要删除表中所有数据，那么在效率上要低于TRUNCATE语句。</li>
<li>如果表中有自增长列，TRUNCATE语句会重置自增长的计数器，但DELETE语句不会。</li>
<li>TRUNCATE语句执行后，数据无法恢复，而DELETE语句执行后，可以使用事务回滚进行恢复。</li>
</ul>
<h2>第二节 <code>DQL</code>语句</h2>
<h3>1. 什么是<code>DQL</code></h3>
<p><code>DQL</code>全称是Data Query Language，表示数据查询语言。体现在数据的查询操作上，因此，<code>DQL</code>仅包括<code>SELECT</code>语句。</p>
<h3>2. SELECT语句</h3>
<pre><code class="language-sql">SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n] FROM 表名 WHERE 查询条件</code></pre>
<blockquote>
<p>注：<code>ALL</code>表示查询所有满足条件的记录，可以省略；<code>DISTINCT</code>表示去掉查询结果中重复的记录AS可以给数据列、数据表取一个别名</p>
</blockquote>
<p><font color=blue>示例</font>：从课程表中查询课程编号小于5的课程名称，从课程表中查询Java课程的学分(score)和学时(time)，从课程表中查询Java课程的学分和学时并重命名</p>
<pre><code class="language-sql">SELECT name FROM course WHERE `number` &lt; 5;
SELECT score, `time` FROM course name=&#039;Java&#039;;
SELECT score AS &#039;学分&#039;, `time` AS &#039;学时&#039; FROM course name=&#039;Java&#039;;
-- AS可以省略
SELECT score &#039;学分&#039;, `time` &#039;学时&#039; FROM course name=&#039;Python&#039;;
-- 给表起别名
SELECT c.name, c.score, c.time FROM course c WHERE c.name=&#039;Java&#039;;
-- 给表起别名的同时给字段重命名
SELECT c.name &#039;课程名称&#039;, c.score &#039;学分&#039;, c.time &#039;学时&#039; FROM course c WHERE c.name=&#039;Python&#039;;</code></pre>
<h3>3. 比较操作符</h3>
<table>
<thead>
<tr>
<th style="text-align: left;">操作符</th>
<th style="text-align: left;">语法</th>
<th style="text-align: left;">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">IS NULL</td>
<td style="text-align: left;">字段名 IS NULL</td>
<td style="text-align: left;">如果字段的值为NULL，则条件满足</td>
</tr>
<tr>
<td style="text-align: left;">IS NOT NULL</td>
<td style="text-align: left;">字段名 IS NOT NULL</td>
<td style="text-align: left;">如果字段的值不为NULL，则条件满足</td>
</tr>
<tr>
<td style="text-align: left;">BETWEEN ... AND ...</td>
<td style="text-align: left;">字段名 BETWEEN 最小值 AND 最大值</td>
<td style="text-align: left;">如果字段的值在最小值与最大值之间（能够取到最小值和最大值），则条件满足</td>
</tr>
<tr>
<td style="text-align: left;">LIKE</td>
<td style="text-align: left;">字段名 LIKE '%匹配内容%'</td>
<td style="text-align: left;">如果字段值包含有匹配内容，则条件满足</td>
</tr>
<tr>
<td style="text-align: left;">IN</td>
<td style="text-align: left;">字段名 IN(值1，值2，...， 值n)</td>
<td style="text-align: left;">如果字段值在值1,值2, ...，值n中，则条件满足</td>
</tr>
</tbody>
</table>
<p><font color=blue>示例</font>：从课程表查询课程名为NULL的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE name IS NULL;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程名不为NULL的课程信息</p>
<pre><code class="language-sql">SELECT * FROM coures WHERE name IS NOT NULL;</code></pre>
<p><font color=blue>示例</font>：从课程表查询学分在2~4之间的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE score BETWEEN 2 AND 4;
-- 等价于
SELECT * FROM course WHERE score &gt;= 2 ANDE score &lt;= 4;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程名包含&quot;V&quot;的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE name LIKE &#039;%V%&#039;;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程名以&quot;J&quot;开头的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE name LIKE &#039;J%&#039;;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程名以&quot;p&quot;结尾的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE name LIKE &#039;%p&#039;;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程名只有三个字符的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE name LIKE &#039;___&#039;;</code></pre>
<p><font color=blue>示例</font>：从课程表查询课程编号为1,3,5的课程信息</p>
<pre><code class="language-sql">SELECT * FROM course WHERE `number` IN (1, 3, 5);</code></pre>
<h3>4. 分组</h3>
<p>数据表准备：新建学生表student，包含字段学号（no），类型为长整数，长度为20，是主键，自增长，非空；姓名（name），类型为字符串，长度为20，非空；性别（sex），类型为字符串，长度为2，默认值为&quot;男&quot;；年龄（age），类型为整数，长度为3，默认值为0；成绩（score），类型为浮点数，长度为5，小数点后面保留2位有效数字</p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS student(
    `no` BIGINT(20) AUTO_INCREMENT NOT NULL PRIMARY KEY COMMENT &#039;学号&#039;,
    name VARCHAR(20) NOT NULL COMMENT &#039;姓名&#039;,
    sex VARCHAR(2) DEFAULT &#039;男&#039; COMMENT &#039;性别&#039;,
    age VARCHAR(3) INT(3) DEFAULT 0 COMMENT &#039;年龄&#039;,
    score DOUBLE(5, 2) COMMENT &#039;成绩&#039;
)ENGINE=InnoDB CHARSET=UTF8 COMMENT &#039;学生表&#039;</code></pre>
<p>插入测试数据：</p>
<pre><code class="language-sql">INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;枫阿雨&#039;, &#039;男&#039;, 19, 89);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;雨阿枫&#039;, &#039;男&#039; 19, 90);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;阿枫雨&#039;, &#039;男&#039;, 19, 62);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;枫雨阿&#039;, &#039;男&#039;, 22, 75);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;雨枫阿&#039;, &#039;女&#039;, 18, 59);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;阿雨枫&#039;, &#039;其他&#039;, 27, 88);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;亚烟雨&#039;, &#039;男&#039;, 19, 88);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;烟亚雨&#039;, &#039;女&#039;, 28, 81);
INSERT INTO student(no, name, sex, age, score) VALUES (DEFAULT, &#039;雨亚烟&#039;, &#039;其他&#039;, 32, 62);</code></pre>
<h4>4.1 分组查询</h4>
<pre><code class="language-sql">SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n] FROM 表名 WHERE 查询条件 GROUP BY 字段名1, 字段名2, ...,字段名n;</code></pre>
<blockquote>
<p>注：分组查询所得结果只会展示组内的第一条数据</p>
</blockquote>
<p><font color=blue>示例</font>：从学生表查询成绩在80分以上的学生信息并按性别分组</p>
<pre><code class="language-sql">SELECT * FROM student WHERE score &gt; 80 GROUP BY sex;</code></pre>
<p><font color=blue>示例</font>：从学生表查询成绩在60~80之间的学生信息并按性别和年龄分组</p>
<pre><code class="language-sql">SELECT * FROM student WHERE score &gt;= 60 AND score &lt;= 80 GROUP BY sex, age;</code></pre>
<h4>4.2 聚合函数</h4>
<ul>
<li>
<p><font color=red><strong>COUNT()：</strong></font>统计满足条件的数据总条数</p>
<p><font color=blue>示例</font>：从学生表查询成绩在80分以上的学生人数</p>
<pre><code class="language-sql">SELECT COUNT(*) total FROM student WHERE score > 80;</code></pre>
</li>
<li>
<p><font color=red><strong>SUM()：</strong></font>只能用于数值类型的字段或表达式，九三该满足条件的字段值的总和</p>
<p><font color=blue>示例</font>：从学生表查询不及格的学生人数和总成绩</p>
<pre><code class="language-sql">SELECT COUNT(*) totalCount, SUM(score) totalScore FROM student WHERE score < 60;  </code></pre>
</li>
<li>
<p><font color=red><strong>AVG()：</strong></font>只能用于数值类型的字段或者表达式，计算该满足条件的字段值的平均值</p>
<p><font color=blue>示例</font>：从学生表查询男生、女生、其他类型的学生的平均成绩</p>
<pre><code class="language-sql">SELECT sex, AVG(score) avgScore FROM student GROUP BY sex; </code></pre>
</li>
<li>
<p><font color=red><strong>MAX()：</strong></font>只能用于数值类型的字段或者表达式，计算该满足条件的字段值的最大值</p>
<p><font color=blue>示例</font>：从学生表查询学生的最大年龄</p>
<pre><code class="language-sql">SELECT MAX(age) FROM student;</code></pre>
</li>
<li>
<p><font color=red><strong>MIN()：</strong></font>只能用于数值类型的字段或者表达式，计算该满足条件的字段值的最小值</p>
<p><font color=blue>示例</font>：从学生表查询学生的最低分</p>
<pre><code class="language-sql">SELECT MIN(score) FROM student;</code></pre>
</li>
</ul>
<h4>4.3 分组查询结果筛选</h4>
<p>分组后如果还需要满足其他条件，则需要使用HAVING子句来完成。</p>
<pre><code class="language-sql">SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n] FROM 表名 WHERE 查询条件 GROUP BY 字段名1, 字段名2, ...,字段名n HAVING 筛选条件;</code></pre>
<p><font color=blue>示例</font>：从学生表查询年龄在18~22之间的学生信息并按性别分组，找出组内平均分在75分以上的组</p>
<pre><code class="language-sql">SELECT * FROM student WHERE age BETWEEN 18 AND 22 GROUP BY sex HAVING AVG(score) &gt; 75;</code></pre>
<h3>5. 排序</h3>
<pre><code class="language-sql">SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n] FROM 表名 WHERE 查询条件 ORDER BY 字段名1 ASC|DESC，字段名2 ASC|DESC,..., 字段名n ASC|DESC;
-- DESC : 降序排序
-- ASC : 升序排序</code></pre>
<blockquote>
<p>注：<code>ORDER BY</code>必须位于<code>WHERE</code> 条件之后。</p>
</blockquote>
<p><font color=blue>示例</font>：从学生表查询年龄在18~30岁之间的学生信息并按成绩从高到低排列，如果成绩相同，则按年龄从小到大排列</p>
<pre><code class="language-sql">SELECT * FROM student WHERE age BETWEEN 18 AND 30 ORDER BY score DESC, age ASC;</code></pre>
<h3>6. 分页</h3>
<pre><code class="language-sql">SELECT ALL/DISTINCT * | 字段名1 AS 别名1[,字段名1 AS 别名1, ..., 字段名n AS 别名n] FROM 表名 WHERE 查询条件 LIMIT 偏移量, 查询条数</code></pre>
<p>LIMIT的第一个参数表示偏移量，也就是跳过的行数。<br />
LIMIT的第二个参数表示查询返回的最大行数，可能没有给定的数量那么多行。</p>
<p><font color=blue>示例</font>：从学生表分页查询成绩及格的学生信息，每页显示3条，查询第2页学生信息</p>
<pre><code class="language-sql">SELECT * FROM student WHERE score &gt;= 60 LIMIT 3, 3;</code></pre>
<blockquote>
<p>注：如果一个查询中包含分组、排序和分页，那么它们之间必须按照<font color=red><strong>分组-&gt;排序-&gt;分页</strong></font>的先后顺序排列。</p>
</blockquote>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/10/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%8c-mysql%e7%9a%84%e5%a2%9e%e5%88%a0%e6%94%b9%e6%9f%a5/">从零开始学习MySQL(二) – MySQL的增删改查</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/09/10/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%ba%8c-mysql%e7%9a%84%e5%a2%9e%e5%88%a0%e6%94%b9%e6%9f%a5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>从零开始学习MySQL(一) &#8211; 初识MySQL数据库</title>
		<link>https://www.crazyfay.com/2021/09/06/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%80-%e5%88%9d%e8%af%86mysql%e6%95%b0%e6%8d%ae%e5%ba%93/</link>
					<comments>https://www.crazyfay.com/2021/09/06/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%80-%e5%88%9d%e8%af%86mysql%e6%95%b0%e6%8d%ae%e5%ba%93/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Mon, 06 Sep 2021 12:35:24 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[MySQL]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=177</guid>

					<description><![CDATA[<p>第一章 初识MySQL数据库 第一节 数据库操作 1. 创建数据库的语法 CREATE DATABASE [I [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/06/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%80-%e5%88%9d%e8%af%86mysql%e6%95%b0%e6%8d%ae%e5%ba%93/">从零开始学习MySQL(一) &#8211; 初识MySQL数据库</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<h1>第一章 初识<code>MySQL</code>数据库</h1>
<h2>第一节 数据库操作</h2>
<h3>1. 创建数据库的语法</h3>
<pre><code class="language-sql">CREATE DATABASE [IF NOT EXISTS] 数据库名称 DEFAULT CHARACTER SET 字符集 COLLATE 排序规则;</code></pre>
<p><font color="blue" >实例</font>：创建数据库lesson，并指定字符集为<code>GBK</code>，排序规则为<code>GBK_CHINESE_CI</code> </p>
<pre><code class="language-sql">CREATE DATABASE IF NOT EXISTS lesson DEFAULT CHARACTER SET GBK COLLATE GBK_CHINESE_CI;</code></pre>
<h3>2.修改数据库的语法</h3>
<pre><code class="language-sql">ALTER DATABASE 数据库名称 CHARACTER SET 字符集 COLLATE 排序规则;</code></pre>
<p><font color="blue" >实例</font>：修改数据库lesson，并指定字符集为<code>UTF8</code>，排序规则为<code>UTF8_GENERAL_CI</code> </p>
<pre><code class="language-sql">ALTER DATABASE lesson CHARACTER SET UTF8 COLLATE UTF8 GENERAL_CI;</code></pre>
<h3>3.删除数据库的语法</h3>
<pre><code class="language-sql">DROP DATABASE [IF EXISTS] 数据库名称;</code></pre>
<p><font color="blue" >实例</font>：删除数据库lesson</p>
<pre><code class="language-sql">DROP DATABASE IF EXISTS lesson;</code></pre>
<h3>4.查看数据库语法</h3>
<pre><code class="language-sql">SHOW DATABASE;</code></pre>
<h3>5.使用数据库语法</h3>
<pre><code class="language-sql">USE 数据库名称;</code></pre>
<p><font color="blue" >实例</font>：使用数据库lesson</p>
<pre><code class="language-sql">USE lesson;</code></pre>
<h2>第二节 列类型</h2>
<p>在<code>MySQL</code>中，常用列类型主要为数值类型、日期时间类型、字符串类型</p>
<h3>1. 数值类型</h3>
<table>
<thead>
<tr>
<th style="text-align: center;"><code>tinyint</code></th>
<th style="text-align: center;"><code>smallint</code></th>
<th style="text-align: center;"><code>mediumint</code></th>
<th style="text-align: center;"><code>int</code></th>
<th style="text-align: center;"><code>bigint</code></th>
<th style="text-align: center;"><code>float</code></th>
<th style="text-align: center;"><code>double</code></th>
<th style="text-align: center;"><code>decimal</code></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">1字节</td>
<td style="text-align: center;">2字节</td>
<td style="text-align: center;">3字节</td>
<td style="text-align: center;">4字节</td>
<td style="text-align: center;">8字节</td>
<td style="text-align: center;">4字节(浮点)</td>
<td style="text-align: center;">8字节(浮点)</td>
<td style="text-align: center;">m字节(浮点)</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注：<code>decimal(m, d)</code> 为字符串存储的浮点数，其中m表示总位数，d表示保留小数位数</p>
</blockquote>
<h3>2. 日期时间类型</h3>
<table>
<thead>
<tr>
<th style="text-align: center;"><code>DATE</code>(日期)</th>
<th style="text-align: center;"><code>TIME</code>(时间)</th>
<th style="text-align: center;"><code>DATETIME</code></th>
<th style="text-align: center;"><code>TIMESTAMP(时间戳)</code></th>
<th style="text-align: center;"><code>YEAR</code></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><code>YYYY-MM-dd</code></td>
<td style="text-align: center;"><code>HH:mm:ss</code></td>
<td style="text-align: center;"><code>YY-MM-dd HH:mm:ss</code></td>
<td style="text-align: center;"><code>YY-MM-dd HH:mm:ss</code></td>
<td style="text-align: center;"><code>YYYY</code></td>
</tr>
</tbody>
</table>
<h3>3. 字符串类型</h3>
<table>
<thead>
<tr>
<th style="text-align: left;">类型</th>
<th style="text-align: left;">说明</th>
<th style="text-align: left;">最大长度</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>char</code>[(M)]</td>
<td style="text-align: left;">固定长字符串，索引快但费空间，0&lt;=M&lt;=255</td>
<td style="text-align: left;">M字符</td>
</tr>
<tr>
<td style="text-align: left;"><code>varchar</code>[(M)]</td>
<td style="text-align: left;">可变字符串，0&lt;=M&lt;=65535</td>
<td style="text-align: left;">变长度</td>
</tr>
<tr>
<td style="text-align: left;"><code>text</code></td>
<td style="text-align: left;">文本串</td>
<td style="text-align: left;">2^16^-1字节</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注： <code>char(50)</code> ：不论插入值占用多少位空间，在数据库中都会占50个长度。比如&quot;男&quot;</p>
<p>​     <code>varchar(50)</code>：最大占用50个长度。比如&quot;男&quot;占用1个</p>
<p>​     <code>text</code>：用于存储长文本</p>
</blockquote>
<h3>4. 列类型修饰属性</h3>
<table>
<thead>
<tr>
<th style="text-align: left;">类型</th>
<th style="text-align: left;">说明</th>
<th style="text-align: left;">示例</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>UNSIGNED</code></td>
<td style="text-align: left;">无符号，只能修饰数值类型，表明该列数据不能出现负数</td>
<td style="text-align: left;"><code>UNSIGNED</code> INT(4)，表示只能为4位大于等于0整数</td>
</tr>
<tr>
<td style="text-align: left;"><code>ZEROFILL</code></td>
<td style="text-align: left;">不足的位数使用0</td>
<td style="text-align: left;">INT(4) <code>ZEROFILL</code>，如果给定的值位10，此时只有2位，而该列需要4位，不足的2位由0来填充，最终值为0010</td>
</tr>
<tr>
<td style="text-align: left;"><code>NOT NULL</code></td>
<td style="text-align: left;">表示该列类型的值不能为空</td>
<td style="text-align: left;"><code>VARCHAR</code>(20) NOT NULL，表示该列不能为空值</td>
</tr>
<tr>
<td style="text-align: left;"><code>DEFAULT</code></td>
<td style="text-align: left;">表示设置默认值</td>
<td style="text-align: left;">INT(4) DEFAULT 0，表示该列不赋值时默认为0</td>
</tr>
<tr>
<td style="text-align: left;"><code>AUTO_INCREMENT</code></td>
<td style="text-align: left;">表示自增长，只能应用于数值列类型，该列类型必须为键，且不能为空</td>
<td style="text-align: left;">INT(11) AUTO_INCREMENT NOT NULL PRIMARY KEY。第一次为该列中插入值时为1，第二次为2，以此类推</td>
</tr>
</tbody>
</table>
<h2>第三节 数据表操作</h2>
<h3>1. 数据表类型</h3>
<p><code>MySQL</code>中的数据表类型由许多，如<code>MyISAM</code>、<code>InnoDB</code>、<code>HEAP</code>、<code>BOB</code>、<code>CSV</code>等。其中最常用的就是<code>MyISAM</code>和<code>InnoDB</code></p>
<h4><code>MyISAM</code>和<code>InnoDB</code>的区别</h4>
<table>
<thead>
<tr>
<th style="text-align: left;">名称</th>
<th style="text-align: left;"><code>MyISAM</code></th>
<th style="text-align: left;"><code>InnoDB</code></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">事务处理</td>
<td style="text-align: left;">不支持</td>
<td style="text-align: left;">支持</td>
</tr>
<tr>
<td style="text-align: left;">数据行锁定</td>
<td style="text-align: left;">不支持</td>
<td style="text-align: left;">支持</td>
</tr>
<tr>
<td style="text-align: left;">外键约束</td>
<td style="text-align: left;">不支持</td>
<td style="text-align: left;">支持</td>
</tr>
<tr>
<td style="text-align: left;">全文索引</td>
<td style="text-align: left;">支持</td>
<td style="text-align: left;">不支持</td>
</tr>
<tr>
<td style="text-align: left;">表空间大小</td>
<td style="text-align: left;">较小</td>
<td style="text-align: left;">较大，约2倍</td>
</tr>
</tbody>
</table>
<p>事务：涉及的所有操作是一个整体，要么都执行，要么都不执行。</p>
<p>数据行锁定：一行数据，当一个用户在修改该数据时，可以将该条数据锁定。</p>
<blockquote>
<p>注：如何选择数据表的类型?</p>
</blockquote>
<pre><code class="language-te">当涉及的业务操作以查询居多，修改和删除较少时，可以使用 MyISAM 。当涉及的业务操作经常会有修改和删除操作时使用 InnoDB</code></pre>
<h3>2. 创建数据表</h3>
<pre><code class="language-sql">CREATE TABLE [IF NOT EXISTS] 数据表名称(
    字段名1 列类型(长度) [修饰属性] [键/索引] [注释],
    字段名2 列类型(长度) [修饰属性] [键/索引] [注释],
    字段名3 列类型(长度) [修饰属性] [键/索引] [注释],
    ......
    字段名n 列类型(长度) [修饰属性] [键/索引] [注释]
) [ENGINE = 数据表类型][CHARSET = 字符集编码] [COMMENT = 注释];</code></pre>
<p><font color=blue>示例</font>：创建学生表，表中有字段学号、姓名、性别、年龄和成绩</p>
<pre><code class="language-sql">CREATE TABLE IF NOT EXISTS student(
    `number` VARCHAR(30) NOT NULL PRIMARY KEY COMMENT &#039;学号，主键&#039;,
    name VARCHAR(30) NOT NULL COMMENT &#039;姓名&#039;,
    sex TINYINT(1) UNSIGNED DEFAULT 0 COMMENT &#039;性别：0-男 1-女 2-其他&#039;,
    age TINYINT(3) UNSIGNED DEFAULT 0 COMMENT &#039;年龄&#039;,
    score DOUBLE(5, 2) UNSIGNED DEFAULT NULL COMMENT &#039;成绩&#039;
)ENGINE=InnoDB CHARSET=UTF8 COMMENT=&#039;学生表&#039;; </code></pre>
<h3>3. 修改数据表</h3>
<ul>
<li>
<p>修改表名</p>
<pre><code class="language-sql">ALTER TABLE 表名 RENAME AS 新表名;</code></pre>
<p><font color=blue>示例</font>：将<code>student</code>表名修改为<code>stu</code></p>
<pre><code class="language-sql">ALTER TABLE student RENAME AS stu;</code></pre>
</li>
<li>
<p>增加字段</p>
<pre><code class="language-sql">ALTER TABLE 表名 ADD 字段名 列类型(长度) [修饰属性] [键/索引] [注释];</code></pre>
<p><font color=blue>示例</font>：在<code>stu</code>中添加字段联系电话(phone)，类型为字符串，长度为11，非空</p>
<pre><code class="language-sql">ALTER TABLE stu ADD phone VARCHAR(11) NOT NULL COMMENT '联系电话';</code></pre>
</li>
<li>
<p>查看表结构</p>
<pre><code class="language-sql">DESC 表名; --查看表结构</code></pre>
</li>
<li>
<p>修改字段</p>
<pre><code class="language-sql">-- MODIFY 只能修改字段的修饰属性
ALTER TABLE 表名 MODIFY 字段名 列类型(长度) [修饰属性] [键/索引] [注释];
-- CHANGE 可以修改字段的名字以及修饰属性
ALTER TABLE 表名 CHANGE 字段名 新字段名 列类型(长度) [修饰属性] [键/索引] [注释];</code></pre>
<p><font color=blue>示例</font>：将<code>stu</code>表中的sex字般的类型设置为<code>VARCHAR</code>，长度为2，默认值为男，注释为&quot;性别，男，女，其他&quot;</p>
<pre><code class="language-sql">ALTER TABLE stu MODIFY sex VARCHAR(2) DEFAULT '男' COMMENT '性别：男，女，其他'; </code></pre>
<p><font color=blue>示例</font>：将<code>stu</code>表中的<code>phone</code>字段修改为<code>moblie</code>，属性保持不变</p>
<pre><code class="language-sql">ALTER TABLE stu CHANGE phone mobile VARCHAR(11) NOT NULL COMMENT '联系电话';</code></pre>
</li>
<li>
<p>删除字段</p>
<pre><code class="language-sql">ALTER TABLE 表名 DROP 字段名;</code></pre>
<p><font color=blue>示例</font>：将<code>stu</code>表中的<code>mobile</code>字段删除</p>
<pre><code class="language-sql">ALTER TABLE stu DROP mobile;</code></pre>
</li>
<li>
<p>删除数据表</p>
<pre><code class="language-sql">DROP TABLE [IF EXISTS] 表名;</code></pre>
<p><font color=blue>示例</font>：删除数据表<code>stu</code></p>
<pre><code class="language-sql">DROP TABLE IF EXISTS stu;</code></pre>
</li>
</ul>
<h2></h2>
<p><a rel="nofollow" href="https://www.crazyfay.com/2021/09/06/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%80-%e5%88%9d%e8%af%86mysql%e6%95%b0%e6%8d%ae%e5%ba%93/">从零开始学习MySQL(一) &#8211; 初识MySQL数据库</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2021/09/06/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0mysql%e4%b8%80-%e5%88%9d%e8%af%86mysql%e6%95%b0%e6%8d%ae%e5%ba%93/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
