MySQL进阶2 - 索引

MySQL进阶1 - 索引

  • 1. 索引概述
  • 2. 索引结构
    • 2.1 二叉树
    • 2.2 B-Tree(多路平衡查找树)
    • 2.3 B+Tree
    • 2.4 Hash
  • 3. 索引分类
  • 4. 索引语法
  • 5. SQL性能分析
    • 5.1 SQL执行频率
    • 5.2 慢查询日志
    • 5.3 profile
    • 5.4 explain执行计划
      • 5.3.1 EXPLAIN执行计划各字段含义:
  • 6. 索引使用
    • 6.1 最左前缀法则
    • 6.2 范围查询
    • 6.3 索引列运算
    • 6.4 字符串不加引号
    • 6.5 模糊匹配
    • 6.6 or连接的条件
    • 6.7 数据分布影响
    • 6.8 SQL提示
    • 6.9 覆盖索引
    • 6.10 前缀索引
    • 6.11 单列索引与联合索引
  • 7. 索引设计原则

1. 索引概述

介绍

索引(index)是帮助MySQL高效获取数据数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

优缺点

优势 劣势
提高数据检索的效率,降低数据库的IO成本 索引列也是要占用空间的
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 索引大大提高了查询效率,同时也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低

2. 索引结构

MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:

索引结构 描述
B + Tree结构 最常见的索引类型,大部分引擎都支持B+Tree索引
Hash索引 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R+tree(空间索引) 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full - text(全文索引) 是一种通过建立倒排索引,快速匹配文档的方式,类似于Lucene,Solr,ES
索引 InnoDB MyISAM Memory
B + Tree索引 支持 支持 支持
Hash索引 不支持 不支持 支持
R - tree索引 不支持 支持 不支持
Full - text 5.6版本之后支持 支持 不支持

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

2.1 二叉树

MySQL进阶2 - 索引_第1张图片

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

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

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

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

MySQL进阶2 - 索引_第2张图片
Ps:树的度数指的是一个节点的子节点的个数

上图中有4个key,分别是20、30、62、89,其对应5个指针,小于20的对应第一个指针,20-30之间对应第二个指针,30-62之间对应第三个指针,62-89之间对应第四个指针,大于89的对应第五个指针

具体可参考B-Tree Visualization,在该网站中选择Max. Degree = 5,插入以下数据:

100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250

效果如下:

MySQL进阶2 - 索引_第3张图片

● 5阶的B树,每一个节点最多存储4个key,对应5个指针
● 一旦节点存储的key数量达到5,就会裂变,中间元素向上分裂
● 在B树种,非叶子节点和叶子节点都会存放数据

2.3 B+Tree

以一棵最大度数(max-degree)为4(4阶)的B+Tree为例:

MySQL进阶2 - 索引_第4张图片
在B+ Tree Visualization中选择5阶,同样插入以上数据,效果如下:

MySQL进阶2 - 索引_第5张图片

相对于B-Tree区别:

① 所有的数据都会出现在叶子节点
② 叶子节点形成一个单向链表

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

MySQL进阶2 - 索引_第6张图片

2.4 Hash

哈希索引就是采用一定的Hash算法,将键值换算成新的Hash值,映射到对应的槽位上,然后存储在Hash表中。如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了Hash冲突(也称为Hash碰撞),可以通过链表来解决问题

• Hash索引特点

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

• 存储引擎支持

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

3. 索引分类

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

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

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

聚集索引选取规则:

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

4. 索引语法

• 创建索引

CREATE [UNIQUE | FULLTEXT] INDEX index_name ON table_name (index_col_name, …);

•  查看索引

SHOW INDEX FROM table_name;

• 删除索引

DROP INDEX index_name ON table_name;

• 案例演示

-- 创建表tb_user
create table tb_user(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '姓名',
    phone char(11) comment '手机号',
    email varchar(30) comment '邮箱',
    profession varchar(20) comment '专业',
    age int check ( age > 0 and age < 100 ) comment '年龄',
    gender int check ( gender = 1 or gender = 2 ) comment '性别',
    status char(1) comment '状态',
    createtime datetime comment '创建时间'
) comment '信息表';

