Mysql进阶-索引篇(下)

 SQL性能分析

SQL执行频率

MySQL 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次,通过sql语句的访问频次,我们可以判断到当前数据库到底是以查询为主,还是以增删改为主,从而为数据库优化提供参考依据。 如果是以增删改为主,我们可以考虑不对其进行索引的优化。 如果是以查询为主,那么就要考虑对数据库的索引进行优化了。

0c62ffe6b8c54f18a43282996117fd77.png

慢查询日志 

如果当前数据库是以查询为主,我们可以通过慢查询日志来查看耗时较长的查询,慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有 SQL语句的日志。MySQL的慢查询日志默认没有开启,我们可以查看一下系统变量 slow_query_log。

 慢查询日志默认是关闭的

375261cd484e4dd887613aa728703c2c.png

 开启慢查询日志,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息,同时重启mysql服务,使配置文件生效

# 开启MySQL慢日志查询开关

slow_query_log=1

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

ad7047b43a154514a782a5383c7ba976.png

 查询600万数据的表

eee2fb613cf440e1812862c6adb66c24.png

 毫无疑问出现在慢查询日志上a25b6db262ed4c7fa437a364afaeea05.png

profile详情

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

#查询是否有profile参数
select @@have_profiling
#查询profile功能是否开启 0表示关闭 1表示开启
select @@profiling

cc3470d26ce14cf1a04b88efebc140cc.png

 可以看到,当前MySQL是支持 profile操作的,但是开关是关闭的。可以通过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;

f9c78897baa14e0ba7c47c15a4d63817.png

explain

EXPLAIN 或者 DESC命令获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行 过程中表如何连接和连接的顺序。在select语句开头加上explain关键词即可,通过explain各字段的值,我们可以分析出此时的sql语句走不走索引,有没有回表查询等情况,方便我们进行sql优化

7159495b9656426c8f9e8527bcee28f5.png

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

 索引使用

效率验证

准备一张百万数据的表,根据id查询数据,我们可以发现此时耗时仅为0.01秒,这是因为id默认就是主键索引,这是已经有索引的查询情况

5cc44a84f8b1404693b2d776819da01a.png

 根据name字段查询时,此时没有建立name字段的索引,可以看到耗时达到6秒之久

0767130613884f31a5e4fee757174b84.png

建立索引之后,根据name查询,耗时仅有0.01秒,可见索引能大大提升查询效率

16da7e42c69448ba9c458cdc57346665.png

最左前缀法则

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

以tb_user表为例,此时有idx_user_pro_age_sta这个联合索引,这个联合索引涉及到三个字段,顺序分别为:profession, age,status。

9ee1baad1e4c4c58be19a4f5803f1ac1.png

 对于最左前缀法则指的是,查询时,最左变的列,也就是profession必须存在,否则索引全部失效。 而且中间不能跳过某一列,否则该列后面的字段索引将失效。

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

 联合索引的三个字段都在联合索引必然生效,只要where条件后面的三个字段都在索引就会生效,与字段的先后顺序无关c1a8a66093d84bfc9e34ecf2cfad24b1.png

 我们尝试profession后面的两个字段逐步减少,会发现联合索引仍然生效,正说明只要索引的最左边的字段在where子句中索引就会生效。从图中我们也可以推测出profession字段索引长度为47、age 字段索引长度为2、status字段索引长度为5。0b8902e66d4549c688b83a70ee02f112.png

 但此时我们将查询条件中的profession去掉,此时联合索引失效。

a14881ef6dad4f5dac66c0dcb5e1577f.png

 如果最左的列存在,但是跳过了中间的列,那么只会中间列之后的索引都不会生效,虽然走了联合索引,但长度只有47,为profession字段索引长度。

aafcdf3aae264c779169b2cabdab9813.png

 范围查询

联合索引中,出现范围查询(>,<),范围查询右侧的列索引失效。从索引长度可以看到status字段的索引并没有生效

