MySQL04_索引

112 阅读16分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情


⭐️前面的话⭐️

✉️坚持和努力一定能换来诗与远方!
💭推荐书籍:📚《王道408》,📚《深入理解 Java 虚拟机-周志明》,📚《Java 核心技术卷》
💬算法刷题:✅力扣🌐牛客网
🎈Github
🎈码云Gitee


8. 索引index ⭐️

索引概述、优缺点

索引index是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这

些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

最典型的例子就是查新华字典,通过查找目录快速定位到查找的字。

B + Tree

红黑树

二叉树

B - Tree

image-20220331150549880

备注:上述二叉树索引结构的只是一个示意图,并不是真实的索引结构。

优点:

  • 提高数据检索的效率,降低数据库的IO成本。
  • 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。

缺点:

  • 索引列也是要占用空间的。
  • 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低。

索引结构

我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。

索引InnoDBMyISAMMemory
B+Tree索引支持支持支持
Hash索引不支持不支持支持
R-tree索引不支持支持不支持
Full-text索引5.6版本之后支持支持不支持

二叉树

二叉树缺点:顺序插入时,会形成一个链表,查询性能大大降低。 大数据量情况下,层级较深,检索速度慢。

红黑树:大数据量情况下,层级较深,检索速度慢。

B-Tree(多路平衡查找树)

以一颗最大度数(max-degree)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):

image-20220331151317568

知识小贴士: 树的度数指的是一个节点的子节点个数。

插入 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250 数据为例。

image-20220331151432119

具体动态变化的过程可以参考网站: www.cs.usfca.edu/~galles/vis…

B+Tree

以一颗最大度数(max-degree)为4(4阶)的b+tree为例:

image-20220331151621423

插入 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250 数据为例。

相对于B-Tree区别:

①. 所有的数据都会出现在叶子节点

②. 叶子节点形成一个单向链表

MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺

序指针的B+Tree,提高区间访问的性能。

Hash

哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。

如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。

Hash索引特点:

  • Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,< ,...)
  • 无法利用索引完成排序操作
  • 查询效率高,通常只需要一次检索就可以了,效率通常要高于B+tree索引


存储引擎支持:

在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能,hash索引是存储引擎根据B+Tree索引在指定条件

下自动构建的。

为什么InnoDB存储引擎选择使用B+tree索引结构?(即和另外3种进行对比)

  • 相对于二叉树,层级更少,搜索效率高;
  • 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
  • 相对Hash索引,B+tree支持范围匹配及排序操作;


索引分类

分类含义特点关键字
主键索引针对于表中主键创建的索引默认自动创建,只能有一个primary
唯一索引避免同一个表中某数据列中的值重复可以有多个unique
常规索引快速定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个fulltext

(1)主键索引

