问题0
假设,现在要维护一个支持邮箱登录的系统,用户表的定义如下:
create table SUser(
ID bigint unsigned primary key,
email varchar(64),
...
)engine=innodb;
使用邮箱登录,那么就一定会用到类似下面的查询语句:
select id,name,email from Suser where email='xxxx';
如果email字段没有建立索引,就只能进行全表扫描。那应该如何给email字段建立索引呢?
建立前缀索引方法如下:
alter table SUser add index index1(email);
or
alter table SUser add index index2(email(6));
index1包含了email的所有字符,index2只包含email的前6个字节。如下图:
从图中可以看出,index2占用的空间更小。但同时可能会增加额外的记录扫描次数。
因为使用前缀索引找到一条记录后还要回表判断这条记录是不是要找的,如果不是就继续查找下一条。
但是如果前缀索引的区分度比较高,那么就不用额外增加太多查询成本。即使用前缀索引,如果定义好长度,就可以做到既省空间,又不用额外增加太多查询成本。
首先预设一个可接受的损失比例,比如5%,然后使用如下语句查看不同前缀能区分的记录数,据此结果进行计算即可:
mysql> 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 SUser;
select id,email from SUser where email='[email protected]';
比如上面这条语句,如果使用index1,就可以利用覆盖索引。
如果使用index2,就只能回表再去判断email字段的值。
即使将index2的定义修改为email(18),假设email长度小于等于18,则此时index2已经包含了全部email的信息,但InnoDB 还是要回表查询,因为系统不确定前缀索引的定义是否截断了完整的信息。
所以,使用前缀索引就不能使用覆盖索引对查询性能的优化了!
count(distinct)
验证一下区分度select field_list from t where id=reverse('xxxxxxxxxxxxxxx8907');
alter table t add id_card_crc int unsigned, add index(id_card_crc);
在表上新建一个整数字段用来保存身份证的校验码,并为此字段添加索引。
每次插入新记录时,都同时使用crc32()
函数得到校验码填到这个新字段上。
由于校验码可能有冲突, 不同身份证号使用crc32()
得到的结果可能相同,所以查询语句还要判断id_card的值是否精确相等。
select field_list from t where id_card_crc=crc32('xxxxxxxxxxxxxx8907') and id='xxxxxxxxxxxxxx8907';
使用校验码就使得索引长度变成了4byte
相同点:
都不支持范围查询
不同点:
- 从占用额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间;而hash字段需要增加一个字段。但是如果倒序存储方式使用的字节长度比较长,那这个消耗和hash字段差不多可以抵消。
- 从CPU消耗方面,倒序方式每次读写都要额外调用一次reverse()函数,求hash字段需要额外调用crc()函数。只从这两个函数的计算复杂度来看,reverse()比crc()额外消耗的CPU资源更小些。
- 从查询效率上看,使用hash字段方式的查询性能更稳定一些。crc()虽然会有冲突,但是概率很低,可认为每次查询平均扫描1行。而倒序存储还是使用前缀索引的方式,会增加扫描行数。
如果你在维护一个学校的学生信息数据库,学生登录名的统一格式是”学号 @gmail.com", 而学号的规则是:十五位的数字,其中前三位是所在城市编号、第四到第六位是学校编号、第七位到第十位是入学年份、最后五位是顺序编号。
系统登录的时候都需要学生输入登录名和密码,验证正确后才能继续使用系统。就只考虑登录验证这个行为的话,你会怎么设计这个登录名的索引呢?