索引优化

文章目录

  • 索引优化
    • 概述
    • 语法
    • Explain
      • select
      • select_type
      • type
      • possible_keys和key
      • key_len
      • ref
      • rows
      • filtered
      • Extra
      • 总结
    • 常见的优化手段
      • 单表使用
      • 小结
      • 关联查询
      • 子查询
      • 排序分组
      • 覆盖索引
    • 总结

索引优化

概述

性能下降SQL 慢,执行时间长,等待时间长

  1. 数据过多(单表500w):分库分表
  2. 关联太多的表,太多 join:SQL 优化
  3. 没有充分利用索引:索引建立
  4. 服务器调优及各个参数设置:调整 my.cnf

索引

索引是帮助MySQL高效获取的数据结构。

MySQL的 BTree 索引使用的是B树中的 B+Tree。

B树和B+树的区别

  • B- 树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;
  • B+ 树的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
  • B+树的磁盘读写代价更低 ; B+树的查询效率更加稳定

聚簇索引

聚簇索引即是主键索引,其它辅助索引则为非聚簇索引。

  • MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为非聚簇索引
  • InnoDB: 其数据文件本身就是索引文件。索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为聚簇索引(或聚集索引)。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。

一二级索引

  • 一级索引为主键索引,其它索引为二级索引。
  • 二级索引的叶子节点包含的用户记录由索引列 + 主键组成,存在回表操作
  • 二级索引不走回表情况(索引字段大于等于查询字段)则为覆盖索引

索引优化_第1张图片

语法

创建

CREATE  [UNIQUE]  INDEX [indexName] ON table_name(column)
CREATE  INDEX idx_customer_name ON customer(customer_name); 
CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no); 
CREATE  INDEX idx_no_name ON customer(customer_no,customer_name); 
 # 随表一起建索引 
CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),
  PRIMARY KEY(id),
  KEY (customer_name), # 单值索引
  UNIQUE (customer_name), # 唯一索引
  KEY (customer_no,customer_name) # 复合索引
);

删除

DROP INDEX [indexName] ON mytable; 

查看

SHOW INDEX FROM table_name\G

使用 Alter

# 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list)
#  这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list)
#  添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD INDEX index_name (column_list)
#  该语句指定了索引为 FULLTEXT ,用于全文索引。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list)

哪些情况需要创建索引?

  • 主键自动建立唯一索引
  • 频繁作为查询条件的字段应该创建索引
  • 查询中与其它表关联的字段,外键关系建立索引
  • 单键/组合索引的选择问题, 组合索引性价比更高
  • 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  • 查询中统计或者分组字段

哪些情况不要创建索引?

  • 表记录太少
  • 经常增删改的表或者字段
  • Where 条件里用不到的字段不创建索引
  • 过滤性不好的不适合建索引

Explain

使用 EXPLAIN 关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。

我们来实操一下:Explain + SQL语句

mysql> explain select * from t1 join t2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                 |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | NULL                                  |
|  1 | SIMPLE      | t2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+
2 rows in set, 1 warning (0.00 sec)

主要字段总览:执行计划字段

列名 描述
id 在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_type SELECT关键字对应的那个查询的类型
table 表名
partitions 匹配的分区信息
type 针对单表的访问方法
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 实际使用到的索引长度
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比
Extra 一些额外的信息

select

select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序

  • id相同,执行顺序由上至下
  • id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  • id相同不同,同时存在

我们写的查询语句一般都以SELECT关键字开头,比较简单的查询语句里只有一个SELECT关键字,但是下边两种情况下在一条查询语句中会出现多个SELECT关键字:

  • 查询中包含子查询的情况
  • 查询中包含UNION语句的情况

查询语句中每出现一个SELECT关键字,MySQL就会为它分配一个唯一的id值。这个id值就是EXPLAIN语句的第一个列。对于连接查询来说,一个SELECT关键字后边的FROM子句中可以跟随多个表,所以在连接查询的执行计划中,每个表都会对应一条记录,但是这些记录的id值都是相同的。

id号每个号码,表示一趟独立的查询。一个sql 的查询趟数越少越好。