我们建表的时候,例如下面这个建表语句

 CREATE TABLE `t_blog_sort` (
   `uid` varchar(32) NOT NULL COMMENT '唯一uid',
   `sort_name` varchar(255) DEFAULT NULL COMMENT '分类内容',
   `content` varchar(255) DEFAULT NULL COMMENT '分类简介',
   `create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
   `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间',
   `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态',
   `sort` int(11) DEFAULT '0' COMMENT '排序字段,越大越靠前',
   `click_count` int(11) DEFAULT '0' COMMENT '点击数',
   PRIMARY KEY (`uid`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='博客分类表';

这里面有使用到 PRIMARY KEY (uid),这就是主键索引

(2)唯一索引

唯一索引 类似于普通索引,索引列的值必须唯一

唯一索引和主键索引的区别就是,唯一索引允许出现空值,而主键索引不能为空

 create unique index index_name on table(column)

或者创建表时指定

 unique index_name column

(3) 普通索引

当我们需要建立索引的字段,既不是主键索引,也不是唯一索引

那么就可以创建一个普通索引

 create index  index_name on table(column)

或者创建表时指定

 create table(..., index index_name column)

(4)全文索引

lunce、solr和ElasticSearch就是做全文检索的,里面涉及到了倒排索引的概念,mysql很少使用全文索引。

要用来查找文本中的关键字,不是直接与索引中的值相比较,像是一个搜索引擎,配合 match against 使用,现在只有char,varchar,text上可以创建索引,在数据量比较大时,先将数据放在一个没有全文索引的表里,然后在利用create index创建全文索引,比先生成全文索引在插入数据快很多。

(5)组合索引

目前,在业务不是特别复杂的时候,可能使用一个列作为索引,或者直接采用主键索引即可,但是如果业务变得复杂的时候,就需要用到组合索引,通过对多个列建立索引。

组合索引的用处,假设我现在表有个多个字段:id、name、age、gender,然后我经常使用以下的查询条件

 select * from user where name = 'xx' and age = xx

这个时候,我们就可以通过组合 name 和 age 来建立一个组合索引,加快查询效率,建立成组合索引后,我的索引将包含两个key值

在多个字段上创建索引,遵循最左匹配原则

 alter table t add index index_name(a,b,c);

在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

主键构建的主键索引就是聚集索引;叶子结点挂的数据就是这一行的数据。

如果要针对name字段再建立一个索引,因为聚集索引只会有一个,所以name字段建的索引称为二级索引。叶子结点下面挂的是name

值对应的这一行的的主键值

第七分钟

 select * from user where name = 'arm';
 -- 不会走聚集索引,会走二级索引
 -- A比L小,到Geek,再到Arm,拿到10.
 -- 要获取所有数据,再根据聚集索引查找。这一过程称为回表查询:先走二级索引找到对应的主键值,再根据主键值到聚集索引中拿到这一行的行数据。

聚集索引和二级索引的结构一定要理解,这个在SQL优化中是非常重要的。很多SQL优化策略的底层原理。

看2道思考题:

  1. 一下SQL语句,哪个执行效率高?为什么?(1)
 -- id为主键,name字段创建的有索引
 select * from user where id =10;
 select * from user where name = 'Arm';
  1. InnoDB主键索引的B+tree高度为多高呢?

假设: 一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB的指针占用6个字节的空间,主键即使为bigint,占用字节数为8。

 高度为2:
 n * 8 + (n + 1) * 6 = 16*1024 , 算出n约为 1170 
 1171* 16 = 18736
 高度为3:
 1171 * 1171 * 16 = 21939856
 ​
 2千多万条记录,B+树高度也才为3。效率很高。


索引语法

 -- 创建索引
 create [unique | fulltext] index index_name on table_name(index_col_name,...);
 ​
 -- 查看索引
 show index from table_name;
 ​
 -- 删除索引
 drop index index_name on table_name;

案例:按照下列的需求,完成索引的创建

-- 1. name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
-- 2. phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。
-- 3. 为profession、age、status创建联合索引。
-- 4. 为email建立合适的索引来提升查询效率。

create index idx_user_name on tb_user(name);
create unique index idx_user_phone on tb_user(phone);
create index idx_user_profession_age_status on tb_user(profession,age,status);
create index idx_user_email on tb_user(email);

-- 删除索引
drop index idx_user_email on tb_user;

SQL性能分析

SQL执行频率(查询为主还是增删改为主?)

MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前

数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:

 show global status like 'Com_______';

慢查询日志(优化哪些select语句)(定位执行效率低的SQL)

慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。

MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:

 # 查看当前慢查询日志开关
 show variables like 'slow_query_log';
 # 开启MySQL慢查询日志开关
 slow_query_log=1
 # 设置慢日志的时间为 2秒, 语句执行时间超过 2秒,就会视为慢查询,记录慢查询日志
 long_query_time=2

配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log。

blog.csdn.net/weixin_4067…

 # mac开启慢查询日志: https://www.cnblogs.com/woods1815/p/11829659.html
 mysql> set global slow_query_log='on';
 SET GLOBAL long_query_time = 3;  # 这里需要注意下,long_query_time参数设置后需要下次会话后才生效,当前会话查询还是原来的数值

profile详情(每一条sql的耗时,以及耗时在哪一个阶段)

show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持

profile操作:

 select @@have_profiling;

默认profiling是关闭的,可以通过set语句在session/global级别(会话/全局级别)开启profiling:

 set profiling=1;

执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:

 -- 查看每一条 SQL 的耗时基本情况
 show profiles;
 -- 查看指定query_id的 SQL语句各个阶段的耗时情况
 show profile for query query_id;
 -- 查看指定query_id的 SQL语句cpu的使用情况
 show profile cpu for query query_id;

以上三种方式从执行时间的角度出发。并不能真正评判一条sql的性能。


explain执行计划(重要)

EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

 -- 直接在select语句之前加上关键字explain / desc
 explain select 字段列表 from 表名 where 条件;

EXPLAIN 执行计划各字段含义:(重点字段是type、possible_key、key、key_len)

  • Id:select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)。

  • select_type:表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外

    层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等

  • type:表示连接类型,性能由好到差的连接类型为NULL、system、const(主键或唯一索引)、eq_ref、ref(非唯一性索引)、range、 index、all (全表扫描,性能低)。 (尽量往前优化!)

  • possible_key:显示可能应用在这张表上的索引,一个或多个。

  • Key:实际使用的索引,如果为NULL,则没有使用索引。

  • Key_len:表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下, 长度越短越好 。

  • rows:MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。

  • filtered:表示返回结果的行数占需读取行数的百分比, filtered 的值越大越好。

索引使用(如何正确使用、使用与否)

使用索引

MySQL每次只使用一个索引,与其说 数据库查询只能用一个索引,倒不如说,和全表扫描比起来,去分析两个索引 B+树更耗费时间,所以where A=a and B=b 这种查询使用(A,B)的组合索引最佳,B+树根据(A,B)来排序。

  • 主键,unique字段
  • 和其他表做连接的字段需要加索引
  • 在where 里使用 >, >=, = , <, <=, is null 和 between等字段。
  • 使用不以通配符开始的like,where A like ‘China%’
  • 聚合函数里面的 MIN(), MAX()的字段
  • order by 和 group by字段

何时不使用索引

  • 表记录太少
  • 数据重复且分布平均的字段(只有很少数据的列)
  • 经常插入、删除、修改的表要减少索引
  • text,image 等类型不应该建立索引,这些列的数据量大(加入text的前10个字符唯一,也可以对text前10个字符建立索引)
  • MySQL能估计出全表扫描比使用索引更快的时候,不使用索引

索引何时失效

  • 组合索引为使用最左前缀,例如组合索引(A,B),where B = b 不会使用索引
  • like未使用最左前缀,where A like "%China"
  • 搜索一个索引而在另一个索引上做 order by, where A = a order by B,只会使用A上的索引,因为查询只使用一个索引。
  • or会使索引失效。如果查询字段相同,也可以使用索引。例如 where A = a1 or A = a2(生效),where A=a or B = b (失效)
  • 在索引列上的操作,函数upper()等,or、! = (<>),not in 等

验证索引效率(索引对查询效率提升极大)

 -- 在未建立索引之前,执行如下SQL语句,查看SQL的耗时。-------------------21s
 select * from tb_sku where id = 1;
 select * from tb_sku where id = 1\G;
 select * from tb_sku where sn = '100000003145001';
 -- 针对字段创建索引(1千万数据构建B+树,耗时1分钟11秒)
 create index idx_sku_sn on tb_sku(sn);
 ​
 -- 然后再次执行相同的SQL语句,再次查看SQL的耗时。-------------------0.01s
 select * from tb_sku where sn = '100000003145001';

想到面试题:sql优化之正确使用索引

1 最左前缀法则(针对联合索引)(索引失效或部分失效)

如果索引了多列(联合索引),要遵守最左前缀法则(查询从索引的最左列开始,并且不跳过索引中的列)

如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

最左边的索引要存在,存在位置没有关系。

 explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';(符合最左前缀法则)
 explain select * from tb_user where profession = '软件工程' and age = 31;(符合最左前缀法则)
 explain select * from tb_user where profession = '软件工程';(符合最左前缀法则)
 explain select * from tb_user where age = 31 and status = '0';(跳过了左边的列profession,不符合,索引失效,全表扫描)
 explain select * from tb_user where status = '0';(同上)
 explain select * from tb_user where profession = '软件工程' and status = '0';(索引status部分失效)

范围查询(业务允许的话,尽量使用>=)

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效

 explain select * from tb_user where profession = '软件工程' and age > 31 and status = '0';(status部分索引失效)
 explain select * from tb_user where profession = '软件工程' and age >= 31 and status = '0';(索引未失效)

索引列运算

不要在索引列上进行运算操作, 索引将失效。

 -- 查询手机号最后2位是15
 explain select * from tb_user where substring(phone,10,2) = '15';(索引失效,type=ALL,全表扫描)

字符串不加引号

字符串类型字段使用时,不加引号, 索引将失效。

 explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';
 explain select * from tb_user where phone = 17799990015;

模糊查询

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。

 explain select * from tb_user where profession like '软件%';
 explain select * from tb_user where profession like '%工程';
 explain select * from tb_user where profession like '%工';

or连接的条件

用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。

 explain select * from tb_user where id = 10 or age = 23;
 explain select * from tb_user where phone = '17799990015' or age = 23;

由于age没有索引,所以即使id、phone有索引,索引也会失效。所以需要针对于age也要建立索引。

数据分布影响(is null和is not null是否走索引取决于表中数据的分布)

如果MySQL评估使用索引比全表更慢,则不使用索引。

 select * from tb_user where phone >= '17799990015';
 select * from tb_user where phone >= '17799990015';

SQL提示(告诉MySQL用哪个索引)

SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。

 -- use index:
 explain select * from tb_user use index(idx_user_pro) where profession = '软件工程'; 
 -- ignore index:
 explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程'; 
 -- force index:
 explain select * from tb_user force index(idx_user_pro) where profession = '软件工程'; 

覆盖索引

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *。(因为 * 很容易出现回表扫描)

 explain select id,profession from tb_user where profession = '软件工程' and age = 31 and status = '0';
 explain select id,profession,age,status from tb_user where profession = '软件工程' and age = 31 and status = '0';
 ​
 explain select id,profession,age,status,name from tb_user where profession = '软件工程' and age = 31 and status = '0';
 explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';

知识小贴士:

  • using index condition :查找使用了索引,但是需要回表查询数据
  • using where; using index :查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

 -- 面试题思考
 -- 一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对以下SQL语句进行优化, 该如何进行才是最优方案:
 select id, username, password from tb_user where username = 'itcast';
 -- 建立username, password的联合索引

前缀索引(降低索引的体积,只针对前缀建立索引)

当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影

响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

 create index idx_xxx on table_name(column(n));
 -- 前缀长度: 可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
 select count(distinct email)/count(*) from tb_user;
 select count(distinct substring(email,1,5)/count(*) from tb_user);

单列索引与联合索引

单列索引:即一个索引只包含单个列。

联合索引:即一个索引包含了多个列。

在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引,而非单列索引。

 explain select id, phone, name from tb_user where phone = '17799990010' and name = '韩信';
 -- 必然涉及覆盖索引的问题,会回表查询,因为phone的索引当中必然不包含name字段的值。
 -- 所以,推荐创建联合索引。

多条件联合查询时, MySQL优化器会评估哪个字段的索引效率更高,会选择该索引完成本次查询。

联合索引情况:(最终构建的B+树如下,节点存储phone和name的值)(这一个索引结构已经获取到了想要的数据,覆盖索引,不会回表查询)


索引设计原则

  1. 针对于数据量较大(超过100w),且查询比较频繁的表建立索引
  2. 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引
  3. 尽量选择区分度高(身份证号,反例是性别)的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  4. 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引
  5. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
  6. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
  7. 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

索引总结

 -- 1.概述
 索引是高效获取数据的数据结构 ;
 -- 2.索引结构
 B+ Tree: 所有的数据出现在叶子结点,而且叶子结点形成一个双向链表。
 Hash
 -- 3.索引分类
 主键索引(主键默认会创建的)、唯一索引、常规索引、全文索引
 聚集索引(必须存在,只能有一个。叶子结点挂的是一行row的数据。默认主键索引就是聚集索引。)、二级索引(叶子结点挂的是对应行的主键)
 -- 4.索引语法(创建、查看、删除)
 create [unique] index xxx on xxx(xxx);
 show index from xxx;
 drop index xxx from xxx;
 -- 5.SQL性能分析
 执行频次(查询为主还是增删改为主)、慢查询日志(定位执行比较耗时的sql语句)、profile(监控每一条sql语句的耗时以及具体的时间耗费在哪一个阶段)、explain(使用最多,查看sql语句的执行计划评判sql语句的性能)
 -- 6.索引使用
 联合索引(最重要的是遵循最左前缀法则,防止失效)
 索引失效(索引列上运算、字符串不加引号、前面模糊匹配、or连接一侧没有索引、全表扫描更快)
 SQL提示(指定使用哪个索引)
 覆盖索引(涉及回表查询的问题。所谓覆盖索引是指查询返回的列在索引结构中都包含了。所谓回表查询是指先走二级索引再到聚集索引)
 前缀索引(缩小索引的体积,提高查询的效率)
 单列/联合索引(推荐使用联合索引。理由:性能较高;如果使用得当可以避免回表查询。)
 -- 7.索引性能分析
 表(数据量大,查询频率高)
 字段(查询条件(where)、排序(order by)、分组(group by)后的字段)
 建立设么索引(尽量建立唯一索引,区分度高)(尽量使用联合索引)(尽量使用前缀索引)