索引

什么是索引

索引是一种方便我们高效查找某一列或几列数据的一种数据结构,一般是 B+树或者 hash树。想象一下在一个表中有一列是我们经常需要用于作为查询条件的列,也就是它经常出现在 where 子句中,那么如果每次用到它都要顺序遍历全表数据来找到我们所需要的那一行,听着好像效率不太高的样子,所以就出现了索引这个东西。

因为索引一般是使用树这种数据结构来存储的,而树是对排序很友好的一种数据结构,例如一个二叉树,左边都是比根小的而右边都是比根大的,要查找一个数据就很容易。所以有了索引之后就可以增加检索的效率,大大缩短查找时间。

索引的创建与删除

创建索引

可以在创建表的时候一起创建索引,也可以在建完表之后单独创建

在建表的时候创建索引:

CREATE TABLE `tb` (
`tid` int(3) NOT NULL,
`tname` varchar(10) DEFAULT NULL,
`test_column` int(3) DEFAULT NULL,
PRIMARY KEY (`tid`),
KEY `name_index` (`tname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

以上语句创建了一个名为 tb 的表(表创建完成之后可以通过以下SQL语句来查看创建该表所需要的SQL语句:

show create table 表名;

我们创建了 tb 表,并指定了 主键为 tid 字段,在 tname 列创建了一个名为 name_index 的索引,并指定了引擎为 InnoDB、字符编码方式为 utf8mb4。

在建表后通过 alter 语句或 create 语句来创建索引:

alter table 表名 add index 索引名(列1, 列2, 列3...);
create index 索引名 on 表名(列1, 列2, 列3...);

可以对一个或多个列共同添加索引。索引创建完成后可以通过以下语句来查看该表的所有索引信息:

MariaDB [sql_optimize]> show index from tb;
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tb | 0 | PRIMARY | 1 | tid | A | 3 | NULL | NULL | | BTREE | | |
| tb | 1 | name_index | 1 | tname | A | 3 | NULL | NULL | YES | BTREE | | |
+-------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
2 rows in set (0.00 sec)

可以看到主键也是一个索引

删除索引

有两种删除索引的方式:

drop index 索引名 on 表名;
alter table 表名 drop index 索引名;

索引的使用

使用explain分析SQL语句

使用索引的时候有几点需要注意的地方来避免让索引失效,要观察索引是否失效可以通过 explain 语句来查看 SQL 语句的执行情况。

MariaDB [sql_optimize]> explain select * from tb t1 where exists (select t2.tid from tb2 t2 where t1.tid = t2.tid);
+------+--------------+-------------+--------+---------------+---------+---------+---------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+-------------+--------+---------------+---------+---------+---------------------+------+-------------+
| 1 | PRIMARY | <subquery2> | ALL | distinct_key | NULL | NULL | NULL | 1 | |
| 1 | PRIMARY | t1 | eq_ref | PRIMARY | PRIMARY | 4 | sql_optimize.t2.tid | 1 | |
| 2 | MATERIALIZED | t2 | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+------+--------------+-------------+--------+---------------+---------+---------+---------------------+------+-------------+
3 rows in set (0.00 sec)

id

数值越大执行顺序越靠前,数值一样时从上往下顺序执行,在本例中也就是 t2 -> subquery2 -> t1。

select_type

查询类型,取值有SIMPLE(简单查询,不包含子查询或 union)、PRIMARY(主查询,一般出现在有子查询的语句中)等。

table

使用的表,有时候会有一些临时表,比如这里的 subquery2。

type

类型,这个类型和上面的 select_type 不要一样,这个 type 字段可以看成是 SQL 语句执行速度的一个衡量方式,

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

一般来说 system 和 const 是达不到的,eq_ref 也是比较困难的,所以我们一般能达到的是 ref、range 和 index,当然这些都是针对有索引的情况来说的,没有索引的话那就只能是 ALL。

possible_keys 和 key

预测会使用的索引和实际使用的索引

extra

一些额外信息,比较常见的几种有

  • using filesort:需要额外一次排序,常见于有 order by的语句中
  • using temporary:用到了临时表,常见于有 group by 的语句中
  • using index:代表使用了索引
  • using where:意味不明

前两种代表性能消耗较大,是我们需要避免的,如果出现了这两个信息说明我们的 SQL 语句需要优化了,using index 意味着性能有所提升,而 using where 的出现好像很难总结出什么规律,一般不太需要关注它。

最佳左前缀

这个是针对复合索引来说的,也就是一个索引中包含多个列的时候。最佳左前缀的意思是我们使用索引的时候要按照复合索引的顺序来使用,不要跨列,也就是说,如果一个索引的定义是(a,b,c,d),那我们使用的时候就要按照 abc 的顺序来使用。说到这个使用顺序就要提到 SQL 的解析过程了

编写过程:

select dinstinct  ..from  ..join ..on ..where ..group by ...having ..order by ..limit ..

解析过程:

from .. on.. join ..where ..group by ....having ...select dinstinct ..order by limit ...

按照这个解析过程,这样的一条 SQL 语句是符合最佳左前缀的:

select d from tb where a=... and b=... and c=...;

我们同时使用了 abc 这三个字段,并且解析顺序也会是 a -> b -> c -> d

这样的 SQL 语句是不符合最佳左前缀的,它会使得一部分索引失效:

select d from tb where a=... and c=...;

b 列没有使用到,也就是说我们只用了 acd 这三列,跨了 b 列,这条语句会导致 a 后面的索引都失效,也就是只有 a 使用到了索引, c=... 语句并没有使用索引。

举个例子:

MariaDB [sql_optimize]> explain select b4 from test03 where b1=1 and b2=1 and b3=1;
+------+-------------+--------+------+-------------------+-------------------+---------+-------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+------+-------------------+-------------------+---------+-------------------+------+-------------+
| 1 | SIMPLE | test03 | ref | index_b1_b2_b3_b4 | index_b1_b2_b3_b4 | 14 | const,const,const | 1 | Using index |
+------+-------------+--------+------+-------------------+-------------------+---------+-------------------+------+-------------+
1 row in set (0.00 sec)
MariaDB [sql_optimize]> explain select b4 from test03 where b1=1 and b3=1;
+------+-------------+--------+------+-------------------+-------------------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+------+-------------------+-------------------+---------+-------+------+--------------------------+
| 1 | SIMPLE | test03 | ref | index_b1_b2_b3_b4 | index_b1_b2_b3_b4 | 4 | const | 1 | Using where; Using index |
+------+-------------+--------+------+-------------------+-------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

可以看到第二个 SQL 语句中跨了 b2 列,所以 index_b1_b2_b3_b4 部分失效了(索引是否部分失效可以通过 key_len 字段看出来)。

索引覆盖

覆盖索引(covering index ,或称为索引覆盖)即从非主键索引中就能查到的记录,而不需要查询主键索引中的记录,避免了回表的产生减少了树的搜索次数,显著提升性能。

尽量不要使用 SELECT *语句,因为这样会发生回表查询不能使用索引覆盖从而导致查询效率低。观察以下两条 SQL 语句,一个是 SELECT * 一个是只选择需要的列:

MariaDB [sql_optimize]> explain select * from test02 where a4=4 and a6=4;
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
| 1 | SIMPLE | test02 | ref | index_a4,index_a4_a6 | index_a4 | 5 | const | 1 | Using where |
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
1 row in set (0.00 sec)
MariaDB [sql_optimize]> explain select a4,a6 from test02 where a4=4 and a6=4;
+------+-------------+--------+------+----------------------+-------------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+------+----------------------+-------------+---------+-------------+------+-------------+
| 1 | SIMPLE | test02 | ref | index_a4,index_a4_a6 | index_a4_a6 | 10 | const,const | 1 | Using index |
+------+-------------+--------+------+----------------------+-------------+---------+-------------+------+-------------+
1 row in set (0.00 sec)

可以看到使用SELECT *的语句执行时没有走复合索引(即 index_a4_a6,这是由 a4 和 a6 功能组成的一个复合索引),而是走了 index_a4 这个只有 a4 组成的索引,而使用 SELECT a4, a6的语句则走了复合索引,因为整条SQL 语句就只用到了 a4 和 a6 这两列,这两列在index_a4_a6 存储了,所以不需要回表查询,查一次这个复合索引就可以拿到结果了,而前面的SELECT *语句还需要回表查询那些索引里没有字段。所以说尽量不要使用SELECT *,需要用到什么字段就 select 什么字段,避免索引覆盖失效同时也可以减少 IO 消耗。

避免对索引列进行额外运算

对索引进行额外的运算(加减乘、类型转换等)会导致索引失效:

MariaDB [sql_optimize]> explain select a4 from test02 where a4*2=4;
+------+-------------+--------+-------+---------------+----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+-------+---------------+----------+---------+------+------+--------------------------+
| 1 | SIMPLE | test02 | index | NULL | index_a4 | 5 | NULL | 4 | Using where; Using index |
+------+-------------+--------+-------+---------------+----------+---------+------+------+--------------------------+
1 row in set (0.00 sec)
MariaDB [sql_optimize]> explain select a4 from test02 where a4=4;
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
| 1 | SIMPLE | test02 | ref | index_a4,index_a4_a6 | index_a4 | 5 | const | 1 | Using index |
+------+-------------+--------+------+----------------------+----------+---------+-------+------+-------------+
1 row in set (0.00 sec)

可以看到 type 从ref 退化成了 index,并且 row 是 4 说明发生了回表查询(test02 表中一共4条数据)。

SQL语句优化

前面我们已经说了索引的重要性了,所以 SQL 优化的很大一部分就是索引的优化,当然还有一些其他的优化原则,这就是我们本节要讲的东西。

小表驱动大表

这个原则不只是写 SQL 语句需要遵循,我们平时写代码的时候也要尽量遵循这个原则。比如写双层 for 循环的时候,尽量把循环次数小的那个 for 放在外层而循环次数多的放在内层,这样就可以减少从内外侧循环切换的次数,减少一些性能消耗。

举个例子,两个 for 循环,一个要循环10次一个要循环100次,当然不管两个循环怎么组合最终都是一共要循环1000次,但是如果把循环10次的放在外层,那么就从外层循环跳到内层循环的次数就只要10次,反之要100次。所以把循环次数少的那个 for 循环放在外面可以减少栈帧的切换次数从而提升性能。

回到 SQL 场景中就是当存在子查询的时候,把数据量大的表放在子查询里而数据量小的表放在主查询里。当然可能有的场景下我们就是必须得把大表放在主查询里,因为我们需要的字段在大表里,那么这时候我们就可以使用 existsin 这两个关键词来做一些转换来提升 SQL 语句的效率了:

首先说一下 inexists的区别:

  • in: 先查子查询,查出结果后和主查询做笛卡尔积,子查询只查一次。
  • exists: 先查主查询,然后每次进行主查询的时候都会遍历一遍子查询表,也就是说子查询执行次数为主查询表中的数据量n。

假设现在t1为小表,t2为大表

小表在外层时

正例:

select * from t1 where exists(select id from t2 where id=t1.id);

反例:

select * from t1 where id in (select id from t2);

大表在外层时

正例:

select * from t2 where id in (select id from t1);

反例:

select * from t2 where exists(select id from t1 where id=t2.id);

总结起来就是 in后面跟小表,exists后面跟大表

[MySQL] 索引的使用、SQL语句优化策略的更多相关文章

  1. [转]mysql大表更新sql的优化策略

    看了该文章之后,很受启发,mysql在update时,一般也是先select.但注意,在Read Committed隔离级别下,如果没有使用索引,并不会锁住整个表, 还是只锁住满足查询条件的记录而已. ...

  2. mysql大表更新sql的优化策略(转)

    看了该文章之后,很受启发,mysql在update时,一般也是先select.但注意,在Read Committed隔离级别下,如果没有使用索引,并不会锁住整个表, 还是只锁住满足查询条件的记录而已. ...

  3. SQL语句优化、mysql不走索引的原因、数据库索引的设计原则

    SQL语句优化 1 企业SQL优化思路 1.把一个大的不使用索引的SQL语句按照功能进行拆分 2.长的SQL语句无法使用索引,能不能变成2条短的SQL语句让它分别使用上索引. 3.对SQL语句功能的拆 ...

  4. 浅谈mysql配置优化和sql语句优化【转】

    做优化,我在这里引用淘宝系统分析师蒋江伟的一句话:只有勇于承担,才能让人有勇气,有承担自己的错误的勇气.有承担错误的勇气,就有去做事得勇气.无论做什么事,只要是对的,就要去做,勇敢去做.出了错误,承担 ...

  5. SQL语句优化 -- 以Mysql为例

     本文参考下面的文章:    1: [真·干货]MySQL 索引及优化实战 2:  Mysql语句的执行过程 3:  sql优化的几种方法 我将  sql语句优化分为三个方面,(此处不包括 业务逻辑的 ...

  6. MySQL之SQL语句优化

    语句优化 即优化器利用自身的优化器来对我们写的SQL进行优化,然后再将其放入InnoDB引擎中执行. 条件简化 移除不必要的括号 select * from x where ((a = 5)); 上面 ...

  7. 点评阿里JAVA手册之MySQL数据库 (建表规约、索引规约、SQL语句、ORM映射)

    下载原版阿里JAVA开发手册  [阿里巴巴Java开发手册v1.2.0] 本文主要是对照阿里开发手册,注释自己在工作中运用情况. 本文内容:MySQL数据库 (建表规约.索引规约.SQL语句.ORM映 ...

  8. MYSQL SQL语句优化

    1.EXPLAIN 做MySQL优化,我们要善用EXPLAIN查看SQL执行计划. 下面来个简单的示例,标注(1.2.3.4.5)我们要重点关注的数据: type列,连接类型.一个好的SQL语句至少要 ...

  9. MySQL常用SQL语句优化

    推荐阅读这篇博文,索引说的非常详细到位:http://blog.linezing.com/?p=798#nav-3-2 在数据库日常维护中,最常做的事情就是SQL语句优化,因为这个才是影响性能的最主要 ...

  10. MySQL基础操作&&常用的SQL技巧&&SQL语句优化

    基础操作     一:MySQL基础操作         1:MySQL表复制             复制表结构 + 复制表数据             create table t3 like t ...

随机推荐

  1. IOC常用的创建对象方式

    通过无参构造方法来创建 1.User.java public class User { private String name; public User() { System.out.println( ...

  2. flutter系列之:builder为构造器而生

    目录 简介 Builder StatefulBuilder LayoutBuilder 总结 简介 flutter中有很多种Builder,虽然所有的builder都是构造器,但是不同的builder ...

  3. 齐博x1服务器性能太差,调整系统升级每次校验的文件数

    系统升级需要校验本地的文件是否被修改过,系统默认每次检验1千个文件,一般来说需要分四到五页来处理,如下图所示. 如果你的服务器性能太差的话,就需要手工把数值调小.把下面的代码复制出来.进入后台数据库管 ...

  4. LabVIEW开放神经网络交互工具包【ONNX】,大幅降低人工智能开发门槛,实现飞速推理

    前言 前面给大家介绍了自己开发的LabVIEW AI视觉工具包,后来发现有一些onnx模型无法使用opencv dnn加载,且速度也偏慢,所以就有了今天的onnx工具包,如果你想要加载更多模型,追求更 ...

  5. iptables和firewalld基础

    1.四表五链概念: filter表 过滤数据包 Nat表 用于网络地址转换(IP.端口) Mangle表 修改数据包的服务类型.TTL.并且可以配置路由实现QOS Raw表 决定数据包是否被状态跟踪机 ...

  6. 【Azure 环境】把OpenSSL生产的自签名证书导入到Azure Key Vault Certificate中报错

    问题描述 通过本地生成的自签名证书导入到Azure Key Vault Certificate报错. 错误信息 the specified PEM X.509 certificate content ...

  7. LAPM概述及配置

    一.LAMP概述 1.1LAMP的概念 LAMP架构是目前成熟的企业网站应用模式之一,指的是协同工作的一整套系统和相关软件,能够提供动态web站点服务及其应用开发环境 LAMP是一个缩写词,具体包括L ...

  8. django启动报错:DisallowedHost at /

    学习django第一天,第一次启动服务就报错,报错内容如下: DisallowedHost at / Invalid HTTP_HOST header: '192.168.116.22:8000'. ...

  9. Typora基本使用

    Typora主要功能介绍 1.语言环境 文件>>>偏好设置>>>系统语言 2.创建另一个编辑页面 ctrl+N 几乎所有软件的新建页面的快捷键都是它 3.保存文件 ...

  10. 关于deepin-wine或wine设置PATH环境变量的方法

    前言 更改wine中PATH变量主要是为了能在 cmd输入一些命令而已,这里你可能会问怎么用cmd? deepin-wine cmd 这样就进入了cmd,而设置PATH 环境变量不能像windows一 ...