查询优化器可能对涉及子查询的查询语句进行重写,从而转换为连接查询。所以如果我们想知道查询优化器对某个包含子查询的语句是否进行了重写,直接查看执行计划就好了

mysql> explain select * from t1 where a in (select a from t2);
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref        | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL       |    8 |   100.00 | NULL        |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | luban.t1.a |    1 |   100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

可以看到,虽然我们的查询语句是一个子查询,但是执行计划中 t1 和 t2 表对应的记录的 id 值全部是1,这就表明了查询优化器将子查询转换为了连接查询

UNION会把多个查询的结果集合并起来并对结果集中的记录进行去重(使用的是内部的临时表)

mysql> explain select * from t1 union select * from t2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type  | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
|  1 | PRIMARY      | t1         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | NULL            |
|  2 | UNION        | t2         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | NULL            |
| NULL | UNION RESULT | <union1,2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL |     NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)

正如上边的查询计划中所示,UNION子句是为了把 id 为1的查询和 id 为2的查询的结果集合并起来并去重,所以在内部创建了一个名为的临时表(就是执行计划第三条记录的table列的名称),id 为 NULL 表明这个临时表是为了合并两个查询的结果集而创建的。

UNION ALL 就不需要为最终的结果集进行去重,它只是单纯的把多个查询的结果集中的记录合并成一个并返回给用户,所以也就不需要使用临时表。所以在包含UNION ALL子句的查询的执行计划中,就没有那个id为NULL的记录

select_type

查询的类型,主要是用于区别 普通查询、联合查询、子查询等的复杂查询。

  • SIMPLE:查询语句中不包含UNION或者子查询的查询都算作是SIMPLE类型。连接查询也算是SIMPLE类型
explain select * from t1;
explain select * from t1 join t2;
  • PRIMARY:对于包含UNION、UNION ALL或者子查询的大查询来说,它是由几个小查询组成的,其中最左边的那个查询的 select_type 值就是 PRIMARY (最外层)
explain select * from t1 where a in (select a from t2) or c = 'c';
  • UNION:对于包含UNION或者UNION ALL的大查询来说,它是由几个小查询组成的,其中除了最左边的那个小查询以外,其余的小查询的 select_type 值就是 UNION。
mysql> explain select * from t1 union select * from t2;
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
| id | select_type  | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra           |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
|  1 | PRIMARY      | t1         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | NULL            |
|  2 | UNION        | t2         | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |   100.00 | NULL            |
| NULL | UNION RESULT | <union1,2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL |     NULL | Using temporary |
+----+--------------+------------+------------+------+---------------+------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
  • UNION RESULT:MySQL选择使用临时表来完成UNION查询的去重工作,针对该临时表的查询的select_type就是UNION RESULT
  • SUBQUERY:非相关子查询,由于select_type为SUBQUERY的子查询由于会被物化,所以只需要执行一遍。
explain select * from t1 where a in (select a from t2) or c = 'c';
  • DEPENDENT SUBQUERY:相关子查询,select_type为DEPENDENT SUBQUERY的查询可能会被执行多次
explain select * from t1 where a in (select a from t2 where t1.a = t2.a) or c = 'c';
  • DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生) MySQL会递归执行这些子查询, 把结果放在临时表里。
explain select * from (select a, count(*) from t2 group by a ) as deliver1;
  • MATERIALIZED:当查询优化器在执行包含子查询的语句时,选择将子查询物化之后与外层查询进行连接查询时,该子查询对应的select_type属性就是MATERIALIZED。
explain select * from t1 where a in (select c from t2 where e = 1);

type

显示查询使用了何种类型,从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL

  • system:当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。
CREATE TABLE t(i int) Engine=MyISAM;
INSERT INTO t VALUES(1);
explain select * from t;
  • const:当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const。
explain select * from t1 where a = 1;
  • eq_ref:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是 eq_ref
 explain select * from t1 join t2 on t1.a = t2.a;
  • ref:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref。
explain select * from t1 where b = 1;
  • ref_or_null:当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是 ref_or_null
explain select * from t1 where b = 1 or b is null;
  • index_merge:索引合并,通常出现在有 or 的关键字的sql中
