MySQL只学有用的--给字符串添加索引,order by性能优化, count()性能优化

怎么给字符串字段添加索引

根据前一篇文章我们知道,两个概念:

  1. 索引的长度不宜过长
  2. 前缀索引

根据这两个概念,我们在给字符串字段设置索引的时候就可以设置索引的长度。如果不设置,默认索引会包含整个字符串。

举个例子

select table user add index(email);
或者 
select table user add index(email(6));

第一个语句创建索引会包含整个字符串。第二个语句创建索引会只会包含前6个字符串。

在使用第二条语句的创建索引的时候,也要有一定的技巧,要注意索引的区分度,如果索引太短的话,字段的值大量重复,在搜索的时候就会出现大量回表操作。 因此索引的长度我们是需要计算的。

字符串索引计算长度步骤

  1. 首先,你可以使用下面这个语句,查询这个列上有多少个值。
select count(distinct email) as sum from user;

然后,依次选取不同长度的前缀来看这个值,比如我们要看一下4~7个字节的前缀索引,可以用这个语句 :

select 
count(distinct left(email,4)) as l4,
count(distinct left(email,5)) as l5
count(distinct left(email,6)) as l6,
count(distinct left(email,7)) as l7,
from user;

当然,使用前缀索引很可能会损失区分度,所以你需要预先设定一个可以接受的损失比例,比如5%。 然后,在返回的L4~L7中,找出不小于L*95%的值,假设这里L6和L7都满足,你就可以选择前缀长度为6。

前缀索引对覆盖索引的影响

看下面这个语句

select id,email from user where email= "[email protected]";

当我们查询这个语句的时候,如果使用的email索引是全索引的话,这个时候是可以使用索引覆盖的,因为普通索引中的值是主键索引的值。而如果我们在创建索引的时候使用了前缀索引,就无法使用索引覆盖了,会进行回表操作。
即使我们在创建索引的时候,指定的长度就是字段的长度也会进行回表操作。因为innoDB会认为我们使用了前缀索引。认识这个索引的值是不全的。

count统计的相关玩法

由于MySQL不同引擎使用的计算方法不一致,这里只聊一聊InnoDB引擎。

InnoDB引擎count()原理分析

InnoDB引擎在执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。这是因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB表“应该返回多少行”也是不确定的。

假设表t中现在有10000条记录,我们设计了三个用户并进行会话。

  • 会话A先启动一个事务并查询一次表的总行数。
  • 会话B启动事务,插入一行记录后,查询表的总行数。
  • 会话C先启动一个单独的语句,插入一行记录后,查询表的总行数。

在这里插入图片描述
你会看到,在最后一个时刻,三个会话A,B,C会同时查询表T的总行数,但拿到的结果却不相同。

这和InnoDB的事务设计有关系,可重复读是它的默认的隔离级别,在代码上就是通过多版本并发控制,也就是MVCC来实现的。第一行记录都要判断自己是否对这个会话可见,因此对于count(*)请求来说,InnoDB只好把数据一行一行地读出依次判断,可见的行才能够用于计算“基于这个查询”的表的总行数。
MySQL会尽量的走普通索引,因为普通索引的值是主键值,整体比较小。

对比一下count(*) count(主键)、count(字段)、count(1)

  1. count(主键ID),InnoDB引擎会遍历整张表,把每一行的ID值都取出来,返回给server层。server层拿到ID后,判断是不可能为空的,就按行累加。
  2. count(1),InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字"1"进行,判断是不可能为空的,按行累加。

单看这两个用法的差别的话,你能对比出来,count(1) 执行得要比count(主键ID)快。因为从引擎返回ID会涉及到解析数据行,以及拷贝字段值的操作。

  1. count(字段), 需要一行一行的读记录,如果在定义字段的时候设置为not null则直接累加,否则就需要先判断是否为null 才会累加。

  2. count(*),并不会把全部字段取出来,而是专门做了优化,不取值,直接按行累加。

如果表中有普通索引,count(1)和count(*)都会选择走一个最短的普通索引。

在这里插入图片描述
按照效率排序:
count(字段)<count(主键ID)<count(1)≈count()
建议直接使用count(
) 就好了。

Order By 排序

order by 排序算法有两种rowId 和全字段排序。

全字段排序: InnoDB有一个sort_buffer,会将数据查询出来放到sort_buffer中,在进行排序。sort_buffer的大小是由一个sort_buffer_size设置。
如果在排序的数据太大,sort_buffer内存中放不下,则不得不利用磁盘临时文件辅助排序。就是将数据分别放入到磁盘中的临时文件进行排序,在将这些小的有序文件,合并成一个大的有序文件,返回。

rowid排序: 只会将需要排序的字段和主键放到sort_buffer中,先排序在拿id去查询出,需要返回的数据。

set max_length_for_sort_data = 16

max_length_for_sort_data是MySQL中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL就认为单行太大,就会采用rowid这种算法。
以上都是InnoDB引擎自动的优化点。

利用索引

我们知道InnoDB的索引有这样两个特性

  1. 有序
  2. 索引覆盖
    当我们查询的SQL要查询的字段是索引覆盖的内容,排序的字段上也建立了索引,就会简化整个过程。

我们先建立索引

alter table t add index city_user_age(city,name,age)

在这里插入图片描述
可以看到extra上少了 “Using filesort”, “Using index condition”. 多了一个"Using index",表示的就是使用了索引覆盖,性能上会快很多。

如果只创建了city,name这两个联合索引的话就会出来这样的情况。
在这里插入图片描述
发现少了"Using filesort",也是少了排序这个步骤直接返回结果,只是需要进行回表操作。 这样也很快了。

小结

我们聊了String加索引的办法使用前缀索引, 并聊了聊创建前缀索引的时候需要注意辨识度,要让索引中值的重复度降低。

我们还聊了聊统计函数count(),我们需优先使用count(*), Mysql 会自动选择一个最小的普通索引来进行查询。

我们还聊了排序Order by, 我们在可能的情况下要优先利用索引覆盖这个原则。如是不行的话,也尽可能的利用索引的有序性。

引用

《Mysql实战45讲》,count(1)和count(*)的对比

以上内容均为读书所得,如有错误请联系。

交个朋友好

以上内容均为读书所得, 更多有趣有料的科技资讯请关注公众号。(交个朋友)

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章