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. sails项目创建与常用基础操作总结

    1.全局安装: cnpm install -g sails 2.创建项目: sails new sails_shop ,选2 或者: sails new sails_shop --fast ,选2 c ...

  2. spring 5.x 系列第7篇 —— 整合Redis客户端 Jedis和Redisson (xml配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件 2.2 单机配置 ...

  3. 修改npm默认安装路径

    npm config ls npm config set prefix D:\ag\npm

  4. 微信jssdk支付坑

    1.使用easywechat开发的时候,由于没有注意,配置文件中默认的请求地址是 https://api.weixin.qq.com/结果调试了半天,一直报错“40066” 这也是怪自己粗心,结果去分 ...

  5. 手把手docker部署java应用(初级篇)

    本篇原创发布于 Flex 的个人博客:点击跳转 前言   在没有 docker 前,项目转测试是比较麻烦的一件事.首先会化较长的时间搭建测试环境,然后在测试过程中又经常出现测试说是 bug,开发说无法 ...

  6. Selenium Grid分布式测试环境搭建

    Selenium Grid简介 Selenium Grid实际上是基于Selenium RC的,而所谓的分布式结构就是由一个hub节点和若干个node代理节点组成.Hub用来管理各个代理节点的注册信息 ...

  7. Linux文件系统目录结构详解

    在我们初学嵌入式Linux时,首先学习的就是Linux的最小根文件系统:下面我将为初学者们详细的阐述一下Linux的最小根文件系统. 根目录在Linux中即为“/”,要进入根目录,命令“cd  /”即 ...

  8. 从零开始基于go-thrift创建一个RPC服务

    Thrift 是一种被广泛使用的 rpc 框架,可以比较灵活的定义数据结构和函数输入输出参数,并且可以跨语言调用.为了保证服务接口的统一性和可维护性,我们需要在最开始就制定一系列规范并严格遵守,降低后 ...

  9. Minimum Spanning Tree

    前言 说到最小生成树(Minimum Spanning Tree),首先要对以下的图论概念有所了解. 图 图(Graph)是表示物件与物件之间的关系的数学对象,是图论的基本研究对象.图的定义方式有两种 ...

  10. c++ 二分答案

    c++ 二分答案 问题 使得x^x达到或超过n位数字的最小正整数x是多少?n<=2000000000 分析 对与这种较难求解的问题,我们很难想出较好的解决策略.但是,我们至少知道答案一定在1与2 ...