索引

什么是索引

索引是一种方便我们高效查找某一列或几列数据的一种数据结构,一般是 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. 解决console控制台反复打印“WebSocket connection to ws://localhost:9528/sockjs-node/107/uadaszgz.websocket failed:Invalid frame header

    element-admin-vue 项目console台一直报websocket连接失败 解决办法 1.vue.config.js中配置devServer.proxy的ws为false  (我没成功) ...

  2. File常用的方法操作、在磁盘上创建File、获取指定目录下的所有文件、File文件的重命名、将数据写入File文件

    文章目录 1.基本介绍 2.构造方法 3.常用的方法 4.代码实例 4.1 创建文件和目录(目录不存在) 4.1.1 代码 4.1.2 测试结果 4.2 测试目录存在的情况.直接写绝对的路径名 4.2 ...

  3. Sublime Text - Linux Package Manager Repositories

    Linux Package Manager Repositories http://www.sublimetext.com/docs/linux_repositories.html Sublime T ...

  4. 京东云开发者|经典同态加密算法Paillier解读 - 原理、实现和应用

    摘要 随着云计算和人工智能的兴起,如何安全有效地利用数据,对持有大量数字资产的企业来说至关重要.同态加密,是解决云计算和分布式机器学习中数据安全问题的关键技术,也是隐私计算中,横跨多方安全计算,联邦学 ...

  5. pip 国内源 包管理

    配置国内源 linux配置 修改 ~/.pip/pip.conf 文件,如下,添加了源并修改了默认超时时间 [global] timeout = 3000 index-url = http://mir ...

  6. 定位java程序中占用cpu最高的线程堆栈信息

    找出占用cpu最高的线程堆栈信息 在java编码中,有时会因为粗心导致cpu占用较高的情况,为了避免影响程序的正常运行,需要找到问题并解决.这里模拟一个cpu占用较高的场景,并尝试定位到代码行. 示例 ...

  7. 【JAVA】详解在JAVA中int与Integer的区别以及背后的原因。

    区别 首先我们要明确,这两点之间有什么区别? 主要有以下几点: 数据类型不同:int是基础数据类型,而 Integer是包装数据类型: 默认值不同:int的默认值是 0,而 Integer的默认值是 ...

  8. 网页嵌入zabbix页面(不同域名)

    先来结论: 方案一:绕过身份验证:https://www.cnblogs.com/JaSonS-toy/p/4939805.html(我不是这样实现,可以自行尝试) 方案二: 1.保证请求的ip与请求 ...

  9. C++初阶(类的访问权限以及封装+this指针+构造函数+析构函数+拷贝构造函数+参数列表+友元+内部类)

    面向过程与面向对象 C语言是面向过程的,关注的是过程(函数),分析出求解问题的步骤,通过函数调用逐步解决问题. C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成. ...

  10. dafny : 微软推出的形式化验证语言

    dafny是一种可验证的编程语言,由微软推出,现已经开源. dafny能够自我验证,可以在VS Code中进行开发,在编辑算法时,写好前置条件和后置条件,dafny验证器就能实时验证算法是否正确. 在 ...