learn.lianglianglee.com/专栏/MySQL实战45讲/37 什么时候会使用内部临时表?.md.html
2022-05-11 18:52:13 +08:00

1251 lines
31 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>37 什么时候会使用内部临时表?.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/MySQL实战45讲/00 开篇词 这一次让我们一起来搞懂MySQL.md">00 开篇词 这一次让我们一起来搞懂MySQL.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/01 基础架构一条SQL查询语句是如何执行的.md">01 基础架构一条SQL查询语句是如何执行的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/02 日志系统一条SQL更新语句是如何执行的.md">02 日志系统一条SQL更新语句是如何执行的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/03 事务隔离:为什么你改了我还看不见?.md">03 事务隔离:为什么你改了我还看不见?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/04 深入浅出索引(上).md">04 深入浅出索引(上).md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/05 深入浅出索引(下).md">05 深入浅出索引(下).md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/06 全局锁和表锁 :给表加个字段怎么有这么多阻碍?.md">06 全局锁和表锁 :给表加个字段怎么有这么多阻碍?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/07 行锁功过:怎么减少行锁对性能的影响?.md">07 行锁功过:怎么减少行锁对性能的影响?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/08 事务到底是隔离的还是不隔离的?.md">08 事务到底是隔离的还是不隔离的?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/09 普通索引和唯一索引,应该怎么选择?.md">09 普通索引和唯一索引,应该怎么选择?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/10 MySQL为什么有时候会选错索引.md">10 MySQL为什么有时候会选错索引.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/11 怎么给字符串字段加索引?.md">11 怎么给字符串字段加索引?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/12 为什么我的MySQL会“抖”一下.md">12 为什么我的MySQL会“抖”一下.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/13 为什么表数据删掉一半,表文件大小不变?.md">13 为什么表数据删掉一半,表文件大小不变?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/14 count()这么慢,我该怎么办?.md">14 count()这么慢,我该怎么办?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/15 答疑文章(一):日志和索引相关问题.md">15 答疑文章(一):日志和索引相关问题.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/16 “order by”是怎么工作的.md">16 “order by”是怎么工作的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/17 如何正确地显示随机消息?.md">17 如何正确地显示随机消息?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/18 为什么这些SQL语句逻辑相同性能却差异巨大.md">18 为什么这些SQL语句逻辑相同性能却差异巨大.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/19 为什么我只查一行的语句,也执行这么慢?.md">19 为什么我只查一行的语句,也执行这么慢?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/20 幻读是什么,幻读有什么问题?.md">20 幻读是什么,幻读有什么问题?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/21 为什么我只改一行的语句,锁这么多?.md">21 为什么我只改一行的语句,锁这么多?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/22 MySQL有哪些“饮鸩止渴”提高性能的方法.md">22 MySQL有哪些“饮鸩止渴”提高性能的方法.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/23 MySQL是怎么保证数据不丢的.md">23 MySQL是怎么保证数据不丢的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/24 MySQL是怎么保证主备一致的.md">24 MySQL是怎么保证主备一致的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/25 MySQL是怎么保证高可用的.md">25 MySQL是怎么保证高可用的.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/26 备库为什么会延迟好几个小时?.md">26 备库为什么会延迟好几个小时?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/27 主库出问题了,从库怎么办?.md">27 主库出问题了,从库怎么办?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/28 读写分离有哪些坑?.md">28 读写分离有哪些坑?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/29 如何判断一个数据库是不是出问题了?.md">29 如何判断一个数据库是不是出问题了?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/30 答疑文章(二):用动态的观点看加锁.md">30 答疑文章(二):用动态的观点看加锁.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/31 误删数据后除了跑路,还能怎么办?.md">31 误删数据后除了跑路,还能怎么办?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/32 为什么还有kill不掉的语句.md">32 为什么还有kill不掉的语句.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/33 我查这么多数据,会不会把数据库内存打爆?.md">33 我查这么多数据,会不会把数据库内存打爆?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/34 到底可不可以使用join.md">34 到底可不可以使用join.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/35 join语句怎么优化.md">35 join语句怎么优化.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/36 为什么临时表可以重名?.md">36 为什么临时表可以重名?.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/MySQL实战45讲/37 什么时候会使用内部临时表?.md">37 什么时候会使用内部临时表?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/38 都说InnoDB好那还要不要使用Memory引擎.md">38 都说InnoDB好那还要不要使用Memory引擎.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/39 自增主键为什么不是连续的?.md">39 自增主键为什么不是连续的?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/40 insert语句的锁为什么这么多.md">40 insert语句的锁为什么这么多.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/41 怎么最快地复制一张表?.md">41 怎么最快地复制一张表?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/42 grant之后要跟着flush privileges吗.md">42 grant之后要跟着flush privileges吗.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/43 要不要使用分区表?.md">43 要不要使用分区表?.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/44 答疑文章(三):说一说这些好问题.md">44 答疑文章(三):说一说这些好问题.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/45 自增id用完怎么办.md">45 自增id用完怎么办.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/我的MySQL心路历程.md">我的MySQL心路历程.md.html</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/结束语 点线网面一起构建MySQL知识网络.md">结束语 点线网面一起构建MySQL知识网络.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>37 什么时候会使用内部临时表?</h1>
<p>在[第 16]和[第 34]篇文章中,我分别和你介绍了 sort buffer、内存临时表和 join buffer。这三个数据结构都是用来存放语句执行过程中的中间数据以辅助 SQL 语句的执行的。其中,我们在排序的时候用到了 sort buffer在使用 join 语句的时候用到了 join buffer。</p>
<p>然后你可能会有这样的疑问MySQL 什么时候会使用内部临时表呢?</p>
<p>今天这篇文章,我就先给你举两个需要用到内部临时表的例子,来看看内部临时表是怎么工作的。然后,我们再来分析,什么情况下会使用内部临时表。</p>
<h1>union 执行流程</h1>
<p>为了便于量化分析,我用下面的表 t1 来举例。</p>
<pre><code>create table t1(id int primary key, a int, b int, index(a));
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i&lt;=1000)do
insert into t1 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
</code></pre>
<p>然后,我们执行下面这条语句:</p>
<pre><code>(select 1000 as f) union (select id from t1 order by id desc limit 2);
</code></pre>
<p>这条语句用到了 union它的语义是取这两个子查询结果的并集。并集的意思就是这两个集合加起来重复的行只保留一行。</p>
<p>下图是这个语句的 explain 结果。</p>
<p><img src="assets/402cbdef84eef8f1b42201c6ec4bad4e.png" alt="img" /></p>
<p>图 1 union 语句 explain 结果</p>
<p>可以看到:</p>
<ul>
<li>第二行的 key=PRIMARY说明第二个子句用到了索引 id。</li>
<li>第三行的 Extra 字段,表示在对子查询的结果集做 union 的时候,使用了临时表 (Using temporary)。</li>
</ul>
<p>这个语句的执行流程是这样的:</p>
<ol>
<li>创建一个内存临时表,这个临时表只有一个整型字段 f并且 f 是主键字段。</li>
<li>执行第一个子查询,得到 1000 这个值,并存入临时表中。</li>
<li>执行第二个子查询:
<ul>
<li>拿到第一行 id=1000试图插入临时表中。但由于 1000 这个值已经存在于临时表了,违反了唯一性约束,所以插入失败,然后继续执行;</li>
<li>取到第二行 id=999插入临时表成功。</li>
</ul>
</li>
<li>从临时表中按行取出数据,返回结果,并删除临时表,结果中包含两行数据分别是 1000 和 999。</li>
</ol>
<p>这个过程的流程图如下所示:</p>
<p><img src="assets/5d038c1366d375cc997005a5d65c600e.jpg" alt="img" /></p>
<p>图 2 union 执行流程</p>
<p>可以看到,这里的内存临时表起到了暂存数据的作用,而且计算过程还用上了临时表主键 id 的唯一性约束,实现了 union 的语义。</p>
<p>顺便提一下,如果把上面这个语句中的 union 改成 union all 的话,就没有了“去重”的语义。这样执行的时候,就依次执行子查询,得到的结果直接作为结果集的一部分,发给客户端。因此也就不需要临时表了。</p>
<p><img src="assets/c1e90d1d7417b484d566b95720fe3f6d.png" alt="img" /></p>
<p>图 3 union all 的 explain 结果</p>
<p>可以看到,第二行的 Extra 字段显示的是 Using index表示只使用了覆盖索引没有用临时表了。</p>
<h1>group by 执行流程</h1>
<p>另外一个常见的使用临时表的例子是 group by我们来看一下这个语句</p>
<pre><code>select id%10 as m, count(*) as c from t1 group by m;
</code></pre>
<p>这个语句的逻辑是把表 t1 里的数据,按照 id%10 进行分组统计,并按照 m 的结果排序后输出。它的 explain 结果如下:</p>
<p><img src="assets/3d1cb94589b6b3c4bb57b0bdfa385d98.png" alt="img" /></p>
<p>图 4 group by 的 explain 结果</p>
<p>在 Extra 字段里面,我们可以看到三个信息:</p>
<ul>
<li>Using index表示这个语句使用了覆盖索引选择了索引 a不需要回表</li>
<li>Using temporary表示使用了临时表</li>
<li>Using filesort表示需要排序。</li>
</ul>
<p>这个语句的执行流程是这样的:</p>
<ol>
<li>创建内存临时表,表里有两个字段 m 和 c主键是 m</li>
<li>扫描表 t1 的索引 a依次取出叶子节点上的 id 值,计算 id%10 的结果,记为 x
<ul>
<li>如果临时表中没有主键为 x 的行,就插入一个记录 (x,1);</li>
<li>如果表中有主键为 x 的行,就将 x 这一行的 c 值加 1</li>
</ul>
</li>
<li>遍历完成后,再根据字段 m 做排序,得到结果集返回给客户端。</li>
</ol>
<p>这个流程的执行图如下:</p>
<p><img src="assets/0399382169faf50fc1b354099af71954.jpg" alt="img" /></p>
<p>图 5 group by 执行流程</p>
<p>图中最后一步,对内存临时表的排序,在[第 17 篇文章]中已经有过介绍,我把图贴过来,方便你回顾。</p>
<p><img src="assets/b5168d201f5a89de3b424ede2ebf3d68.jpg" alt="img" /></p>
<p>图 6 内存临时表排序流程</p>
<p>其中,临时表的排序过程就是图 6 中虚线框内的过程。</p>
<p>接下来,我们再看一下这条语句的执行结果:</p>
<p><img src="assets/ae6a28d890efc35ee4d07f694068f455.png" alt="img" /></p>
<p>图 7 group by 执行结果</p>
<p>如果你的需求并不需要对结果进行排序,那你可以在 SQL 语句末尾增加 order by null也就是改成</p>
<pre><code>select id%10 as m, count(*) as c from t1 group by m order by null;
</code></pre>
<p>这样就跳过了最后排序的阶段,直接从临时表中取数据返回。返回的结果如图 8 所示。</p>
<p><img src="assets/036634e53276eaf8535c3442805dfaeb.png" alt="img" /></p>
<p>图 8 group + order by null 的结果(内存临时表)</p>
<p>由于表 t1 中的 id 值是从 1 开始的,因此返回的结果集中第一行是 id=1扫描到 id=10 的时候才插入 m=0 这一行,因此结果集里最后一行才是 m=0。</p>
<p>这个例子里由于临时表只有 10 行,内存可以放得下,因此全程只使用了内存临时表。但是,内存临时表的大小是有限制的,参数 tmp_table_size 就是控制这个内存大小的,默认是 16M。</p>
<p>如果我执行下面这个语句序列:</p>
<pre><code>set tmp_table_size=1024;
select id%100 as m, count(*) as c from t1 group by m order by null limit 10;
</code></pre>
<p>把内存临时表的大小限制为最大 1024 字节,并把语句改成 id % 100这样返回结果里有 100 行数据。但是,这时的内存临时表大小不够存下这 100 行数据也就是说执行过程中会发现内存临时表大小到达了上限1024 字节)。</p>
<p>那么,这时候就会把内存临时表转成磁盘临时表,磁盘临时表默认使用的引擎是 InnoDB。 这时,返回的结果如图 9 所示。</p>
<p><img src="assets/a76381d0f3c947292cc28198901f9e6e.png" alt="img" /></p>
<p>图 9 group + order by null 的结果(磁盘临时表)</p>
<p>如果这个表 t1 的数据量很大,很可能这个查询需要的磁盘临时表就会占用大量的磁盘空间。</p>
<h1>group by 优化方法 -- 索引</h1>
<p>可以看到不论是使用内存临时表还是磁盘临时表group by 逻辑都需要构造一个带唯一索引的表,执行代价都是比较高的。如果表的数据量比较大,上面这个 group by 语句执行起来就会很慢,我们有什么优化的方法呢?</p>
<p>要解决 group by 语句的优化问题,你可以先想一下这个问题:执行 group by 语句为什么需要临时表?</p>
<p>group by 的语义逻辑,是统计不同的值出现的个数。但是,由于每一行的 id%100 的结果是无序的,所以我们就需要有一个临时表,来记录并统计结果。</p>
<p>那么,如果扫描过程中可以保证出现的数据是有序的,是不是就简单了呢?</p>
<p>假设,现在有一个类似图 10 的这么一个数据结构,我们来看看 group by 可以怎么做。</p>
<p><img src="assets/5c4a581c324c1f6702f9a2c70acddd19.jpg" alt="img" /></p>
<p>图 10 group by 算法优化 - 有序输入</p>
<p>可以看到,如果可以确保输入的数据是有序的,那么计算 group by 的时候,就只需要从左到右,顺序扫描,依次累加。也就是下面这个过程:</p>
<ul>
<li>当碰到第一个 1 的时候,已经知道累积了 X 个 0结果集里的第一行就是 (0,X);</li>
<li>当碰到第一个 2 的时候,已经知道累积了 Y 个 1结果集里的第二行就是 (1,Y);</li>
</ul>
<p>按照这个逻辑执行的话,扫描到整个输入的数据结束,就可以拿到 group by 的结果,不需要临时表,也不需要再额外排序。</p>
<p>你一定想到了InnoDB 的索引,就可以满足这个输入有序的条件。</p>
<p>在 MySQL 5.7 版本支持了 generated column 机制,用来实现列数据的关联更新。你可以用下面的方法创建一个列 z然后在 z 列上创建一个索引(如果是 MySQL 5.6 及之前的版本,你也可以创建普通列和索引,来解决这个问题)。</p>
<pre><code>alter table t1 add column z int generated always as(id % 100), add index(z);
</code></pre>
<p>这样,索引 z 上的数据就是类似图 10 这样有序的了。上面的 group by 语句就可以改成:</p>
<pre><code>select z, count(*) as c from t1 group by z;
</code></pre>
<p>优化后的 group by 语句的 explain 结果,如下图所示:</p>
<p><img src="assets/c9f88fa42d92cf7dde78fca26c4798b9.png" alt="img" /></p>
<p>图 11 group by 优化的 explain 结果</p>
<p>从 Extra 字段可以看到,这个语句的执行不再需要临时表,也不需要排序了。</p>
<h1>group by 优化方法 -- 直接排序</h1>
<p>所以,如果可以通过加索引来完成 group by 逻辑就再好不过了。但是,如果碰上不适合创建索引的场景,我们还是要老老实实做排序的。那么,这时候的 group by 要怎么优化呢?</p>
<p>如果我们明明知道,一个 group by 语句中需要放到临时表上的数据量特别大,却还是要按照“先放到内存临时表,插入一部分数据后,发现内存临时表不够用了再转成磁盘临时表”,看上去就有点儿傻。</p>
<p>那么我们就会想了MySQL 有没有让我们直接走磁盘临时表的方法呢?</p>
<p>答案是,有的。</p>
<p>在 group by 语句中加入 SQL_BIG_RESULT 这个提示hint就可以告诉优化器这个语句涉及的数据量很大请直接用磁盘临时表。</p>
<p>MySQL 的优化器一看,磁盘临时表是 B+ 树存储,存储效率不如数组来得高。所以,既然你告诉我数据量很大,那从磁盘空间考虑,还是直接用数组来存吧。</p>
<p>因此,下面这个语句</p>
<pre><code>select SQL_BIG_RESULT id%100 as m, count(*) as c from t1 group by m;
</code></pre>
<p>的执行流程就是这样的:</p>
<ol>
<li>初始化 sort_buffer确定放入一个整型字段记为 m</li>
<li>扫描表 t1 的索引 a依次取出里面的 id 值, 将 id%100 的值存入 sort_buffer 中;</li>
<li>扫描完成后,对 sort_buffer 的字段 m 做排序(如果 sort_buffer 内存不够用,就会利用磁盘临时文件辅助排序);</li>
<li>排序完成后,就得到了一个有序数组。</li>
</ol>
<p>根据有序数组,得到数组里面的不同值,以及每个值的出现次数。这一步的逻辑,你已经从前面的图 10 中了解过了。</p>
<p>下面两张图分别是执行流程图和执行 explain 命令得到的结果。</p>
<p><img src="assets/8269dc6206a7ef20cb515c23df0b846a.jpg" alt="img" /></p>
<p>图 12 使用 SQL_BIG_RESULT 的执行流程图</p>
<p><img src="assets/83b6cd6b3e37dfbf9699cf0ccc0f1bec.png" alt="img" /></p>
<p>图 13 使用 SQL_BIG_RESULT 的 explain 结果</p>
<p>从 Extra 字段可以看到,这个语句的执行没有再使用临时表,而是直接用了排序算法。</p>
<p>基于上面的 union、union all 和 group by 语句的执行过程的分析我们来回答文章开头的问题MySQL 什么时候会使用内部临时表?</p>
<ol>
<li>如果语句执行过程可以一边读数据,一边直接得到结果,是不需要额外内存的,否则就需要额外的内存,来保存中间结果;</li>
<li>join_buffer 是无序数组sort_buffer 是有序数组,临时表是二维表结构;</li>
<li>如果执行逻辑需要用到二维表特性就会优先考虑使用临时表。比如我们的例子中union 需要用到唯一索引约束, group by 还需要用到另外一个字段来存累积计数。</li>
</ol>
<h1>小结</h1>
<p>通过今天这篇文章,我重点和你讲了 group by 的几种实现算法,从中可以总结一些使用的指导原则:</p>
<ol>
<li>如果对 group by 语句的结果没有排序要求,要在语句后面加 order by null</li>
<li>尽量让 group by 过程用上表的索引,确认方法是 explain 结果里没有 Using temporary 和 Using filesort</li>
<li>如果 group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大 tmp_table_size 参数,来避免用到磁盘临时表;</li>
<li>如果数据量实在太大,使用 SQL_BIG_RESULT 这个提示,来告诉优化器直接使用排序算法得到 group by 的结果。</li>
</ol>
<p>最后,我给你留下一个思考题吧。</p>
<p>文章中图 8 和图 9 都是 order by null为什么图 8 的返回结果里面0 是在结果集的最后一行,而图 9 的结果里面0 是在结果集的第一行?</p>
<p>你可以把你的分析写在留言区里,我会在下一篇文章和你讨论这个问题。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/MySQL实战45讲/36 为什么临时表可以重名?.md">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/MySQL实战45讲/38 都说InnoDB好那还要不要使用Memory引擎.md">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709972df08893d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>