@
目录
- 前言
- [一、SELECT 优化](#一、SELECT 优化)
- (一)WHERE子句优化
- (二)范围优化
- (三)索引合并优化
- 1、定义
- [2、交叉访问算法 Using intersect](#2、交叉访问算法 Using intersect)
- [3、联合访问算法 Using union](#3、联合访问算法 Using union)
- [4、排序联合算法 Using sort_union](#4、排序联合算法 Using sort_union)
- (四)索引下推优化
- (五)外连接优化
- [(六)IS NULL 优化](#(六)IS NULL 优化)
- [(七)ORDER BY 优化](#(七)ORDER BY 优化)
- [1、使用索引进行 ORDER BY](#1、使用索引进行 ORDER BY)
- [2、使⽤filesort进行 ORDER BY](#2、使⽤filesort进行 ORDER BY)
- [(八)GROUP BY 优化](#(八)GROUP BY 优化)
- [(九)DISTINCT 优化](#(九)DISTINCT 优化)
- (十)函数调用优化
- 二、索引失效
- (一)复合索引没有遵守最左原则
- (二)WHERE中有OR且条件列没有索引
- (三)复合索引中第一个使用范围查询的条件或不以%开头的模糊查询之后的列不使用索引
- [(四)Like 查询以 % 开头](#(四)Like 查询以 % 开头)
- [(五)Where 子句中有表达式或函数](#(五)Where 子句中有表达式或函数)
- [(六)使用<> , not in](#(六)使用<> , not in)
- 三、索引使用原则
- 结语
前言
本期将继续介绍MySQL的SQL调优部分内容,此次要介绍的是SELECT 优化、索引失效的问题。
一、SELECT 优化
使用SELECT语句查询数据库中的数据是最频繁的操作,优化查询是使用数据库的重中之重,又由于程序员编写SQL语句时,出于可读性考虑和
自身水平的问题,提交到MySQL服务器的SQL风格各不相同,此时MySQL内置的优化器可以帮助我们完成一部分优化操作,了解如何其具体的优化方法有助于写出高性能的SQL语句。
(一)WHERE子句优化
本小节虽然介绍的是SELECT 的WHERE 子句优化,但同样适用于DELETE 和UPDATE 语句中的 WHERE 子句。下面我们来看以下WHERE 子句的优化场景。
1、删除不必要的括号
-- 优化前
((a AND b) AND c OR (((a AND b) AND (c AND d))))
-- 优化后
(a AND b AND c) OR (a AND b AND c AND d)
2、常量合并
-- 优化前
(a<b AND b=c) AND a=5
-- 优化后
b>5 AND b=c AND a=5
3、常量条件去除
-- 优化前
(b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6)
-- 优化后
b=5 OR b=6
值得注意的是在 MySQL 8.0.14 及更高版本中,对于以上常量的优化发生在准备阶段而不是优化阶段,这有助于简化表连接。
4、删除无效或超出范围的值
-- 表结构如下,有⼀个TINYINT 类型的列 (1byte)
CREATE TABLE t (c TINYINT UNSIGNED NOT NULL);
-- 优化前
SELECT * FROM t WHERE c < 256;
-- 优化后
SELECT * FROM t WHERE 1;
5、优先查询常量表
在MySQL中常量表分为两种类型:
- System表:只包含一行数据的表。通常用于优化查询,因为其数据是固定的,对查询性能影响极小。
- Const表:经过WHERE语句中的限制条件筛选后只包含一行数据的表。这个条件通常是某列等于⼀个常量值,该列上通常有索引,可能是主键或唯⼀键。MySQL可以通过索引快速定位该行。
以下操作都属于常量表操作
-- 单表中⽤主键查询
SELECT * FROM t WHERE primary_key=1;
-- 多表中⽤主键查询
SELECT * FROM t1,t2 WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
在表连接查询中,读取任何其他表之前都先读取所有常量表。
6、优化器的其他优化
- 提前检测无效的常量表达式并返回空结果集。
- 如果不使用GROUP BY或聚合函数(COUNT(), MIN()等),HAVING将与WHERE合并。
- 没有WHERE的单个表上的COUNT(*)直接从MyISAM和MEMORY表的表信息中检索。
7、高效查询示例
sql
-- 统计所有⾏数
SELECT COUNT(*) FROM tbl_name;
-- 聚合函数中使⽤索引列
SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
-- 复合索引时,where条件使⽤索引列1,聚合函数中使⽤其他索引列
SELECT MAX(key_part2) FROM tbl_name WHERE key_part1=constant;
-- 排序时使⽤索引列、复合索引时,按索引列的顺序排序
SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... LIMIT 10;
-- 排序时使⽤索引列,复合索引时,按索引列的顺序排序,可以为不同的列指定排序规则
SELECT ... FROM tbl_name ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;
(二)范围优化
范围查询是非常常见的查询方式,在范围查询的列上建立索引可以有效的提高查询效率。
1、单部索引范围访问
(1)定义
单部索引范围访问的范围条件定义如下:
- 对于 BTREE 和 HASH 索引当使用 =、<=>、IN() 操作符时,索引部分与常量值的比较是一个范围条件。
- 另外对于 BTREE 索引当使用 >、<、>=、<=、BETWEEN、<> 操作符时,索引部分与常量值的比较是⼀个范围条件。
- LIKE 的参数是一个不以通配符开头的常量字符串也是⼀个范围条件。
- 对于所有索引类型,多个范围条件用 OR 或 AND 组合形成⼀个范围条件。
所谓的"常量值"指的是以下情况之一:
- 查询字符串中的常量
- 表连接中 const 表或 system 表中的列
- 由上述类型组成的表达式
具体例子如下:
sql
-- key_col 是表t1中定义的索引列
-- key_col与常量区间进⾏⽐较
SELECT * FROM t1 WHERE key_col > 1 AND key_col < 10;
-- key_col与常量进⾏⽐较并且在⼀个范围中
SELECT * FROM t1 WHERE key_col = 1 OR key_col IN (15,18,20);
-- key_col与字符串常量⽐较并且在开头不是通配符的字符串范围中
SELECT * FROM t1 WHERE key_col LIKE 'ab%' OR key_col BETWEEN 'bar' AND 'foo';
(2)优化过程
MySQL尝试从 WHERE 子句中为每个可能的索引提取范围条件。在提取过程中,不能用于构造范围条件的条件将被删除,产⽣重叠范围的条件将被合并,产生空范围的条件将被删除。
例子如下:
普通的select语句
其中key1是索引列,nonkey未被索引列
SELECT * FROM t1 WHERE
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z');
优化过程:
-- 1. 提取原始的WHERE条件
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z')
-- 2. 删除nonkey = 4和 key1 LIKE '%b' (因为它们不能⽤于范围扫描), 为了防止条件遗漏用TRUE替换这两个条件
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')
-- 3. 合并始终为真或为假的条件(key1 LIKE 'abcde%' OR TRUE) -- 始终为真
(key1 < 'uux' AND key1 > 'z') -- 始终为假
-- 4. ⽤常量替换得到如下条件(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)
-- 5. 删除不必要的TRUE和FALSE常量(key1 < 'abc') OR (key1 < 'bar')
-- 6. 将重叠的间隔组合成⼀个,产⽣⽤于范围扫描的最终条件:(key1 < 'bar')
从优化过程中可见,真正用于范围扫描的条件可能比 WHERE 子句中的条件要少。同时范围条件提取算法可以处理任意深度的嵌套AND/OR构造,最终输出不依赖于 WHERE 子句中条件出现的顺序。
2、多部索引范围访问
(1)定义
多部索引范围访问是单部索引范围条件的扩展,使用复合索引中的多个索引列进行范围查询。
(2)索引前导列
索引前导列通常是指联合索引(复合索引)中最左边的那一列(或几列)。且MySQL 使用联合索引时,遵循一个非常核心的规则:最左前缀法则。
现在有一个以下的联合索引:
sql
ALTER TABLE index_demo ADD INDEX idx_age_gender_name (age, gender, name);
在这个索引中,age 就是前导列(最左列),(age, gender) 是前导列组合。
具体使用场景如下:
只要你查询条件里包含了最左边的 age,索引就能被用上。
WHERE age = 20 -> 走索引(用到了 age)。
WHERE age = 20 AND gender = '男' -> 走索引(用到了 age 和 gender)。
WHERE age = 20 AND gender = '男' AND name = 'Tom' -> 走索引(用到了全部三列)。
WHERE age = 20 AND name = 'Tom' -> 走索引(只用了 age,因为跳过了 gender,name 只能回表后过滤)。
(3)HASH索引
HASH 索引的底层是一个哈希表。当你存入一条数据时,MySQL 会对索引列的值(比如 id = 60000)做一次哈希运算(Hash Function),得出一个哈希值(比如算出来是 8)。然后,MySQL 就把这条数据的内存/磁盘地址存到哈希表里第 8 个位置。
在查询时,如果你查 WHERE id = 60000,MySQL 不用像 B+ 树那样一层一层往下找,而是直接用同样的哈希算法算一下,得到 8,然后直接去第 8 个位置拿数据。
时间复杂度:O(1)(常数级别)。理论上,不管表里有 100 条还是 1 亿条数据,等值查询的耗时几乎是一样的(极快)
使用例子:
sql
CREATE TABLE test_memory (
id INT,
name VARCHAR(20),
INDEX USING HASH (id) -- 显式指定 HASH 索引
) ENGINE = MEMORY;
在多部索引范围访问里,对于 HASH 索引,有以下条件表达式:
key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;
以上 const1 , const2 , ... 都是常量, cmp 是 =、<=>、is NULL 比较运算符之⼀。条件涵盖了所有复合索引列。(也就是说,有N个条件,每个条件对应⼀个N部索引),例如,下面是⼀个由三部分组成的HASH索引的范围条件:
key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'
(4)BTREE索引
在 MySQL 中建索引时,如果不指定 USING HASH,默认建的就是 BTREE 索引。在 MySQL 的 InnoDB 引擎中,虽然建表语句写的是 BTREE,但它底层的真实数据结构其实是 B+树。
BTREE索引可以使⽤ =, <=>, IS NULL, >, <, >=, <=, !=, <>, BETWEEN,
LIKE (参数是一个不以通配符开头的常量字符串),来确定⼀个范围,有如下条件:
key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10
- 当第⼀个条件比较使用 =, <=>, IS NULL 时,优化器使用索引确定单个区间。
- 当某个条件使⽤ >, <, >=, <=, !=, <>, BETWEEN, LIKE 进⾏范围检索时,当前条件使用索引,后续的条件全部不使用索引。
- 进行不等于 !=, <> 运算时,优化器根据分析结果决定是否使⽤索引,如果全表扫描效率更高,则不使用索引。如果使用索引将会得到两个区间
以上的条件表达式由于key_part2 使⽤于 >= ,key_part3不再使用索引,可表示为:
('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)
在条件表达式中使用OR,如果条件所覆盖的区间不同,将使用两个区间的并集,如下面所示
-- 条件表达式
(key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5)
-- 条件区间
(1,-inf) < (key_part1,key_part2) < (1,2)
(5,-inf) < (key_part1,key_part2)
(三)索引合并优化
1、定义
多条件的查询语句中where查询通常会包含多个条件判断,以AND或OR操作进行连接。在对一个表进行查询时最多只能利用该表上的⼀个索引,其他条件需要在回表查询时进行判断(不考虑覆盖索引的情况)。当回表的记录数很多时,需要进行大量的随机IO,这可能导致查询性能下降。此时要用索引合并(Index Merge)来解决该问题。
索引合并是通过对⼀个表同时使用多个索引进行条件扫描,并将满足条件的多个主键集合取交集或并集后再进行回表,可以提升查询效率。索引合并可以检索多个范围扫描的数据行,并将结果合并为一个。索引合并仅合并单个表中的索引扫描,而不能跨表扫描。合并的结果是多个索引扫描的并集、交集或交集的并集。
值得注意的是索引合并不使用于全文索引。
索引合并会在 EXPLAIN 输出中索引合并在 type 列中显示为 index_merge 。在Extra字段中显示索引合并的具体算法。
示例:
sql
-- 查询1
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;
-- 查询2
SELECT * FROM tbl_name WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;
2、交叉访问算法 Using intersect
索引合并交叉访问算法使用于将⼀个 WHERE 子句中的条件转换为多个不同索引的范围条件并结合 AND 一起使用。该算法使用的索引执行同步扫描,并返回各索引扫描结果的交集。
-- ⼆级索引只能使⽤等值查询;key_part部分为索引列,条件个数正好与所有索引列匹配,不能只
是部分
key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN
-- 使⽤主键的任何范围条件
primary_key < const
例子:
sql
-- ⼆级索引是等值查询
SELECT * FROM tbl_name WHERE key1_part1 = 1 AND key1_part2 = 2 AND key2 = 2;
-- 主键索引可以使⽤范围查询
SELECT * FROM innodb_table WHERE primary_key < 10 AND key_col1 = 20;
3、联合访问算法 Using union
索引合并联合访问算法使用于将⼀个 WHERE 子句中的条件转换为多个不同索引的范围条件并结合 OR ⼀起使用。该算法使用的索引执行同步扫描,并返回各索引扫描结果的并集。
-- ⼆级索引只能使⽤等值查询;key_part 部分为索引列,条件个数正好与所有索引列匹配,不能
只是部分
key_part1 = const1 OR key_part2 = const2 ... OR key_partN = constN
-- 在InnoDB表中使⽤主键的任何范围条件
primary_key < const
例子:
sql
-- 多个单列普通索引使⽤OR
SELECT * FROM t1 WHERE key1 = 1 OR key2 = 2 OR key3 = 3;
-- 多个普通索引条件过滤之后使⽤OR
SELECT * FROM innodb_table WHERE (key1 = 1 AND key2 = 2) OR (key3 = 'foo' AND key4 = 'bar') AND key5 = 5;
4、排序联合算法 Using sort_union
联合访问算法只实现了对⼆级(普通)索引进行等值查询,但更多的时候需要对⼆级索引进行范围查找。所以索引合并排序联合算法支持了⼆级索引的范围查找。
根据索引查询得到主键集合,对于每个主键集合进行排序,然后求并集,这样做的好处是扩展了使用条件,增加了使用的范围;缺点就是消耗更大了。
例子:
sql
-- ⼆级索引不要求等值查询
SELECT * FROM tbl_name WHERE key_col1 < 10 OR key_col2 < 20;
-- 使⽤复合索引时不要求所有列都匹配索引
SELECT * FROM tbl_name WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col = 30;
(四)索引下推优化
当使用索引进行范围或不以%开头的模糊查询时,会先过滤出⼀个范围,如果没有索引下推则需要根据主键回表后再判断其他的where条件。
如果使用复合索引且其他的条件可以用复合索引中的列判断,则在存储引擎层就进行过滤,而不用回表查询后再过滤,最终会减少回表的次数,以提升效率。
(五)外连接优化
我们知道MySQL中外连接包括 LEFT JOIN 和 RIGHT JOIN 。
对于一个左外连接, A LEFT JOIN B join_specification , MySQL在实现的过程中有如下规则:
- 表B依赖于表A以及表A依赖的所有表。
- 表A依赖于在 LEFT JOIN 条件中使⽤的所有表(表B除外)。
- LEFT JOIN 条件用于决定如何从表b中检索数据行(不使用WHERE子句中的任何条件)。
- 执行所有标准连接优化,先读取依赖的表再读取当前表, 如果存在循环依赖, 则报错。
- 执行所有标准的 WHERE 优化。◦ 如果表A中有一行匹配WHERE条件,但表B中没有匹配条件的数据行,则生成⼀个额外的B行,所有列都设置为NULL。
RIGHT JOIN 的实现类似于 LEFT JOIN ,只是表的顺序不同。在执行的过程中右连接会被转换为等效的左连接。
对于外连接来说,表的连接顺序不能随意交换,约束条件也不能随意的下推。如果可以将外连接转换为内连接,那么就可以简化查询优化过程。
所以外连接在转为内连接需要有 "严格的约束条件",并且这些约束条件引用了外连接中可能会补空值的一侧(即"可空侧")的表中的列。这种情况下,这些约束条件能够将可空侧产生的空值过滤掉,使得最终结果等同于内连接的结果。
例子:
sql
-- 查询所有参加考试的学⽣成绩,加⼊了WHERE IS NOT NULL 过滤了右表中的NULL记录
select * from student stu left join score s on stu.id = s.student_id where s.score is not null;
-- 查询所有分数⼤于80分以上的成绩,加⼊了WHERE s.score > 80 过滤了右表中的NULL记录
select * from student stu left join score s on stu.id = s.student_id where s.score > 80;
(六)IS NULL 优化
MySQL可以对 col_name IS NULL 执行与 col_name = constant_value 相同的优化。例如,MySQL可以使用索引和范围用 IS NULL 来搜索 NULL。
-- 以下SQL中使用NULL作为条件
SELECT * FROM tbl_name WHERE key_col IS NULL;
SELECT * FROM tbl_name WHERE key_col <=> NULL;
SELECT * FROM tbl_name WHERE key_col=const1 OR key_col=const2 OR key_col IS NULL;
要注意的是如果 WHERE 子句对 NOT NULL 的列的添加 col_name IS NULL 条件,则该表达式将被优化掉。所以建议在建表的时候为非空的列指定NOT NULL约束。
例子:id为主键,在列a和列b上建立索引
sql
drop table if exists t1;
create table t1 (
id bigint primary key auto_increment,
a varchar(10) not null,
b varchar(10),
c varchar(10),
index (a),
index (b)
);
show index from t1;
insert into t1 values (null, 'aaa','bbb', 'ccc');
insert into t1 values (null, '111', null, null);
-- 主键不可能为空,执⾏计划中显⽰ Impossible WHERE 表⽰当前条件永远不可能为真
select * from t1 where id is null;
-- ⾮空列不可能为空,执⾏计划中显⽰ Impossible WHERE
select * from t1 where a is null;
-- 条件组合中存在is null 也会被优化掉
select * from t1 where a = 'aaa' or a is null;
-- 查询 b列,explain中显⽰使⽤了索引下推
select * from t1 where b is null;
-- 查询 c列,explain中显⽰使⽤全表扫描
select * from t1 where c is null;
(七)ORDER BY 优化
1、使用索引进行 ORDER BY
在对查询结果进行ORDER BY排序时,可能会使用到索引,由于索引是有序的,使用索引可以避免额外的排序。
以下为用索引进行 ORDER BY的常见场景:
- 在表中建立复合索引 (key_part1, key_part2) ,以下的查询可以使用索引来解析ORDER BY部分。排序时直接使用索引列,可能使用索引避免额外排序。
SELECT * FROM t1 ORDER BY key_part1, key_part2;
- key_part1 是常数时,通过索引访问的所有行都按照key_part2排序 。
SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2;
- ORDER BY 对索引列进行升序或降序排序时,也可能使用索引避免额外排序;两列可以按同一方向排序(都是ASC,或者都是DESC),也可以按相反方向排序(一个ASC,一个DESC) 。在DESC排序时,索引进行反向扫描。
-- 同是升序
SELECT * FROM t1 ORDER BY key_part1 ASC, key_part2 ASC;
-- 同是降度
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
-- 一个降序一个升序
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;
- key_part1与常量比较,并使用key_part1排序,可能使用索引避免额外排序。可能使用索引避免额外排序指的是如果查询列表中有不在索引中的列,而造成回表查询的效率比直接使用索引排序的效率高,优化器不使用索引排序,是否使用索引避免额外排序由优化器决定;有回表查询时如果WHERE足够充分,让回表查询的范围更小,使用索引的效率更高,那就会使用索引避免额外排序。
SELECT * FROM t1 WHERE key_part1 > constant ORDER BY key_part1 ASC;
SELECT * FROM t1 WHERE key_part1 < constant ORDER BY key_part1 DESC;
- 在InnoDB表中,由于每个索引都会包含主键,当查询列表中只包含主键和索引列时,ORDER BY时直接使用索引,避免额外排序。
SELECT pk, key_part1, key_part2 FROM t1 ORDER BY key_part1, key_part2;
以下情况MySQL不使⽤索引来处理ORDER BY,但可以使用索引作为WHERE条件。
- 对不同的索引使用ORDER BY
-- key1, key2是两个不同的普通索引
SELECT * FROM t1 ORDER BY key1, key2;
- 不符合最左原则
SELECT * FROM t1 ORDER BY key1_part1, key1_part3;
- WHERE中的索引与ORDER BY 的索引不一致
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
- ORDER BY中对索引列进行转换和运算
SELECT * FROM t1 ORDER BY ABS(key);
SELECT * FROM t1 ORDER BY -key;
2、使⽤filesort进行 ORDER BY
如果ORDER BY不能使⽤索引避免额外的排序,MySQL将执行filesort文件排序操作。 文件排序会用到额外的内存空间,可以通过 sort_buffer_size 来设置排序内存的大小,默认大小为 256KB ,可以通过选项文件修改。
--在mysqld节点下设置sort_buffer_size
mysqld
sort_buffer_size = 16M
也可以用以下命令查看sort_buffer_size的大小
sql
SHOW VARIABLES LIKE 'sort_buffer_size';
如果结果集太大超出 sort_buffer_size 内存限制,则文件排序会根据需要使用临时磁盘文件,这样会严重影响查询效率。且如果EXPLAIN输出的Extra列包含 Using filesort 时,就说明查询语句执行了文件排序,需要优化。
(八)GROUP BY 优化
在执行GROUP BY分组查询时,会先把符合WHERE条件的结果保存在⼀个新创建的临时表中,临时表中每个分组字段的所有行都是连续的,然后再分离每个组并应用聚合函数(如果有的话)。由于索引本身就是连结的,通过使用索引可以避免创建临时表。
例子:
sql
-- 新建表并创建索引
create table t2 (
c1 varchar(10),
c2 varchar(10),
c3 varchar(10),
index idx_c1_c2 (c1, c2)
);
处于以下分组情况时查询可以使用索引。
- 分组时使用索引列并遵循最左原则
-- 分组时使⽤索引列并遵循最左原则
SELECT c1, c2 FROM t2 GROUP BY c1, c2;
- 对同⼀个索列使用MIN()或MAX()
-- 对同⼀个索列使用MIN()或MAX()
SELECT c1, MIN(c2), MAX(c2) FROM t2 GROUP BY c1;
- 如果使⽤其他聚合函数,必须加DISTINCT
-- 如果使⽤其他聚合函数,必须加DISTINCT
SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t2;
-- 对两个列进⾏COUNT(),必须加DISTINCT
SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2,c1) FROM t2;
- 查询列表中的列都在GROUP BY中的列之前,之后的索引列在where中引用常量
-- 查询列表中的列都在GROUP BY中的列之前,之后的索引列在where中引用常量
SELECT c1, c2 FROM t2 WHERE c1 < const GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t2 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t2 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t2 WHERE c3 = const GROUP BY c1, c2;
以下分组查询不使用索引。
- 使用了除MIN()或MAX()之外的聚合函数 (优化器会根据索引匹配程序来决定是否使用索引)
-- 使用了除MIN()或MAX()之外的聚合函数
SELECT c1, SUM(c2) FROM t2 GROUP BY c1;
- GROUP BY子句中的列没有遵循索引的最左原则
-- GROUP BY子句中的列没有遵循索引的最左原则
SELECT c2 FROM t2 GROUP BY c2, c3;
(九)DISTINCT 优化
通常DISTINCT被视为GROUP BY的特殊情况,所以GROUP BY查询的优化也适用于DISTINCT子句的查询。例如下面两个查询是等价的:
sql
-- DISTINCT去重后只有⼀⾏数据
SELECT DISTINCT c1, c2, c3 FROM t2 WHERE c1 > const;
-- GROUP BY 分组后,只有⼀⾏数据
SELECT c1, c2, c3 FROM t2 WHERE c1 > const GROUP BY c1, c2, c3;
(十)函数调用优化
MySQL函数分为确定性函数和非确定性函数。多次调用同一个函数并传入相同的参数,如果返回不同的结果,那么这个函数就是非确定性函数。当查询中使用了非确定性函数则无法使用索引,但将非确定性函数的值保存在变量中,然后在查询语句中使用则可以使用索引。
sql
-- POW()是⼀个确定性的函数,查询使⽤索引
SELECT * FROM t WHERE id = POW(1,2);
-- FLOOR()是⼀个没有确定性的函数,查询不使⽤索引
SELECT * FROM t WHERE id = FLOOR(1 + RAND() * 49);
-- ⽤变量接收没有确定性的函数的值
SET @keyval = FLOOR(1 + RAND() * 1);
-- 使⽤索引
EXPLAIN SELECT * FROM t1 WHERE id = @keyval;
二、索引失效
什么是索引失效,简单来说就是设置好了索引,却没所有使用到该索引。mysql判断全表扫描⽐索引快时不使用索引,常发生在数据量极少的表中,或是过滤条件效果不佳时。而索引失效主要会出现在以下的场景中。
(一)复合索引没有遵守最左原则
(二)WHERE中有OR且条件列没有索引
(三)复合索引中第一个使用范围查询的条件或不以%开头的模糊查询之后的列不使用索引
(四)Like 查询以 % 开头
(五)Where 子句中有表达式或函数
将 id列的值+1后再进行条件过滤。
(六)使用<> , not in
三、索引使用原则
为了减少索引失效,提高sql语句的查询效率,我们建议在使用索引时遵循以下原则。
- 每张表必须有主键,推荐BIGINT类型且自增
- DISTINCT,ORDER BY,GROUP BY,JOIN条件,WHERE条件的列加索引
- 对频繁进行数据操作的表,不要建立太多索引,因为维护索引也需要很大的成本
- 使用复合索引时遵守最左原则
- 多表JOIN有确定条件时WHERE class_id = 1,可以分成多个单表查询,然后在程序中合并
- 避免在重复值太多的列上建⽴索引,比如大部分都是相同的值,查了索引之后还会回表,那么还不如全表扫描
- 使用指定的索引use index(index_name), force index(index_name)
- 创建索引时可以指定ASC DESC, create index index_name(col desc, col1 asc);
- 创建一个复合索引(a, b, b),相当于同时创建了(a), (a,b), (a,bc)
- 创建索引之前,确保当前数据库实例没有未提交的大事务,防止数据锁死
- 对不常使使用的索引进行清理
- 删除无用的索引,避免对执行计划造成负面的影响
结语
以上就是MySQL的调优的全部内容啦。如果喜欢我的内容,请点赞、收藏加关注,多多支持我,以及欢迎各位在评论区讨论交流,并指出我的不足,谢谢大家。