-- 添加数据
insert into tb_user(name, phone, email, profession, age, gender, status, createtime)
values('安景辉', 13080354567, '[email protected]', '软件工程', 23, 1, 6, '2001-02-02 00:00:00'),
      ('白佳颖', 13097742368, '[email protected]', '通讯工程', 26, 2, 0, '2001-03-05 00:00:00'),
      ('柏凡', 13123456789, '[email protected]', '英语', 25, 1, 2, '2002-03-02 00:00:00'),
      ('陈梦洁', 13245678901, '[email protected]', '工程造价', 27, 2, 0, '2001-07-02 00:00:00'),
      ('曹宇涛', 13356789012, '[email protected]', '软件工程', 22, 1, 1, '2001-04-22 00:00:00'),
      ('董碧云', 13467890123, '[email protected]', '舞蹈', 22, 2, 0, '2001-02-07 00:00:00'),
      ('单景瑞', 13578901234, '[email protected]', '应用数学', 27, 1, 0, '2001-02-08 00:00:00'),
      ('冯彩云', 13689012345, '[email protected]', '化工', 28, 2, 5, '2001-05-23 00:00:00'),
      ('范石磊', 13790123456, '[email protected]', '金属材料', 30, 1, 0, '2001-09-18-00:00:00'),
      ('关海燕', 13801234567, '[email protected]', '机械工程及其自动化', 27, 2, 2, '2001-08-16 00:00:00'),
      ('高少杰', 13912345678, '[email protected]', '无机非金属材料工程', 26, 1, 0, '2001-06-12 00:00:00'),
      ('韩楚仪', 13112345678, '[email protected]', '会计', 23, 2, 0, '2001-05-11 00:00:00'),
      ('郝飞羽', 13212345678, '[email protected]', '工程造价', 32, 1, 1, '2001-04-09 00:00:00'),
      ('贺冰倩', 13312345678, '[email protected]', '应用数学', 25, 2, 2, '2001-04-10 00:00:00'),
      ('黄浩然', 13412345678, '[email protected]', '软件工程', 24, 1, 3, '2001-02-12 00:00:00'),
      ('黄欣茹', 13512345678, '[email protected]', '软件工程', 24, 2, 0, '2001-01-30 00:00:00'),
      ('江逸舟', 13612345678, '[email protected]', '工业经济', 35, 1, 0, '2000-05-03 00:00:00'),
      ('李佳妮', 13712345678, '[email protected]', '化工', 38, 2, 1, '2001-08-08 00:00:00'),
      ('康正乐', 13812345678, '[email protected]', '国际贸易', 30, 1, 0, '2007-03-12 00:00:00'),
      ('刘可颐', 13987654321, '[email protected]', '城市规划', 39, 2, 0, '2001-08-15 00:00:00'),
      ('李海洋', 13012345678, '[email protected]', '城市规划', 40, 1, 0, '2000-04-12 00:00:00'),
      ('马一鸣', 13187654321, '[email protected]', '土木工程', 21, 1, 3, '2002-07-18 00:00:00'),
      ('林泽洋', 13287654321, '[email protected]', '城市园林', 20, 1, 0, '2002-03-10 00:00:00'),
      ('秦芷妍', 13387654321, '[email protected]', '工程造价', 29, 2, 4, '2003-05-26 00:00:00');

-- 查询索引
show index from tb_user;
-- 1.name字段为姓名字段,该字段的值可能会重复,为该字段创建索引
create index idx_user_name on tb_user(name);
-- 2. phone手机号字段的值是非空且唯一的,为该字段创建唯一索引
create unique index idx_user_phone on tb_user(phone);
-- 3. 为profession、age、status创建联合索引
create index idx_user_pro_age_sta on tb_user(profession, age, status);
-- 4. 为email建立合适的索引来提升查询效率
create index idx_user_email on tb_user(email);

-- 删除索引
drop index idx_user_name on tb_user;
drop index idx_user_pro_age_sta on tb_user;
drop index idx_user_phone on tb_user;
drop index idx_user_email on tb_user;

查询结果如下:

MySQL进阶2 - 索引_第7张图片

5. SQL性能分析

5.1 SQL执行频率

MySQL客户端连接成功后,通过show[session | global] status命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT 的访问频次:

SHOW GLOBAL STATUS LIKE ‘Com_______’;

其中,show session status是查看当前会话的状态信息,show global status是查看全局的状态信息,如下:

MySQL进阶2 - 索引_第8张图片

5.2 慢查询日志

慢查询日志记录了所有执行时间超过指定参数(long _query_time,单位:秒,默认10秒) 的所有SQL语句的日志。MySQL的慢查询日志默认没有开启,查询慢查询日志开关是否开启的指令如下:

SHOW VARIABLES LIKE ‘slow_query_log’;
MySQL进阶2 - 索引_第9张图片

而查询慢查询日志时间的指令如下:

SHOW VARIABLES LIKE ‘long_query_time’;
MySQL进阶2 - 索引_第10张图片

