索引

什么是索引

索引是一种方便我们高效查找某一列或几列数据的一种数据结构,一般是 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 场景中就是当存在子查询的时候,把数据量大的表放在子查询里而数据量小的表放在主查询里。当然可能有的场景下我们就是必须得把大表放在主查询里,因为我们需要的字段在大表里,那么这时候我们就可以使用 exists 和 in 这两个关键词来做一些转换来提升 SQL 语句的效率了:

首先说一下 in和exists的区别:

  • 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. swoole学习笔记

    一.服务端 0. swoole常用的配置项: daemonize = true 守护进程化 worker_num #swoole配置参数 设置启动的Worker进程数: 如 1 个请求耗时 100ms ...

  2. Java学生管理系统(详解)

    相信大部分人都有接触过这个 Java 小项目--学生管理系统,下面会分享我在做这个项目时的一些方法以及程序代码供大家参考(最后附上完整的项目代码). 首本人只是个初学Java的小白,可能项目中有许多地 ...

  3. 齐博x1关于小程序个性源代码的说明

    系统默认推荐商家小程序使用通用型的源码,即框架套壳iframe形式的.这个灵活性更高.但如果有特殊需求的话,也可以设置个性源码,比如配合uni-app使用,针对不同的小程序就使用不同的uni-app风 ...

  4. etcd实现分布式锁

    转载自:etcd实现分布式锁 当并发的访问共享资源的时候,如果没有加锁的话,无法保证共享资源安全性和正确性.这个时候就需要用到锁 1.需要具备的特性 需要保证互斥访问(分布式环境需要保证不同节点.不同 ...

  5. SpringCloud整合分布式事务Seata 1.4.1 支持微服务全局异常拦截

    项目依赖 SpringBoot 2.5.5 SpringCloud 2020.0.4 Alibaba Spring Cloud 2021.1 Mybatis Plus 3.4.0 Seata 1.4. ...

  6. 二十六、StatefulSet资源控制器

    StatefulSet资源控制器 一.statefulset介绍 StatefulSet 是为了解决有状态服务的问题而设计的资源控制器. 匹配 Pod name ( 网络标识 ) 的模式为:(stat ...

  7. Codeforces Round #831 (Div. 1 + Div. 2) A-E

    比赛链接 A 题解 知识点:数学. \(2\) 特判加 \(7\),其他加 \(3\) 直接偶数. 时间复杂度 \(O(1)\) 空间复杂度 \(O(1)\) 代码 #include <bits ...

  8. Pytest学习

    pytest简介 pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高. 使用介绍 编写规则 测试函数以t ...

  9. 修改Oracle共享池大小

    1.  sysdba登录数据库 [oracle@ufdb165 ~]$ sqlplus /nolog SQL*Plus: Release 11.2.0.4.0 Production on Wed Au ...

  10. vim快捷键及命令大全

    定位光标: G 将光标定位到文本末尾行首 gg 将光标定位到文本启始位置 0 (这个是零)定位到光标所在行行首 $ 定位到光标所在行行尾 数字G 跳转到第n行 移动光标: h 向左移动 l 向右移动 ...