索引的优点
索引的缺点
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
B+Tree索引进化:
结构:哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在 hash表中。如果两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表来解决。
特点:
存储引擎支持:
思考题:
为什么InnoDB存储引擎选择使用B+tree索引结构?
- 相对于二叉树,层级更少,搜索效率高;
- 对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,但是存储索引结构的一个页的大小有限,这样导致一页中存储 的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
- 相对Hash索引,B+tree支持范围匹配及排序操作;
在MySQL数据库,将索引的具体类型主要分为以下几类:主键索引、唯一索引、常规索引、全文索引。
在InnoDB存储引擎中,根据索引的存储形式,又分为两种:
聚集索引选取规则:
思考
以下SQL语句那个执行效率高?select * from user where id = 1; select * from user where name = 'Arm'
备注:id为主键,name字段创建索引
答案:第一条效率高,因为没有回表查询
回表查询: 这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name ( index_col_name,... ) ;
SHOW INDEX FROM table_name ;
DROP INDEX index_name ON table_name ;
案例演示:
-- 查看索引
show index from tb_user;
-- 创建常规索引
create index idx_user_name on tb_user(name);
-- 创建唯一索引
create unique index idx_user_phone on tb_user(phone);
-- 创建联合索引
create index idx_user_pro_age_sta on tb_user(profession,age,status);
-- 删除索引
drop index idx_user_name on tb_user;
MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信 息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:
-- session 是查看当前会话 ;
-- global 是查询全局数据 ;
SHOW GLOBAL STATUS LIKE 'Com_______';
通过上述指令,我们可以查看到当前数据库到底是以查询为主,还是以增删改为主,从而为数据 库优化提供参考依据。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以 查询为主,那么就要考虑对数据库的索引进行优化了。我们又该如何定位针对于那些查询语句进行优化呢? 次数我们可以借助于慢查询日志。
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有 SQL语句的日志。
# 查看慢查询日志状态
show variables like 'slow_query_log';
如果要开启慢查询日志,需要在MySQL的配置文件 (/etc/my.cnf) 中配置如下信息:
# 开启MySQL慢查询日志开关
slow_query_log=1;
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
配置完毕之后,通过以下指令重新启动MySQL服务器进行测试,查看慢日志文件中记录的信息 /var/lib/mysql/localhost-slow.log。
systemctl restart mysqld
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling 参数,能够看到当前MySQL是否支持profile操作:
SELECT @@have_profiling;
可以通过set语句在 session/global级别开启profiling:
SET profiling = 1;
EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行 过程中表如何连接和连接的顺序。
语法:
-- 直接在select语句之前加上关键字 explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件 ;
Explain 执行计划中各个字段的含义:
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始, 并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
例如:
在 tb_user 表中,有一个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession, age,status。
explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0';
explain select * from tb_user where profession = '软件工程' and status='0';
explain select * from tb_user where age = 31 and status = '0';
思考题
当执行SQL语句:
explain select * from tb_user where age = 31 and status = ‘0’ and profession = ‘软件工程’;
时,是否满足最左前缀法则,走不走 上述的联合索引,索引长度?
![[Pasted image 20220310172147.png]]
可以看到,是完全满足最左前缀法则的,索引长度54,联合索引是生效的。 注意 : 最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是 第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。
不要在索引列上进行运算操作, 索引将失效。
# 索引失效
explain select * from tb_user where substring(phone,10,2) = '15';
字符串类型字段使用时,不加引号,索引将失效。
# 索引成功
explain select * from tb_user where phone = '17799990015';
# 索引失效
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连接的条件,左右两侧字段都有索引时,索引才会生效。
# id phone有索引,age 没有
# 索引失效
explain select * from tb_user where id = 10 or age = 23;
# 索引失效
explain select * from tb_user where phone = '17799990017' or age = 23;
联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。
# 当范围查询使用> 或 < 时,走联合索引了,但是范围查询右边的status字 段是没有走索引的
explain select * from tb_user where profession = '软件工程' and age > 30 and status = '0';
# 当范围查询使用>= 或 <= 时,走联合索引了,且所有的字段都是走索引的。
explain select * from tb_user where profession = '软件工程' and age >= 30 and status = '0';
如果MySQL评估使用索引比全表更慢,则不使用索引。MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃 索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不 如走全表扫描来的快,此时索引就会失效。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优 化操作的目的。例如在查询的时候,自己来指定使用哪个索引完成查询。
explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';
explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';
explain select * from tb_user force index(idx_user_pro) where profession = '软件工 程';
尽量使用覆盖索引,减少 select *
。 那么什么是覆盖索引呢? 覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到 。
先来看一组SQL的执行计划,看看执行计划的差别,然后再来具体做一个解析。
explain select * from tb_user where profession = '软件工程' and age = 31 and status = '0' ;
explain select profession,age,status from tb_user where profession = '软件工程' and age = 31 and status = '0' ;
从上述的执行计划我们可以看到,这四条SQL语句的执行计划前面所有的指标都是一样的,看不出来差 异。但是此时,我们主要关注的是后面的Extra,第一条SQL的结果为 Using index condition; 第二条条SQL的结果为: Using where; Using Index 。
因为,在tb_user表中有一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段 profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主 键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引 直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据 ,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表 查询(除非是根据主键查询,此时只会扫描聚集索引)。
思考题:
一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化, 该如何进行才是最优方案:
select id,username,password from tb_user where username = ‘itcast’;答案:
针对于 username, password建立联合索引, sql为: create index idx_user_name_pass on tb_user(username,password); 这样可以避免上述的SQL语句,在查询的过程中,出现回表查询。
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让 索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
语法:
create index idx_xxxx on table_name(column(n)) ;
示例:
为tb_user表的email字段,建立长度为5的前缀索引。
create index idx_email_5 on tb_user(email(5));
前缀长度:
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
# 计算不重复的email数量占全部数据占比
select count(distinct email) / count(*) from tb_user ;
# 计算不重复email分割一定长度占全部数据占比
select count(distinct substring(email,1,5)) / count(*) from tb_user ;
单列索引:即一个索引只包含单个列。
联合索引:即一个索引包含了多个列。
在业务场景中,如果存在多个查询条件,考虑针对于查询字段建立索引时,建议建立联合索引, 而非单列索引。因为当在and连接的两个字段上都是有单列索引时,最终mysql只会选择一个索引,也就是说,只能走一个字段的索引,此时是会回表查询的。
本文笔记参考黑马MySQL教程,相关连接如下:https://www.bilibili.com/video/BV1Kr4y1i7ru?p=102&spm_id_from=333.1007.top_right_bar_window_history.content.click