Linux(centOS)中需在MySQL的配置文件(etc/my.cnf)中配置如下信息以开启慢查询日志开关:

# 开启MySQL慢查询日志查询开关
slow_query_log=1
# 设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

详细配置及操作如下:

① 首先输入以下指令对/etc/my.cnf进行编辑

vi /etc/my.cnf

② 按’i’键进行编辑,并在文档末尾添加如下语句:

# 开启慢查询日志开关
slow_query_log=1
# 设置慢查询日志时间为2秒
long_query_time=2

③ 按’Esc’键退出编辑,输入’:x’或’:wq’或’:wq!'保存并退出

④ 重启MySQL服务器

systemctl restart mysqld

⑤ 慢查询日志文件存路径:/var/lib/mysql/localhost-slow.log

MySQL进阶2 - 索引_第11张图片
如该位置没有该文件可在MySQL中通过命令show varaiables like ‘%slow_query_log_file%’查看慢查询日志存放位置

⑥ 查看localhost-slow.log内容

cat localhost-slow.log
MySQL进阶2 - 索引_第12张图片
从中可以看到,除了MySQL版本号、端口号等信息外并无其他多余信息

⑦ 查看慢查询日志文件尾部实时输出的内容的命令(单独新建个窗口以便查看实时输出)

tail -f localhost-slow.log
MySQL进阶2 - 索引_第13张图片

⑧ 在另一窗口执行MySQL指令,如查询tb_user表的数据:

select * from tb_user;
MySQL进阶2 - 索引_第14张图片

可以看到,由于查询时间未超过2秒,所以慢查询日志实时输出并没有出现数据。接下来使数据库睡眠3秒,再查看慢日志查询实时输出:

select sleep(3);
MySQL进阶2 - 索引_第15张图片

此时,睡眠的指令可以在慢查询日志中看到,而Windows端的慢查询日志开启请参考https://www.cnblogs.com/taozihua/p/10331897.html

5.3 profile

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

SELECT @@have_profiling;

profiling默认是关闭的,可以通过以下指令查看profiling是否开启:

SELECT @@profiling;
若为0,则为关闭状态,若为1,则为开启状态

若profiling处于关闭状态,可通过set语句开启profiling:

set [global | session] profiling=1

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

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

案例演示

-- 查询profiling是否打开
select @@profiling;

-- 开启profiling
set profiling = 1;

-- 执行SQL操作
select * from tb_user;
select * from tb_user where id = 15;
select * from tb_user where name = '单景瑞';
select count(*) from tb_user;
select * from tb_user where gender = 1;
select sleep(3);

-- 查看每一条sql的耗时基本情况
show profiles;

此时,运行结果如下:
MySQL进阶2 - 索引_第16张图片

-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query 11;

此时,运行结果如下:
MySQL进阶2 - 索引_第17张图片

-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query 11;

MySQL进阶2 - 索引_第18张图片

5.4 explain执行计划

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

语法:

# 直接在select语句之前加上关键字explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;

演示

explain select * from tb_user where id = 8;

结果如下:
在这里插入图片描述

5.3.1 EXPLAIN执行计划各字段含义:

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的值越大越好

案例演示

案例中所演示的表分别为students、courses、student_course以及tb_user,在MySQL基础4-多表查询以及本文中可以找到对应的表

id

explain select s.* from students s where s.id in (select sc.student_id from student_course sc where sc.sourse_id = (select c.id from courses c where c.name = 'Python'));

运行结果如下:

在这里插入图片描述

从图中可以看到,SQL语句编写顺序是students、student_course、courses,但从执行结果来看,id最大的为3,

对应为courses表,这是最先执行的,其次是id为2的student_course表,然后id相同的则由上而下顺序执行,也就

是先执行(第二个子查询),最后才执行students表的查询。

select_type

select_type参考意义不大,它只是说明当前SQL语句的查询类型

type

type代表访问的类型,如:

explain select * from tb_user where id = 1;

在这里插入图片描述

在该查询中,连接类型为const,而type访问类型中,性能由好至差分别为 NULL、system、const、eq_ref、ref、range、

index、all。其中 system 只有访问系统表的时候才会出现,根据主键、唯一索引进行访问,一般会出现const,如:

show index from tb_user;
select * from tb_user;
explain select * from tb_user where phone = '13689012345';

在这里插入图片描述

在这里插入图片描述

如果使用非唯一性的索引进行查询时,就会出现ref,如:

explain * from tb_user where name = '黄浩然';

在这里插入图片描述

在优化SQL语句的时候,尽量把type往前优化,尽量不要出现all,如果出现all的话,代表全表扫描,性能会比较低

