Mysql优化(出自官方文档) - 第五篇

1 GROUP BY Optimization

通常来讲,实现group by的方式是创建一个临时表,然后按照group by的列插入到临时表中,在进行后续处理,但是如果group by的列均来自于同一个index(唯一或者二级索引),那么Mysql会使用index来进行group by处理。关于索引的使用方式,主要有两种:

  1. Loose Index Scan:将group by操作和所有的range一起来进行操作。
  2. Tight Index Scan:首先进行range scan,然后在对获取到的结果进行分组。
  • Loose Index Scan

    定义:不需要扫描group by的所有列来满足where条件,只需要考虑索引中的一部分来满足要求。Loose Index Scan需要满足下面的条件:

    • 只针对一个表
    • group by的列必须是索引leftmost的列(如果没有group by,distrinct也可以,且distrinct的列也必须为leftmost),比如,在一个表(t1,t2,t3,t4)上有索引(t1,t2,t3),此时group by的列为(t1,t2)或者(t1),均适用于Loose Index Scan,如果是(t1,t3,t4),那么将无法使用该优化技术。
    • 只支持MIN,MAX聚合函数,并且作用的对象都必须为同一个列,该列必须在索引里面且在group by语句中。
    • select中不在group by的列比较对象必须为常量,包括MINMAX
    • 索引中的列必须完全被索引,比如:一个c1 varchar(20),索引不能为c1(10),必须为整个长度。

    在EXPLAIN输出里面,如果采用了这种优化技术,那么会显示Using index for group-by

    下面的语句均使用这种优化技术(假设t1(t1,t2,t3,t4),有索引(t1,t2,t3)):

    SELECT c1, c2 FROM t1 GROUP BY c1, c2;
    SELECT DISTINCT c1, c2 FROM t1;
    SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
    SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
    SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
    SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
    SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;

    下面的语句不适用这种情况:

    • 聚合函数只支持MINMAX
    SELECT c1, SUM(c2) FROM t1 GROUP BY c1;
    • group by的列必须为leftmost:

      SELECT c1, c2 FROM t1 GROUP BY c2, c3;
    • select中剩余的列(没有被group by包含的)必须和一个常量进行比较,下面的c3不满足:

      SELECT c1, c3 FROM t1 GROUP BY c1, c2;

    除了MINMAX外,Loose Index Scan也可以作用于其他形式的聚合函数,但是有如下限制:

    • AVG, SUMCOUNT均可以支持,但是AVG, SUM必须只有一个参数,COUNT可以有多个参数
    • 不能有GROUP BYDISTINCT语句
    • 文章开头的限制依旧适用于这种情况。

    假设t1表(t1,t2,t3,t4)有索引(t1,t2,t3),下面的语句也可以使用与Loose Index Scan:

    SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;
    SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
  • Tight Index Scan

    当Loose Index Scan无法适用时,此时如果适用于Tight Index Scan,Mysql依旧不会去创建一个临时表,Tight Index Scan的定义为:如果有where条件,那么会根据索引范围直接进行扫描(index Scan),反之,会进行一个Full Index Scan。对于下面的语句,不适用于Loose Index Scan,但是依旧可以采用Tight Index Scan来进行分组:

    依旧假设t1表(t1,t2,t3,t4)有索引(t1,t2,t3):

    • GROUP BY的列不是leftmost,但是进行Full Index Scan后,在对其进行过滤。
    SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
    SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;

2 DISTINCT Optimization

由于DISTINCT和GROUP BY是可以相互转换的,因此,适用于GROUP BY的情况也同样适用于DISTINCT,比如下面的语句是等价的:

SELECT DISTINCT c1, c2, c3 FROM t1
WHERE c1 > const; SELECT c1, c2, c3 FROM t1
WHERE c1 > const GROUP BY c1, c2, c3;

3 LIMIT Query Optimization

大多数时候,Mysql也会对LIMIT row_count语句(没有HAVING)进行优化,大致总结如下:

  • LIMIT只有小部分行时,Mysql会选择用full table scan,而不是使用索引

  • Mysql扫描到LIMIT限定的行数后就会停止扫描,如果有order by就会停止排序,如果有distinct也会停止扫描,此时,Mysql可能只对表的大部分行进行了排序,所以会导致一个结果:带LIMIT和不带LIMITorder by的结果有几率不一致,该点下面会进行解释。

  • Mysql默认不会存储已经查询出来的结果,使用SQL_CALC_FOUND_ROWS,这样子可以通过 SELECT FOUND_ROWS()来重复获取计算出来的结果。

  • 如果需要使用临时表,Mysql也会利用row_count来计算所需要的空间。

  • 如果使用了LIMIT,那么优化器也有可能避免使用filesort,而是使用内存方式进行排序。

