怎么给字符串字段加索引?
现在 几乎所有的系统都支持邮箱登录,如何在邮箱这样的字段上建立合理的索引?
MySQL是支持前缀索引的,也就说,你可以定义字符串的一部分作为索引。默认地,如果你创建索引的语句不指定前缀长度,那么索引就包含整个字符串。
1 |
|
第一句创建的index1索引里面,包含了每个记录的整个字符串;在第二句语句创建的index2索引李曼,对于每个记录都是只取前6个字节。
由于email(6)这个索引结构中每个邮箱字段都只取前6个字节,所以占用的空间更小,这就是使用前缀索引的优势。
但是损失是,可能会增加额外的记录扫描次数。
1 | select id,name,email from SUser where email='zhangssxyz@xxx.com'; |
如果使用的index1
- 从index1索引树周到满足索引值是“zhangssxyz@xxx.com”的这条记录,取值ID2的值;
- 到主键上查到主键值是ID2的行,判断email的值是正确的,将这行记录加入结果集;
- 取index1索引树上刚刚查到的位置下一条记录,发现已经不满足email=‘zhangssxyz@xxx.com’的条件,循环结束。
如果使用的是index2
- 从index2索引树找到满足索引值是‘zhangs’的记录,找到的第一个ID1;
- 到主键上查到主键值是ID1的行,判断出email的值不是‘zhangssxyz@xxx.com’,这行记录丢弃;
- 取index2上刚刚查到的位置的下一条记录,发现仍然是’zhangs‘,取出ID2,再到索引ID上取整行然后判断,这次值对了,将这行记录加入结果集;
- 重复上一步,知道index2上取到的值不是’zhangs‘时,循环结束。
使用前缀索引,定义好长度,就可以做到既节省空间,有不用额外增加太多的查询成本。
可以通过统计索引上有多少个不同的值来判断要使用多长的前缀。
1 |
|
前缀索引对覆盖索引的影响
如果利用覆盖索引,只查询邮箱和id,从index1查到结果之后就直接放回了,不需要回到ID索引再取查一次。
如果利用前缀索引,即使索引的内容已经是全部的字段信息,也必须要回到ID索引再去判断email字段的值,因为系统并不确定前缀索引的定义是否截断了完整信息。
其他方式
索引选取的越长,占用的磁盘空间就越大,相同的数据页能放下的索引值就越少,搜索效率也就会越低。
针对身份证号码而言:
第一种方式就是使用倒序存储:
1 |
|
由于身份证的最后6位没有地址码这样的重复逻辑,所以最后6位很可能就提供了足够的区分度。当然,实践中你不要忘记使用count(distinct)方法做验证。
第二种方式就是使用hash字段:
在表上再创建一个整数字段,老保存身份证的校验码,同时在这个字段上创建索引。
1 |
|
然后每次插入新纪录的时候,都同时用crc32()这个函数得到校验码填到这个新字段,由于校验码可能存在冲突,也就说两个不同的身份证号通过crc32()函数得到的结果可能是相同,所以你的查询语句where 部分要判断id_card的值是不是精确相同。
1 |
|
使用倒序存储和使用hash字段这两种方法的异同点
相同点:都不支持范围查询。
倒序存储的字段上创建的索引是按照倒序字符串的方式排序的,已经没有办法利用索引方式查出身份证号码在范围之间的所有市民了。同样的,hash字段的方式也只能支持等值查询。
区别:
- 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而hash字段方法需要增加一个字段。当然,倒序存储方式使用4个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个hash字段也差不多抵消了。
- 在CPU消耗方面,倒序方式每次读和写的时候,都需要额外一次reverse函数,而hash字段的方式需要额外调用一次crc32()函数。如果只从这个两个函数的计算复杂度来看的话,reverse函数额外消耗的CPU资源会更小。
- 从查询效率上看,使用hash字段方式的查询性能相对稳定一些,因为crc32算出来的值虽然有点冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近1。而倒序存储方式毕竟还是用的前缀索引的方式,会增加回表查询的扫描行数。