如果出现index,代表用了索引,但也会对索引进行扫描,虽然比all快,但性能也比较低

extra代表额外的信息,也就是说,在执行查询的过程当中,在前面几个字段中没有展现出来的值,将会在extra进行展示

关于posssible_key、key、key_len、rows 在此不多做演示

重点关注:  type、possible_key、key、ken_len

6. 索引使用

6.1 最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

案例演示

本案例中所用的表为本文中创建的tb_user表

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

运行结果如下:

在这里插入图片描述

该查询用到了联合索引,索引长度为93,profession、age、status这三个字段的索引全用上了,符合最左前缀法则,且中间并未跳过任何一列

explain select * from tb_user where profession = '软件工程' and age = 24;

运行结果如下:

在这里插入图片描述

该查询索引长度为88,意味着status字段的索引长度为5,该查询也满足最左前缀法则,中间并未跳过某一列

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

运行结果如下:

在这里插入图片描述

该查询语句只查询了profession字段,并未跳过某一列,所以也满足最左前缀法则,且可由前两条查询结果推断出age 的索引长度为5

select * from tb_user where profession = '软件工程' and status = '0';

运行结果如下:

在这里插入图片描述

该查询索引长度为83,意味着profession用到索引,而status没用到,所以索引长度和单独查询profession字段的索引长度是一样的。原因是profession和status间还有一个字段age被跳过,这导致了索引部分失效,也就是status字段的索引失效

explain select * from tb_user where age = 24 and status ='0';

运行结果如下:

在这里插入图片描述

该查询用到的是全表扫描而不是索引,因为最左边的profession字段并未存在,所以该查询不满足最左前缀法则,导致索引失效

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

运行结果如下:

在这里插入图片描述

该查询也用到了索引,因为最左前缀法则要求的是最左列必须存在,但并不意味着最左列的顺序必须是最左边,所以该查询语句满足最左前缀法则

总结: 最左前缀法则要求包含索引中最左边的列,如果最左边的列不存在,索引失效,如果跳过了其中某一列,该列后面的索引将会失效

6.2 范围查询

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

案例演示

explain select * from tb_user where profession = '软件工程' and age > 23 and status = '0';

运行结果如下:

在这里插入图片描述

结合前文来看,该查询满足最左前缀法则,但索引长度为88,说明age后面的那一列,也就是status索引失效。而在业务允许的情况下,尽量用 >= 或 <= 来代替,以规避索引失效的情况

explain select * from tb_user where profession = '软件工程' and age >= 23 and status = '0';

运行结果如下:

在这里插入图片描述

6.3 索引列运算

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

案例演示

-- 查询手机尾号为56的用户信息
select * from tb_user where substring(phone,10,2) = '56';

-- 查看执行信息
explain select * from tb_user where substring(phone,10,2) = '56';

运行结果如下:

MySQL进阶2 - 索引_第19张图片

可以看到,在索引列phone上引用函数运算substring后,索引失效,所以在业务中应尽量减少对索引列进行运算的操作

6.4 字符串不加引号

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

案例演示

select * from tb_user where phone = 13912345678;

explain select * from tb_user where phone = 13912345678;

运行结果如下:

MySQL进阶2 - 索引_第20张图片

在该查询语句中,由于phone字段作为字符串在查询的时候未加引号,导致索引失效

6.5 模糊匹配

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

案例演示

-- 尾部模糊匹配
select * from tb_user where profession like '软件%';

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

-- 头部模糊匹配
select * from tb_user where profession like '%工程';

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

运行结果如下:

从中可以看到,在进行尾部模糊匹配时索引并未失效,但对头部进行模糊匹配时索引失效了

6.6 or连接的条件

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

案例演示

explain eslect * from tb_user where id = 15 or age = 24;

explain select * from tb_user whre phone = '13412345678' or age = 24;

运行结果如下:

MySQL进阶2 - 索引_第21张图片

由于age字段没有索引,只有联合索引,而如果仅仅只对age字段进行查询,不满足最左前缀法则,联合索引不会用到。而正由于联合索引用不到,所以即使id、phone有索引,索引也会失效,所以需要针对age字段也建立索引

-- 建立age字段索引
create index idx_user_age on tb_user(age);

-- 再次查询
explain select * from tb_user where id = 15 or age = 24;

explain select * from tb_user where phone = '13412345678' or age = 24;

运行结果如下:

MySQL进阶2 - 索引_第22张图片

此时可以看到,在建立age字段的索引后,用到的是phone字段及age字段的索引,而非联合索引,所以该查询索引未失效