explain select * from t1 where a = 1 or b  = 1;
  • unique_subquery:如果查询优化器决定将 IN 子查询转换为 EXISTS 子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的 type 列的值就是 unique_subquery。子查询关联唯一索引
explain select * from t1 where c in (select a from t2 where t1.e = t2.e) or a = 1;
  • index_subquery:与 unique_subquery 类似,只不过访问子查询中的表时使用的是普通的索引。
explain select * from t1 where c in (select b from t2 where t1.e = t2.e) or a = 1;
  • range:一般就是在你的where语句中出现了between、<、>、in等的查询
explain select * from t1 where a > 1;
  • index:当我们可以使用覆盖索引,但需要扫描全部的索引记录时,该表的访问方法就是index。
explain select b from t1;
  • ALL:全表扫描

备注:一般来说,得保证查询至少达到range级别,最好能达到ref。

从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL

possible_keys和key

possible_keys:显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。

key实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠

不过有一点比较特别,就是在使用index访问方法来查询某个表时,possible_keys列是空的,而key列展示的是实际使用到的索引。

possible_keys列中的值并不是越多越好,可能使用的索引越多,查询优化器计算查询成本时就得花费更长时间,所以如果可以的话,尽量删除那些用不到的索引

key_len

key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:

  • 对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
  • 如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。
  • 对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。

key_len字段能够帮你检查是否充分的利用上了索引,数字越大,查询一般越快。

ref

当使用索引列等值匹配的条件去执行查询时,也就是在访问方法是const、eq_ref、ref、ref_or_null、unique_subquery、index_subquery其中之一时,ref列展示的就是与索引列作等值匹配的东西是什么,比如只是一个常数或者是某个列。

rows

如果查询优化器决定使用全表扫描的方式对某个表执行查询时,执行计划的rows列就代表预计需要扫描的行数,如果使用索引来执行查询时,执行计划的rows列就代表预计扫描的索引记录行数。

filtered

代表查询优化器预测在这扫描的记录中,有多少条记录满足其余的搜索条件,是百分比。

Extra

Extra列是用来说明一些额外信息的,我们可以通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询语句。

  • No tables used:当查询语句的没有FROM子句时将会提示该额外信息。
explain select 1;
  • Impossible WHERE:查询语句的WHERE子句永远为FALSE时将会提示该额外信息。
explain select b from t1 where 1=0;
  • No matching min/max row:当查询列表处有MIN或者MAX聚集函数,但是并没有符合WHERE子句中的搜索条件的记录时,将会提示该额外信息。
explain select max(a) from t1 where a=100;
  • Using index:当我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用索引覆盖的情况下,在Extra列将会提示该额外信息。
explain select d from t1 where b =1;
  • Using index condition:有些搜索条件中虽然出现了索引列,但却不能使用到索引(在MySQL 5.6版本后加入的新特性)
explain select * from t1 where b =1 and b like '%1';
  • Using where:当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息。
explain select * from t1 where e = 1;
  • Using join buffer (Block Nested Loop):在连接查询执行过程中,当被驱动表不能有效的利用索引加快访问速度,MySQL一般会为其分配一块名叫 join buffer的内存块来加快查询速度。
explain select * from t1 join t2 on t1.e = t2.e;
  • Using filesort:很多情况下排序操作无法使用到索引,只能在内存中(记录较少的时候)或者磁盘中(记录较多的时候)进行排序,这种在内存中或者磁盘上进行排序的方式统称为文件排序(英文名:filesort)。如果某个查询需要使用文件排序的方式执行查询,就会在执行计划的Extra列中显示Using filesort提示。
explain select * from t1 order by e;
  • Start temporary、End temporary:查询优化器会优先尝试将IN子查询转换成 semi-join,而 semi-join又有好多种执行策略,当执行策略为 DuplicateWeedout时,也就是通过建立临时表来实现为外层查询中的记录进行去重操作时,驱动表查询执行计划的 Extra列将显示 Start temporary提示,被驱动表查询执行计划的 Extra列将显示 End temporary提示
explain select * from t1 where a in (select e from t2 where e = 1);
  • FirstMatch(表名):在将In子查询转为semi-join时,如果采用的是FirstMatch执行策略,则在被驱动表执行计划的Extra列就是显示FirstMatch(tbl_name)提示。
