维护邮箱登录系统,用户表
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 个字节
数据结构和存储区别
一、前缀索引
占用空间小,可能会增加额外的记录扫描次数。
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 函数,hash : crc32() 函数。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进制)。
减少索引长度,兼顾前缀匹配