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

场景引入

假设现在维护一个支持邮箱登录的系统,用户表定义如下:

sql 复制代码
create table SUser(ID bigint unsigned primary key,email varchar(64), ... )engine=innodb;

由于登录方式为邮箱,那么一定会有下面这样的业务:

sql 复制代码
select f1,f2 from SUser where email='xxx';

对于上述语句,可以不对email字段加索引,也可以对email建立普通索引,还可以建立前缀索引。其建立索引的语句为:

sql 复制代码
alter table SUser add index index1(email);
alter table SUser add index index2(email(6));

这两种不同建立方式在数据结构和存储上的区别如下:

由于前缀索引只取规定字节数,所以占用空间更小。

但使用前缀索引可能会增加额外的记录扫描次数。比如要执行下面这个查询:

sql 复制代码
select id,name,email from SUser where email='zhangssxyz@xxx.com'

如果使用普通索引,执行顺序为:

  • 从index1索引树找到满足条件的记录,取得主键为ID2;

  • 在主键索引树上找到对应的行,如果email值正确,将这行记录加入结果集;

  • 从index1索引树上查找下一条记录,发现不满足条件,结束。

由于只回主键索引取一次数据,所以系统认为只扫描一行。

如果使用前缀索引,执行顺序为:

  • 从index2索引树找到满足条件的记录,第一个是ID1;

  • 在主键索引树上找到对应的行,发现email值不正确,丢弃这行记录;

  • 从index2索引树找到满足条件的下一条记录,取得主键为ID2;

  • 在主键索引树上找到对应的行,如果email值正确,将这行记录加入结果集;

  • 重复上述步骤,直到index2上匹配不到正确的前缀。

这个过程需要回主键索引取4次数据,即扫描了4行。

但是,在该场景下,如果定义的index2为email(7),会发现在index2上能直接取到ID2,也就只需要扫描一行。因此,当使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本

那么,你大概就会好奇了,前缀的长度如何确定呢?

在建立索引时,我们需要关注字段的区分度,区分度越高,重复的键值越少。

可以使用下面语句计算列上有多少不同的值:

sql 复制代码
select count(distinct email) as L from SUser;

然后,可以依次选取不同前缀长度来看:

sql 复制代码
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里需要选取\\ge95% L的值。

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

前缀索引除了可能增加扫描行数,还有其他影响。比如下面这两个语句:

sql 复制代码
select id,email from SUser where email='zhangssxyz@xxx.com';

如果使用index1(整个字符串的索引结构),可以利用覆盖索引,在index1查到结果就直接返回,不需要回表;而如果使用前缀索引,就必须回表,即使使用整个字符串的长度email(18),依然需要回表,因为系统不确定前缀索引的定义是否截断了完整信息。

在该例子中,使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是在选择是否使用前缀索引时需要考虑的一个因素。

其他方式

对于邮箱这样的字段,由于用户名差异较大,使用前缀索引的效果可能不错。但有些字段前缀区分度不好,比如身份证号,这时候如果使用前缀索引,可能需要创建长度较长的前缀索引,才能满足区分度要求。但是这也意味着索引占用磁盘空间越大,搜索效率会越低。

假如,能够确定业务需求里只有按照身份证进行等值查询的需求,那么有更好的处理方式,既可以占用更小空间,又能达到相同查询效率:

(1)使用倒序存储

将身份证号倒过来存储,那么可能取6位就有足够的区分度。每次查询时,使用下面的方式:

sql 复制代码
select field_list from t where id_card = reverse('input_id_card_string');

(2)使用hash字段

可以在表上再创建一个整数字段,比如每次插入新纪录时用crc32()函数得到一个校验码字段。由于校验码可能冲突,在查询时候需要判断id_card字段是否相同。

来看上面两种方法的异同:相同点是都不支持范围查询,不同点有:

  • 占用额外空间不同。倒序存储不消耗额外存储空间,而Hash字段需要增加一个字段。

  • CPU消耗不同。倒序存储方式每次读写都需要额外调用一次reverse函数,而Hash方式需要额外调用一次crc32()函数,从两个函数的计算复杂度看的话前者更小。

  • 查询效率不同。使用Hash方式的查询性能相对更稳定,因为crc32()冲突概率小,可以认为每次查询的平均扫描行数接近1,而倒序存储方式毕竟还是使用前缀索引方式,会增加扫描行数。

相关推荐
杨云龙UP1 小时前
MySQL Host is blocked because of many connection errors 导致 JDBC 连接失败排查与解决
linux·运维·网络·数据库·sql·mysql·登录失败
程序员梅雨3 小时前
MySQL底层探索(一):什么是脏页?什么是冷热分离?
mysql·面试
bksczm5 小时前
MySQL基础篇之表的增删查改(CRUD)细则
sql·mysql
莫陌尛.14 小时前
Docker MySQL 删表卡死、执行超时(MDL锁阻塞)问题复盘与根治方案
mysql·docker·容器
毕业设计70317 小时前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
泡泡鱼(敲代码中)19 小时前
MySQL基础学习笔记:从数据模型到DDL全掌握
开发语言·数据库·笔记·学习·mysql
冰暮流星20 小时前
mysql之表子查询
数据库·mysql
这个DBA有点耶1 天前
MySQL大表DDL锁表锁到崩溃?Online DDL的3个关键参数和实战避坑
数据库·mysql·架构
庆登登登1 天前
MySQL Workbench 鸿蒙 PC 适配全记录:从 GTK_X11 桌面程序到 ArkUI 原生数据库工作台
数据库·mysql·harmonyos
黑色的白兔No11 天前
deepin 25安装mysql
数据库·mysql·debian