explain select * from t1 where a in (select c from t2 where c = 1);

总结

主要字段:id 、type、key_len、rows、Extra

性能按 type 排序

system > const > eq_ref > ref > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

性能按Extra排序

  • Using index:用了覆盖索引
  • Using index condition:用了条件索引(索引下推)
  • Using where:从索引查出来数据后继续用where条件过滤
  • Using join buffer (Block Nested Loop):join的时候利用了join buffer(优化策略:去除外连接、增大join buffer大小)
  • Using filesort:用了文件排序,排序的时候没有用到索引
  • Using temporary:用了临时表(优化策略:增加条件以减少结果集、增加索引,思路就是要么减少待排序的数量,要么就提前排好序);常见于排序 order by 和分组查询 group by。
  • Start temporary, End temporary:子查询的时候,可以优化成半连接,但是使用的是通过临时表来去重
  • FirstMatch(tbl_name):子查询的时候,可以优化成半连接,但是使用的是直接进行数据比较来去重

常见的优化手段

慢查询日志

MySQL 的慢查询日志是 MySQL 提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的SQL,则会被记录到慢查询日志中。

索引优化_第2张图片

  • 使用 SHOW PROCESSLIST 查询所有用户正在干什么;使用 kill [id] 杀进程

插入大量语句优化思路?

通过慢查询日志,针对查询慢的、查询次数多的进行优化

  • sql 语句中拼写多行数据()
  • 插入前删除除主键索引外的索引
  • 关闭事务的主动提交,插入完再提交
  • 多线程

单表使用

全值匹配

EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND deptid=4 AND emp.name = 'abcd';
#全值索引
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME);

索引失效

接下来介绍几种索引失效场景

  • 最左匹配原则:指的是查询从索引的最左前列开始并且不跳过索引中的列。

最左侧字段为 age,所以。。。

 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1 AND emp.name = 'abcd'  ;
  • 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
CREATE INDEX idx_name ON emp(NAME);
  EXPLAIN  SELECT SQL_NO_CACHE * FROM emp WHERE emp.name  LIKE 'abc%' ;#走的range
  EXPLAIN   SELECT SQL_NO_CACHE * FROM emp WHERE LEFT(emp.name,3)  = 'abc';#失效了
  • 匹配列前缀

如果只给出后缀或者中间的某个字符串,是用不到索引的

EXPLAIN  SELECT SQL_NO_CACHE * FROM emp WHERE emp.name  LIKE '%abc%' ;
EXPLAIN  SELECT SQL_NO_CACHE * FROM emp WHERE emp.name  LIKE '%abc' ;
  • 匹配范围值

不能使用索引中范围条件右边的列(emp.name失效),应该把这种字段放到末尾

 EXPLAIN SELECT  SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ; 
 #应该建立
 create index idx_age_name_deptid on emp(age,name,deptid)

不过在使用联合进行范围查找的时候需要注意,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引

  • mysql 在使用不等于(!= 或者<>) 的时候无法使用索引;索引建了也会失效
CREATE INDEX idx_name ON emp(NAME)
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name <>  'abc' 
  • is not null 也无法使用索引,但是is null是可以使用索引的
UPDATE emp SET age =NULL WHERE id=123456; 
EXPLAIN SELECT * FROM emp WHERE age IS NULL  
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL
  • 字符串不加单引号索引失效

建立索引,考虑以下:

  • 考虑索引选择性:是指不重复的索引值(也叫基数,Cardinality)与表记录数的比值
    • 选择性 = 基数 / 记录数
    • 选择性的取值范围为(0, 1],选择性越高的索引价值越大。
  • 考虑前缀索引
    • 用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。
    • 前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引。
SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; -- 0.9007

ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4));

小结

  • 对于单键索引,尽量选择针对当前query选择性更好的索引
  • 在选择组合索引的时候,当前Query中选择性最好的字段在索引字段顺序中,位置越靠前越好。
  • 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
  • 书写sql语句时,尽量避免造成索引失效的情况

关联查询