7621835d4b11437abc299473739e755d.png

 但如果我们将范围查询加上等号即≥和≤这种形式,联合索引就生效,在业务允许的情况下,尽可能的使用类似于 >= 或<=,而避免使用 > 或 <,来避免索引失效

f1f5d236c114447e9eaf3942222e9fad.png

 索引失效情况

 索引列函数运算

当根据phone字段进行等值匹配查询时, 索引生效。

56d3207931ac46c18af0f73e8366d7ba.png

 当进行函数运算时,索引就失效,走的是全表扫描

f1f6a41b3f9c45d4abd8d93b2ae703df.png

如果进行的是数值运算,索引仍然生效

d3cc25844ead44fe9a3c6eb24bae6965.png

 字符串不加引号

如果不给phone字段添加引号,造成索引类型不匹配,索引也会失效

7bc723be16744d31bb30e2c049d5ac6b.png

 模糊查询

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

头部模糊查询,走的是全表扫描

8b473af81d804d8bbc15ea39e5118ea5.png

 如果是尾部模糊,则走联合索引,索引生效,如果前后模糊查询,由于前面模糊查询已使索引失效,所以也是索引失效

e15fd297ba7443a58b97516a970bd966.png

 or连接条件

用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会 被用到,因为or前面的用了索引,or后面的列没有索引还是要走全表扫描,mysql优化器就会判断直接全表扫描,避免浪费一次查找索引树的时间

数据分布影响

相同的SQL语句,只是传入的字段值不同,最终的执行计划也完全不一样,这是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不 如走全表扫描来的快,此时索引就会失效。即数据分布情况也会影响索引是否生效

3d2c6523c97444b2a71549f0f26084a9.png

 SQL提示

sql提示就是在执行查询时我们自己指定要使用的索引

 此时我们有 profession这个字段的单列索引

cc3c342851174e1da0aa432f8a380b60.png

 我们可以看到,possible_keys中 idx_user_pro_age_sta,idx_user_pro 这两个 索引都可能用到,最终MySQL选择了idx_user_pro_age_sta索引。这是MySQL自动选择的结果。我们想看到的是走单列索引,毕竟我的mysql我做主,这时候就要用到sql提示啦!38065709f9424d3d924a36153a02c7d1.png

我们可以在select语句时加上use index(索引名)来建议mysql使用我们指定的索引(仅仅是建议,mysql内部还会再次进行评估)

2b20e99a3ad5489cbc1dbc42320bb1f1.png

 上面的use index 仅仅是建议,要是mysql不听怎么办?这时候就需要force index来强制mysql执行我们指定的索引!即使效率可能下降,但爷乐意,千金难买爷乐意,下图我们可以看到正常情况下肯定不会走sta索引的,毕竟我们where子句是根据profession来查的,但通过我们的强制,也能让mysql执行这一索引

28abba46eb4e4adf85ba09ba7271202e.png

覆盖索引

尽量使用覆盖索引,减少select *, 覆盖索引是指查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到。使用覆盖索引能减少能大大提高查询,其原因就是需要返回的列在索引列已经全部找到,不需要回表查询了,这也是mysql优先使用联合索引的原因

前缀索引

介绍

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

 语法

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

为tb_user表的email字段,建立长度为5的前缀索引。

fee7b812272f4be8b8e587bf7ce91875.png

 前缀长度

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

 查询流程

前缀索引相当于二级索引,但他匹配到时,必须回表查询,确认根据前缀索引匹配到行数据的email值跟sql语句的email值是否一样,同时要遍历到链表的下一个元素,看是否与前缀索引匹配,如果是就要重复刚刚的流程然后返回数据,如果不是,直接返回数据即可

88f8c91cfabd49c8a157e95c90721512.png

 单列索引与联合索引

 前文提到的覆盖索引,mysql会优先使用联合索引,以此来减少回表查询,提高查询效率

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

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

 索引设计原则

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

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

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

4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。 5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间, 避免回表,提高查询效率

6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率,同时索引也会占用硬盘空间。 

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

猜你喜欢

转载自blog.csdn.net/weixin_64133130/article/details/134060684
今日推荐