关于为什么带LIMIT和不带LIMIT可能结果不一样的原因:

order by的列有多个重复列的时候,如果带LIMITMysql会进行部分排序,如果部分排序后发现数目已经满足LIMITrow_count,那么就会停止排序,并将结果直接返回,而这个时候,有些数据还没有得到排序,所以这就导致了全排序(不带LIMIT)和带LIMIT的结果不一致,主要体现在其他列上面,来看下面的例子:

order by的是category列,表的数据如下:

mysql> SELECT * FROM ratings ORDER BY category;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 3 | 2 | 3.7 |
| 4 | 2 | 3.5 |
| 6 | 2 | 3.5 |
| 2 | 3 | 5.0 |
| 7 | 3 | 2.7 |
+----+----------+--------+

限制LIMIT 5的结果可能为:

mysql> SELECT * FROM ratings ORDER BY category LIMIT 5;
+----+----------+--------+
| id | category | rating |
+----+----------+--------+
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+----+----------+--------+

注意结果的第三行和第四行,和初始是不一样的,因为此时Mysql可能只进行了部分排序,所以有几率出现这样的结果。Mysql在实现排序的时候,只会保证order by列的顺序,并不会保证其他列的顺序。

4 Function Call Optimization

在Mysql里面,Function被分为两种:确定性函数和非确定性函数。

确定性函数:给定一个值,多次调用会确定性的返回另外一个值

非确定性函数:给定一个值,多次调用可能返回不同的值,比如:RAND(), UUID()

假设有下面的表:

CREATE TABLE t (id INT NOT NULL PRIMARY KEY, col_a VARCHAR(100));

对于下面的两种查询:

SELECT * FROM t WHERE id = POW(1,2);
SELECT * FROM t WHERE id = FLOOR(1 + RAND() * 49);

解释如下:

对于第一条语句:由于POW产生一个固定的结果,所以Mysql会将其视为一个常量进行优化,有可能使用索引查找来快速找到对应记录。

对于第二条语句:由于RAND会产生不同的结果,所以Mysql会对表t的每一行进行where判断,相应的,就会进行全表扫描,因为需要RAND来产生不同的结果。

综上,如果贸然使用非确定性的函数,对Mysql性能可能会产生隐性的不良影响,可能带来的影响有下面几种情况:

  • 非确定性函数由于无法产生固定的结果,所以优化器就没办法对其优化,比如:如果是常量,可以使用index lookup,但是由于非确定性函数的存在,就只能用table scan的方式了。
  • InnoDB中,非确定性函数可能会导致锁由单行锁升级为range-key lock
  • 在复制操作中,update中有非确定性函数可能是不安全的。

如果不得不用确定性函数,可以使用下面的方式进行优化:

  • RAND的结果首先赋值给一个常量,然后查询语句中直接使用该常量,这样,Mysql就会把where语句后面的值视为常量来进行优化,比如此时可以使用index lookup

    SET @keyval = FLOOR(1 + RAND() * 49);
    UPDATE t SET col_a = some_expr WHERE id = @keyval;
  • 将非确定性函数的值放在derived table里面,然后在where语句里面直接使用derived table里面的值,Mysql也可以使用相应的优化措施。

    UPDATE /*+ NO_MERGE(dt) */ t, (SELECT FLOOR(1 + RAND() * 49) AS r) AS dt
    SET col_a = some_expr WHERE id = dt.r;
  • 有的时候,如果可以确定部分条件,那么用and连接起来非确定性函数,这样子可以导致非确定性函数的执行次数大大减少,比如下面的语句。

    SELECT * FROM t WHERE partial_key=5 AND some_column=RAND();

5 Avoiding Full Table Scans

EXPLAIN的输出结果里面,如果column列显示为ALL,那么说明Mysql本次在进行全表扫描,为了避免全表扫描,而是让Mysql使用索引,可用下面的方式:

  • 使用ANALYZE TABLE tbl_name来更新表索引的分布性。

  • 使用FORCE INDEX命令告诉Mysql table scan的效率要远低于使用索引。

    SELECT * FROM t1, t2 FORCE INDEX (index_for_column)
    WHERE t1.col_name=t2.col_name;
  • 在启动Mysql的时候,mysqld命令加上 --max-seeks-for-key=1000 或者说使用 SET max_seeks_for_key=1000, 该值设置的越低,那么Mysql就更倾向于使用索引。