6.7 数据分布影响

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

** 案例演示**

explain select * from tb_user where phone >= '13612345678';

explain select * from tb_user where phone >= '13112345678';

explain select * from tb_user where phone >= '13012345678';

运行结果如下:
MySQL进阶2 - 索引_第23张图片

从中可以看到,在查询phone >= '13612345678’时用到了索引,这是因为MySQL在查询时评估该表中大部分phone值都小于该值,用索引查询比全表扫描快。但将phone改为’13112345678’或‘13012345678’时,MySQL评估全表大部分phone值都比这俩值大,用全表扫描会比用索引快,所以才进行全表扫描,同理还有:

explain select * from tb_user where profession is null;

explain select * from tb_user where profession is not null;

在该查询中,由于tb_user表中的profession字段没有null值,所以第一个查询语句用到的是索引,而第二个查询语句用到的则是全表扫描,因为MySQL评估当查询时需要比较大部分值时,会优先使用全表扫描,而如果只是一小部分的话,则优先使用索引。当将profession字段的值都设置为null后,结果将相反,这里不做演示

6.8 SQL提示

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

在查询时,如果一个字段有多个索引,MySQL查询优化器会自动选择索引,如下:

-- 创建tb_user表中profession字段的索引
create index idx_user_pro on tb_user(profession);

-- 查询
explain select * from tb_user where profession = '软件工程';

运行结果如下:

在这里插入图片描述

可以看到,在该查询中可能用到的索引分别为’idx_user_pro_age_sta’及’idx_user_pro’,而实际用到的索引为’idx_user_pro_age_sta’,这就是MySQL查询优化器自动选择的结果,如果想指定查询索引,可参考以下三条查询语句:

use  index(告诉数据库用哪个索引):

explain select * from tb_user use index(idx_user_pro) where profession = ‘软件工程’;

在这里插入图片描述

该查询语句只是给MySQL建议用指定查询,至于查询结果,取决于MySQL比较所指定的索引效率快不快后所给出的

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 = ‘软件工程’;

在这里插入图片描述

6.9 覆盖索引

尽量使用覆盖索引(查询用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *

案例演示

-- 删除多余索引,以减少干扰
drop index idx_user_age on tb_user;

drop index idx_user_email on tb_user;

drop index idx_user_pro on tb_user;

-- 测试
explain select id, profession from tb_user where profession = '软件工程' and age = 24 and status = '0';

explain select id, profession, age from tb_user where profession = '软件工程' and age = 24 and status = '0';

explain select id, profession, age, status from tb_user where profession = '软件工程' and age = 24 and status = '0';

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

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

运行结果如下:

从中可以看到Extra中的信息出现不同的地方,而其中’Using where; Using index’的语句的性能要高于’Using index contition’,还需注意的是,如果使用的MySQL版本不同Extra中出现的值,其展示的形式也是不同的

Ps:

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

6.10 前缀索引

当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这回让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀建立索引,这样可以大大节约索引空间,从而提高索引效率

创建前缀索引的语法

create index idx_xxx_xxx on table_name(column(n));

前缀长度

可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的

选择性计算演示

select count(distinct email) / count(*) from tb_user;

select count(substring(email, 1, 5)) / count(*) from tb_user;

运行结果如下:

MySQL进阶2 - 索引_第24张图片

创建前缀索引演示

create index idx_email_5 on tb_user(email(5));

show index from tb_user;

运行结果如下:
MySQL进阶2 - 索引_第25张图片

6.11 单列索引与联合索引

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

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

单列索引情况

explain select id, phone, name from tb_user where phone = '13012345678' and name = '李海洋';

在这里插入图片描述

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

联合索引情况

-- 创建联合索引
create unique index idx_user_phone_name on tb_user(phone, name);

explain select id, phone, name from tb_user where phone = '13012345678' and name = '李海洋';

-- 强制使用联合索引
explain select id, phone, name from tb_user force index(idx_user_phone_name) where phone = '13012345678' and name = '李海洋';

运行结果如下:
MySQL进阶2 - 索引_第26张图片

7. 索引设计原则

Ⅰ. 针对数据量较大,且查询比较频繁的表建立索引

Ⅱ. 针对常作为查询条件(where)、排序(ordder by)、分组(group by)操作的字段建立索引

Ⅲ. 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高

Ⅳ. 如果是字符串类型的字段,字段的长度较长,可以针对字段的特点,建立前缀索引

Ⅴ. 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率

Ⅵ. 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率

Ⅶ. 如果索引不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询

你可能感兴趣的:(MySQL,mysql,数据库)