1. 索引语法
- 创建索引:
语法格式: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,并且查询测试数据。
数据准备好了之后,接下来,我们就来完成如下需求:
A. name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。
语法格式:create index idx_user_name on tb_user(name);
B. phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。
语法格式:create unique index idx_user_phone on tb_user(phone);
C. 为profession、age、status创建联合索引。
语法格式:create index idx_user_pro_age_sta on tb_user(profession,age,status);
D. 为email建立合适的索引来提升查询效率。
语法格式:create index idx_user_email on tb_user(email);
2. SQL性能分析
2.1 SQL执行频率
我们可以在客户端中使用show [session|global] status
查看服务器的状态信息,通过以下指令就可以查看当前数据库对应执行SELECT、UPDATE、DELETE、INSERT的操作次数
语法格式:SHOW GLOBAL STATUS LIKE 'Com_______';
相关字段信息如下:
- Com_select:进行select操作的次数
- Com_insert:进行insert操作的次数
- Com_update:进行update操作的次数
- Com_delete:进行delete操作的次数
💡 提示:通过上述指令我们就可以基本判断当前数据库是执行查询操作居多还是进行增删改等操作居多,如果是查询操作居多后续就可以针对性的进行索引SQL优化!
2.2 慢查询日志
慢查询日志:当某个查询SQL执行的时长超过指定的阈值(long_query_time)时则会将该条SQL记录到慢查询日志(slow_query_log_file)当中,相关命令如下:
- 查看慢查询日志是否开启:
语法格式:show variables like 'slow_query_log';
- 如何开启慢查询日志:
答:在MySQL配置文件(windows为my.ini,Linux系统为my.cnf)当中配置以下内容,随后重启MySQL服务器即可
# 慢查询日志相关
slow_query_log = 1 # 开启慢查询日志
long_query_time = 2 # 慢查询阈值(默认为10)
# slow_query_log_file = '' # 慢查询日志文件位置
- 查看配置内容是否生效:
- 查看慢查询是否开启:
show variables like 'slow_query_log';
- 查看慢查询时间阈值:
show variables like 'long_query_time';
- 查看慢查询日志文件位置:
show variables like 'slow_query_log_file';
2.3 profile详情
Profile:profile详情可以帮助我们查看某个SQL具体的执行阶段耗时,相关操作如下:
- 查看当前MySQL是否支持profile:
语法格式:select @@have_profiling;
- 查看当前MySQL是否开启profiling:
语法格式:select @@profiling;
如果显示为0证明没有开启!
- 开启profiling操作:
语法格式:set profiling=1;
接下来我们可以执行一条SQL语句,比如select * from tb_user;
然后使用show profiles;
查看所有SQL语句的耗时详情
如果想要查看更加具体的耗时信息,可以使用如下SQL语句:show profile for query query_id;
2.4 explain执行计划
执行计划:我们可以使用执行计划查看某个查询SQL的执行信息,包括表是如何连接以及表连接的顺序,语法格式如下:
语法格式:explain/desc select 字段列表 from 表名 where 条件;
其中各个字段含义如下:
- id:select查询的的序列号,表示操作表的顺序(id值越大越先执行,相同则从上到下依次执行)
- select_type:表示select的类型,如果是simple表示单表操作(没有任何子查询和表连接),primary表示主查询,subquery表示子查询
- type:表示连接类型,从好到坏依次为:null、system、const、eq_ref、ref、range、index、all
- possible_key:可能使用到的索引,一个或者多个
- key:实际使用到的索引,如果为null则没有使用索引
- key_len:索引中使用到的字节数
- rows:MySQL认为要执行查询的行数,并不总是精确的
- filtered:表示返回结果的行数占需要读取行数的百分比,值越大越好
3. 索引使用原则
3.1 最左前缀法则
最左前缀法则:如果构建了一个联合索引,此时需要遵循最左前缀原则:即查询的时候要从联合索引最左边的列开始,否则索引失效;并且如果中间跳跃某一列,则后面部分的索引失效。
我们查看tb_user
表中的索引信息:
可以发现我们创建了一个名称为idx_user_pro_age_sta
的联合索引,联合的字段分别是profession、age、status,因此查询过程中想要使用联合索引必须遵循最左前缀原则。如果查询条件没有profession则索引失效,如果中间跳跃某一索引列则后面的索引失效!下面我们就来进行举例:
使用联合索引:
- case1:查询条件包括profession、age、status字段
此时索引长度为54,使用到了全部的联合索引
- case2:查询条件只包括profession和age字段
此时虽然key
字段表明仍然使用到了该联合索引,但是key_len
变为了49,证明只用到了部分索引!即status
字段的索引长度为54 - 49 = 5
- case3:查询条件只包括profession字段
此时key
字段表明使用到了该联合索引,但是key_len
变为了47,证明只用到了部分索引!即age
字段的索引长度为49 - 47 = 2
联合索引失效:
- case1:查询条件只使用age、status字段
此时key
为null并且type
为all证明并没有走索引,而是进行了全表扫描,因此没有使用到联合索引最左边的列,不符合"最左前缀法则",索引失效!
- case2:查询条件使用profession和status字段
此时根据key_len
我们可以推断出只使用到了profession这个字段的索引,因为跳过了中间的索引列age
,因此右侧索引全部失效!
❓ 提问:如果执行语句select * from tb_user where status = ‘0’ and age = 31 and profession = ‘软件工程’,是否会走联合索引?索引长度是多少?
正确答案:符合最左前缀原则,索引长度为54,因为是否遵循最左前缀法则与SQL定义的排列顺序无关,内部查询优化器会自动进行分析优化!
3.2 范围查询
现象:当在使用联合索引的过程中,使用到了范围查询(>,<)此时范围查询右侧索引失效!
举例:执行以下SQL语句:explain select * from tb_user where profession = '软件工程' and age > 31 and status = '0';
查看执行计划:
此时key
证明使用到了联合索引,但是key_len
表明索引长度为49即(只使用到了profession和age字段的索引),范围查询右侧的status字段索引失效!
解决方案:尽量使用>=,<=之类的范围查询
3.3 索引失效情况
3.3.1 索引列运算
原则:如果在索引列上进行了运算,则索引失效!
查看当前tb_user
表中的索引情况:发现在列phone上有一个单列索引
需求:查询phone字段最后两位是’15’的所有用户信息
此时我们发现type
字段为ALL证明没有使用索引,而是进行了全表扫描,因为索引列phone参与了运算!
3.3.2 字符串不加引号
原则:如果索引列为varchar等字符类型,但是查询时没有使用引号则索引失效!
- case1:字符串类型使用引号
- case2:字符串类型不使用引号
上述结果中我们可以看出,虽然两者都可以正常查询出结果,但是仅仅是将status
字段查询条件由’0’变成了0,导致了status字段索引失效!
3.3.3 模糊查询
原则:在使用LIKE等模糊查询时,如果是尾部模糊匹配则索引不会失效,但是如果是头部模糊匹配则索引失效!
- case1:尾部模糊匹配
- case2:头部模糊匹配
- case3:尾部头部均模糊匹配
上述结果中我们可以看出,尾部模糊匹配并不会造成索引失效!但是只要使用到了头部模糊匹配就会造成索引失效!
3.3.4 or连接条件
原则:在使用or语句时,只有两侧都使用到了索引才会生效
- case1:当age没有建立索引时
此时无论是使用id主键索引还是phone唯一索引,因为age字段没有索引,因此or条件不符合两侧均使用索引的情况,此时索引失效!
- case2:当age建立索引时
此时当我们将age
字段添加索引时,继续执行上述执行计划,此时可以看到索引生效了,因为or条件两侧均使用了索引
3.3.5 数据分布影响
原则:如果MySQL评估全表扫描比索引更快时,索引失效!
- case1:查询age >= 1的所有用户
- case2:查询age >= 40的所有用户
此时我们可以发现上述两条SQL语句结构完全一样,只是传入的参数不一样,但是age>=1进行了全表扫描,age>=40则使用索引查询,这是因为当前表中大部分数据都是符合age>=1的,因此MySQL预估走全表扫描的时间效率高于通过索引查询!
3.4 SQL提示
背景引入:
当前profession是存在联合索引的:
但是如果我们给profession加上一个单列索引,继续执行这条SQL语句会走哪个索引呢?
可以发现,此处possible_keys
显示可能使用的索引为联合索引和单列索引,但是MySQL评估之后最终选择了联合索引,那么我们可以指定让MySQL使用哪种索引吗?因此引入了SQL提示:
SQL提示:是优化数据库的一个手段,简单来说就是在SQL执行过程中加入人为指定的一些提示
- use index:建议MySQL使用何种索引(有可能MySQL并不接受你的建议)
- ignore index:忽略指定的索引
- force index:强制使用某种索引
3.5 覆盖索引
覆盖索引:指的是查询过程中使用到了索引,并且需要返回的列恰好都在索引中能够全部找到,因此尽量不要在查询时使用select *类似的语法,否则会造成回表查询,效率低下!
下面我们就来看看以下两条SQL语句的执行计划:
case1:查询id、profession、age、status
字段
case2:查询全部字段
我们需要注意执行计划当中的extra
字段:
- 如果列值为Using where、Using index说明需要返回的字段都在索引中,因此无需回表查询
- 如果列值为Using Index condition说明需要返回的字段并不完全包含在索引中,需要根据id回表查询
总结:
由于我们建立了联合索引(profession、age、status),该索引是一个二级索引,在查询时叶子节点下包含该数据的主键id信息,当需要查询id、profession、age、status等字段内容时,只需要查询二级索引即可获取全部信息,但是如果需要查询以外的字段内容,则需要根据id主键回表查询聚集索引,性能相对较差
❓ 提问: 一张表, 有四个字段(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),此时就可以避免回表查询,且使用到了该联合索引
3.6 前缀索引
3.6.1 基本概念
前缀索引:有时候当我们需要存储文章内容时可能会使用到varchar、text等文本类型,但是如果存储内容很多,此时索引占用的存储空间就会非常大,而且还会导致IO查询效率变低。有时候我们可以根据这些字符串的一部分前缀作为索引,节省空间的同时提升效率。
语法格式:create index idx_name on table_name(index_col_name(n));
例如当前我们尝试给tb_user表中的email字段添加长度为5的前缀索引:
3.6.2 索引选择性
那么我们应该如何选择合适的索引前缀长度呢?可以根据索引的选择性进行分析:选择性就是不重复的索引记录数和数据表总数的比值。索引选择性越高,对应的查询效率就越高(最高的选择性就是1)
我们可以使用诸如下面的SQL语句查看当前前缀长度为n时对应的索引选择性:
语法格式:select count(distinct substr(email,1,5)) / count(*) from tb_user;
此时相对于此表来说,选择前缀长度为5能保持较高的选择性并且占用较低的存储空间
4. 索引设计原则
这里简单介绍构建索引时应当遵守的原则:
- 索引适用于那些数据量较大并且查询操作较为频繁而增删改操作不频繁的表
- 索引常用于那些经常用于进行where、group by、order by的字段
- 索引列应该挑选区分度较高的字段,尽量构建唯一索引,区分度越高,索引查询性能越好
- 如果是字符串类型的列并且字段长度较大,可以考虑使用前缀索引
- 尽量使用联合索引和非单列索引,因为联合索引很多情况可以使用到覆盖索引,避免回表查询
- 要控制索引的数量,索引并不是越多越好,索引越多导致维护的成本也会升高,并且影响增删改的效率
- 如果索引列不能存储null值,请在创建表时使用not null进行约束。以便于查询优化器更好确定使用哪个索引查询