11 | 怎么给字符串字段加索引?

维护邮箱登录系统,用户表

mysql> select  f1, f2 from SUser where email='xxx';    email 没有索引,全表扫描。

创建索引的语句不指定前缀长度,索引就会包含整个字符串。

mysql> alter  table SUser add index index1(email);          //含整个字符串

mysql> alter  table SUser add index index2(email(6));      //只取前 6 个字节

数据结构和存储区别

图 1 email 索引结构
图 2 email(6) 索引结构

一、前缀索引

占用空间小,可能会增加额外的记录扫描次数。

select  id,name,email from SUser where email='[email protected]';

index1执行顺序:

1.  从 index1 索引树找到满足索引值是’[email protected]’的这条记录,取得 ID2 的值;

2.  查主键值=ID2 行, email 值正确,行记录加入结果集;

3.  取 index1 索引树上刚刚查到的位置的下一条记录,发现已经不满足email='[email protected]’的条件了,循环结束。

回主键索引取一次,系统认为扫描一行

index2执行顺序:

1.  从 index2 索引树找到满足索引值是’zhangs’的记录,找到第一个 ID1

2.  查到主键=ID1 行,查出 email 的值不是[email protected]丢弃

3.  index2 下一条,这次值对了加入结果集

4. 重复上一步,idxe2 不是’zhangs’时,循环结束。

回主键索引取 4 次数据(index2要回索引),扫描了 4 行。导致查询次数多。

如果定义index2 不是 email(6) 而是 email(7),前缀’zhangss’只有一个,直接查到 ID2。

前缀索引,定义好长度,节省空间,不用增加太多查询成本。

二、多长前缀好

建立索引时关注区分度,区分度越高,重复键值越少。多少个不同的值,判断用多长前缀:

mysql> select  count(distinct email) as L from SUser;  

选取不同长度的前缀来看这个值,看 4~7 个字节的前缀索引

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;

前缀索引很可能会损失区分度,设定可以接受的损失比例,比如 5%。 L4~L7 中,找出不小于 L * 95% 值,假设L6、L7 都满足,选择前缀长度为 6

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

增加扫描行数,不止影响性能。

select id,email  from SUser where email='[email protected]';  只返回 id 和 email 字段。

用 index1可用覆盖索引,直接返回,不回ID 索引。index2( email(6) )的话,回 ID 索引,再判断 email 字段值。系统并不确定是否截断完整信息。

前缀索引就用不上覆盖索引对查询性能的优化

邮箱效果不错。但公民信息系统,身份证号12 以上前缀索引,满足区分度

占用空间越大,数据页索引值就越少,搜索效率越低。

四、占用空间小,效率高

4.1 倒序存储

身份证号倒过来存,每次查询的时候:

mysql> select  field_list from t where id_card = reverse('input_id_card_string');

最后 6 位。不要忘记用count(distinct)验证

4.2 hash 字段

创建整数字段,保存身份证校验码(创建索引)。

mysql> alter  table t add id_card_crc int unsigned, add index(id_card_crc);

插入同时用 crc32()函数得到校验码填到新字段。可能存在冲突,不同身份证号 crc32() 结果相同,where 判断 id_card 值是否精确相同

mysql> select  field_list from t where id_card_crc=crc32('input_id_card_string') and  id_card='input_id_card_string'

索引长度4 字节,比原来小。

相同:

不支持范围查询。倒序存储的字段上创建的索引是按照倒序字符串的方式排序的,已经没有办法利用索引方式查出身份证号码在 [ID_X, ID_Y]的所有市民了。hash 只支持等值查询。

区别:

1. 占用额外空间:倒序:存储主键索引上,不消耗额外空间, hash 增加一个字段。倒序存储方式 4 字节不够,消耗差不多抵消

2. CPU 消耗:倒序:写和读的时候,额外调用reverse 函数hashcrc32() 函数。reverse 消耗 CPU 资源小。

3. 查询效率hash 稳定,冲突概率小。倒序存储:前缀索引的方式,增加扫描行数。

小结

字符串字段创建索引场景,使用方式:

1.  完整索引,占用空间;

2.  前缀索引,节省空间,增加查询扫描次数,不能用覆盖索引;

3.  倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够问题;

4.  hash 字段索引查询性能稳定,有额外存储、计算消耗不支持范围扫描(跟第三种一样)。

问题

维护学生信息数据库,登录名”学号 @gmail.com",学号:15位数字,城市编号(3)、学校编号(4、6)、入学年份(1~10)、顺序编号(后5)。

只考虑登录验证,怎么设计登录名索引?

正、反向,重复度都高。入学年份+顺序编号(9)

在此基础上,数字类型存。201100001,占 4 个字节。就是一种 hash,字符串转数字

评论1

学校每年预估2万新生,50年才100万记录,直接全字段索引

hash索引适合,登录检验,不需范围查询。倒序索引,区分度没前缀高。

评论2

学号用bigint存储,4个字节,比前缀索引占用要小。比hash索引,有区间查询的优势

评论3

表数据量特别大:

字符串字段进制压缩,限制每个字符范围(如字母数字下划线)。

每个字节并没存8 bit信息量。单个字符取值只有n种可能性(把字符转成0到n-1的数字),把n进制转为为更高进制存储(ascii可看做是128进制)。

减少索引长度,兼顾前缀匹配

你可能感兴趣的:(11 | 怎么给字符串字段加索引?)