Mysql优化(出自官方文档) - 第五篇的更多相关文章

  1. Mysql优化(出自官方文档) - 第九篇(优化数据库结构篇)

    目录 Mysql优化(出自官方文档) - 第九篇(优化数据库结构篇) 1 Optimizing Data Size 2 Optimizing MySQL Data Types 3 Optimizing ...

  2. Mysql优化(出自官方文档) - 第二篇

    Mysql优化(出自官方文档) - 第二篇 目录 Mysql优化(出自官方文档) - 第二篇 1 关于Nested Loop Join的相关知识 1.1 相关概念和算法 1.2 一些优化 1 关于Ne ...

  3. Mysql优化(出自官方文档) - 第一篇(SQL优化系列)

    Mysql优化(出自官方文档) - 第一篇 目录 Mysql优化(出自官方文档) - 第一篇 1 WHERE Clause Optimization 2 Range Optimization Skip ...

  4. Mysql优化(出自官方文档) - 第三篇

    目录 Mysql优化(出自官方文档) - 第三篇 1 Multi-Range Read Optimization(MRR) 2 Block Nested-Loop(BNL) and Batched K ...

  5. Mysql优化(出自官方文档) - 第八篇(索引优化系列)

    目录 Mysql优化(出自官方文档) - 第八篇(索引优化系列) Optimization and Indexes 1 Foreign Key Optimization 2 Column Indexe ...

  6. Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇)

    Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇) 目录 Mysql优化(出自官方文档) - 第十二篇(优化锁操作篇) 1 Internal Locking Methods Row-Leve ...

  7. Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇)

    Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇) 目录 Mysql优化(出自官方文档) - 第十篇(优化InnoDB表篇) 1 Optimizing Storage Layout f ...

  8. Mysql优化(出自官方文档) - 第七篇

    Mysql优化(出自官方文档) - 第七篇 目录 Mysql优化(出自官方文档) - 第七篇 Optimizing Data Change Statements 1 Optimizing INSERT ...

  9. Mysql优化(出自官方文档) - 第六篇

    Mysql优化(出自官方文档) - 第六篇 目录 Mysql优化(出自官方文档) - 第六篇 Optimizing Subqueries, Derived Tables, View Reference ...

随机推荐

  1. SpringBoot启动访问JSP页面,直接进入页面或者访问不到,报404,并且加载tomcat插件tomcat-embed-jasper也不行

    这个问题花费了两天的时间,解决路径: 我用的是SpringBoot1.5.2,SpringMVC和Spring,tomcat启动插件都是默认的版本,Spring是4.3.7,jdk是1.7.0_80, ...

  2. asp.net core 2.2 生产环境直接更新View页面并立即生效

    有的时候我们会直接在生产环境上更新页面文件,比如更改了JS代码,CSS代码或页面的文案,布局等.这种没有改到后台代码的情况一般就是直接发布页面文件了,在asp.net core 2.2以前的版本(没有 ...

  3. Apple官文中的KVO 与 FBKVOController

    前言 本文将主要介绍以下内容: 详细列出Apple官文中KVO的注意事项(Apple KVO相关的引用皆摘自Apple官文). 介绍FBKVOController,以及它如何避免系统提供的KVO坑点. ...

  4. Linux下无法执行tree命令问题

    Linux下不能使用tree命令,是因为没有安装命令, 执行下面代码就行了 yum install tree -y

  5. scrapy基础知识之制作 Scrapy 爬虫 一共需要4步:

    1.新建项目 (scrapy startproject xxx):新建一个新的爬虫项目 2.明确目标 (编写items.py):明确你想要抓取的目标 3.制作爬虫 (spiders/xxspider. ...

  6. 如何取得Spring管理的bean

    本文主要讲3中实现方式,请用第3种方法(通用) 1.servlet方式加载时配置如下 <servlet> <servlet-name>springMVC</servlet ...

  7. 淺談Coach思考模式

    我現在是個窮屌,沒錯.我清楚的知道這一點,但是我也知道,我能改變. 之前幹了7年的評估行業,中間換了3家公司,第一家公司待的時間最長,待了5年.2018年開始,我就在思考轉行.之前在第一家企業接觸過一 ...

  8. Java编程思想:内部类其他知识点

    public class Test { public static void main(String[] args) { // InheritInner.test(); // EggTest.test ...

  9. 使用反射机制将对象序列化Json

    一 思路 获取对象的Class对象. 获取对象的属性数组, 迭代属性数据拼接属性名与属性值, 存入List. 将List转换为流库, 再将流库使用逗号分隔符转换为字符串, 去掉首尾的逗号 二 代码 p ...

  10. 找到linux中当前java的安装位置

    先看java -version $java version "1.8.0_111" Java(TM) SE Runtime Environment (build 1.8.0_111 ...