这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战
在实际应用场景中,存在给字符串设置索引的情况。如果是给长字符串设置索引,例如邮箱、身份证等信息。那么设置的索引占用内存空间是会很大的。
字符串索引设计方案
前缀索引
MySQL支持前缀索引,即可以定义字符串的一部分作为索引。通过设置前缀索引,可以有效减少索引占用的空间。
但设置前缀索引也是有成本的,如果设置的前缀区分度不高,意味着扫描行数可能也会增多。以下面的一个例子来说明。
假设有个用户邮箱表。
| id | |
|---|---|
| 1 | zhangsan01@qq.com |
| 2 | zhangsan011@qq.com |
| 3 | zhangsan02@qq.com |
| .. | .. |
假设有两种索引情况,一种是全字符串索引,即不设前缀。还有一种是前缀索引。
alter table UserEmail add index index1(email);
alter table UserEmail add index index1(email(7));
那么对于索引index1,叶子节点的值就是 email字段;对于索引index2,叶子节点的值就是email字段前7个字节,即 zhangsan。(当然叶子节点还有主键的)
那么,对于查询语句
select * from UserEmail where email='zhangsan02@qq.com';
如果使用索引index1,查询步骤可分解为:
- 在索引树index1下找到zhangsan02@qq.com的记录,取得主键Id=3;
- 回表到主键上找到对应的行,判断email是否正确,将这条记录加入结果集;
- 继续查询index1索引树下刚才位置的下一条记录,直到email值不等于查询的参数,条件结束。
这个过程中,只需要回表一次就能取得数据,只扫描了一行。
如果使用索引index2,查询步骤就变为:
- 在索引树index2中找到等于zhangsan的记录(即email参数的前7个字节),找到Id=1的记录;
- 回表,发现email值不对,继续下一个匹配;
- 找到Id=2的记录,回表,还是不对,继续下一个匹配;
- 找到Id=3的记录,回表,值对了,继续下一个匹配;
- 重复上一步,直到index2索引节点的值不等于zhangsan即停止匹配,返回结果集。
从这里看出,使用email的前7个字节作为索引,所扫描的行数需要3行。
得出结论,使用前缀索引,可能会导致查询语句读数据的次数变多。
所以使用前缀索引,定义好前缀长度是很有必要的,既可以节省内存,也可以实现减少扫描的行数。
可以通过使用以下SQL语句进行索引的区分度比较,索引区分度越高,说明重复的键值就越少。
> select count(distinct email) as Len from UserEmail;
> select
count(distinct left(email,4)) as Len4,
count(distinct left(email,5)) as Len5,
count(distinct left(email,6)) as Len6
from UserEmail;
使用前缀索引很可能会损失区分度,所以要预设一个可以接受的损失比例,比如5%。然后在返回的Len4-Len6中,找出不小于Len * 95%的值。
由于使用前缀索引都需要回表进行校验查询值是否正确,所以在使用前缀索引时,会用不上覆盖索引对查询性能的优化了。因为覆盖索引的优化就是避免回表。
倒叙索引 和 hash字段
除了使用前缀索引,还有倒叙索引和hash字段可以解决字符串索引占用内存空间大的问题。
倒叙索引,是为了解决前缀索引区分度不大的问题,而后面几个字符恰好区分大较高。使用方式是跟前缀索引类似的,实践中也是需要通过count(distinct) 语句去做验证的。只不过在使用时,需要倒过来查询(插入也是的!)
select * from user where idCard = reverse('5453132123');
Hash字段,通过在表中再创建一个整数字段,来保存字符串的校验码(hash值),同时在这个字段上创建索引。hash值也是有可能冲突的,所以查询的时候需要把查询条件等值匹配;
select * from user where idCard = 21312312 and id_crc=crc('21312312');
这两种方式的特点都是不支持范围查询,只能等值查询。
区别的话,可以从占用的额外空间、CPU消耗、查询效率上做比较。
总结
- 直接创建完整索引,可能比较占用空间;
- 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;
- 倒叙存储,再创建前缀索引,用于绕过字符串本身前缀区分度不够的问题;
- 创建Hash字段索引,查询性能稳定,有额外的存储和计算消耗,不支持范围查询。