遵循以下原则

  • 保证被驱动表的join字段已经被索引
  • left join 时,选择小表作为驱动表,大表作为被驱动表。
  • inner join 时,mysql会自己帮你把小结果集的表选为驱动表。
  • 子查询尽量不要放在被驱动表,有可能使用不到索引,虚拟表无法建索引。
  • 能够直接多表关联的尽量直接关联,不用子查询。

示例 left join:给被驱动表(这里为 book)添加索引,给驱动表(这里为 class),无法避免全表扫描

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
# 添加索引优化
ALTER TABLE `book` ADD INDEX Y ( `card`);

子查询

尽量不要使用 not in 或者 not exists

SELECT SQL_NO_CACHE age,count(*)  FROM emp a 
WHERE  id  NOT  IN(SELECT ceo FROM dept b2 WHERE ceo IS NOT NULL)

用 left outer join on xxx is null 替代

EXPLAIN SELECT SQL_NO_CACHE age,count(*) FROM  emp a 
LEFT OUTER JOIN dept b ON a.id =b.ceo
  WHERE  b.ceo IS   NULL

排序分组

  • 无过滤 不索引,必须有过滤条件,where 或者 limit(分页)
explain  select SQL_NO_CACHE * from emp order by age,deptid; 
explain  select SQL_NO_CACHE * from emp order by age,deptid limit 10; 
  • 顺序错,必排序,建索引时,需注意字段顺序

注意:当联合索引左边列的值为常量,也可以使用后边的列进行排序

create index idx_age_deptid_name on emp (age,deptid,name)
explain  select * from emp where age=45 order by deptid; #正常使用索引
explain  select * from emp where age=45 order by  name,deptid; # name在前即可使用索引
explain select * from emp where deptid=45 order by age; # where 即未满足条件
  • 方向反 必排序,要么都升序,要么都降序。
explain select * from emp where age=45 order by  deptid desc, name desc ;#正常
explain select * from emp where age=45 order by  deptid asc, name desc ;#失效

当无法避免 Usingfilesort 时

首先:ORDER BY子句,尽量使用 Index 方式排序,避免使用 FileSort 方式排序

filesort 有两种算法

  • 双路排序:两次扫描磁盘,最终得到数据。读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
  • 单路排序:它把每一行都保存在内存中了,把随机IO变成了顺序IO,但是它会使用更多的空间。从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,避免了第二次读取数据。

优化策略

  • 增大 sort_buffer_size 参数的设置,针对每个进程的 1M-8M之间调整
  • 增大 max_length_for_sort_data 参数的设置,1M-8M之间调整
  • 减少 select 后面的查询的字段。(select * 是大忌)

group by 使用索引的原则几乎跟 order by一致 ,唯一区别是 groupby 即使没有过滤条件用到索引,也可以直接使用索引。

覆盖索引

最后使用索引的手段:覆盖索引

什么是覆盖索引?

简单说就是,select 到 from 之间查询的列 <= 使用的索引列+主键

总结

  1. SQL语句中 IN 包含的值不应过多,不能超过200个,200个以内查询优化器计算成本时比较精准,超过200个是估算的成本,另外建议能用between就不要用in,这样就可以使用range索引了。
  2. SELECT 语句务必指明字段名称:SELECT * 增加很多不必要的消耗(cpu、io、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。
  3. 当只需要一条数据的时候,使用 limit 1
  4. 排序时注意是否能用到索引
  5. 使用 or 时如果没有用到索引,可以改为 union all 或者 union
  6. 如果 in 不能用到索引,可以改成 exists 看是否能用到索引
  7. 使用合理的分页方式以提高分页的效率
  8. 不建议使用 % 前缀模糊查询
  9. 避免在 where 子句中对字段进行表达式操作
  10. 避免隐式类型转换
  11. 对于联合索引来说,要遵守最左前缀法则
  12. 必要时可以使用 force index 来强制查询走某个索引
  13. 对于联合索引来说,如果存在范围查询,比如 between,>,< 等条件时,会造成后面的索引字段失效。
  14. 尽量使用 inner join,避免 left join,让查询优化器来自动选择小表作为驱动表
  15. 必要时刻可以使用 straight_join 来指定驱动表,前提条件是本身是 inner join

你可能感兴趣的:(MySQL)