[关闭]
@adamhand 2019-02-24T21:53:19.000000Z 字数 2877 阅读 1172

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


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

  1. mysql> create table SUser(
  2. ID bigint unsigned primary key,
  3. email varchar(64),
  4. ...
  5. )engine=innodb;

由于要使用邮箱登录,所以业务代码中一定会频繁出现类似于这样的语句:

  1. mysql> select f1, f2 from SUser where email='xxx';

为了加快查询速度,应该在邮箱这个字段上加上索引,这个索引应该怎么加呢?

前缀索引

如果直接给整个邮箱字符串加上索引,如果这个字符串比较长,可能会占用比较大的空间。MySQL 支持前缀索引,也就是说,可以定义字符串的一部分作为索引。给邮箱字符串创建两种索引的方式如下:

  1. mysql> alter table SUser add index index1(email); //直接创建索引
  2. mysql> alter table SUser add index index2(email(6)); //创建前缀索引

这两种索引的存储结构如下:



直接创建索引



创建前缀索引

前缀索引占用的空间会更小,这就是使用前缀索引的优势。但前缀索引也有弱点:可能会增加额外的记录扫描次数

如果有一个查询语句如下:

  1. select id,name,email from SUser where email='zhangssxyz@xxx.com';

如果使用index1,查询步骤如下:

这个过程中,只需要回主键索引取1次数据,所以系统认为只扫描了1行。

如果使用的是 index2,执行顺序是这样的:

在这个过程中,要回主键索引取 4 次数据,也就是扫描了 4 行。

如何选择前缀索引的长度

使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本,那么应该如何选择合适的长度?

实际上,在建立索引时关注的是区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。因此,可以通过统计索引上有多少个不同的值来判断要使用多长的前缀。所以,在选择前缀索引时,可以先使用下面这个语句,算出这个列上有多少个不同的值。

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

然后,依次选取不同长度的前缀来看这个值,比如要看一下 4~7 个字节的前缀索引,可以用这个语句:

  1. mysql> select
  2. count(distinct left(email,4))as L4,
  3. count(distinct left(email,5))as L5,
  4. count(distinct left(email,6))as L6,
  5. count(distinct left(email,7))as L7,
  6. from SUser;

如果可以接受的损失比例为 5%,在返回的 L4~L7 中,找出不小于 L * 95% 的值即可。

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

使用前缀索引可能导致覆盖索引不能用。比如下面的sql语句:

  1. select id,email from SUser where email='zhangssxyz@xxx.com';

这个语句只需要返回idemail,根据建表语句可知id是主键,也就是所有二级索引的叶子节点存储的都是id的值,email这个索引也不例外。所以如果使用index的话可以使用覆盖索引直接得到id的值,但是使用index2的话,就不得不回到 id 索引再去判断 email 字段的值。

即使将 index2 的定义修改为 email(18) 的前缀索引,这时候虽然 index2 已经包含了所有的信息,但 InnoDB 还是要回到 id 索引再查一下,因为系统并不确定前缀索引的定义是否截断了完整信息。

前缀区分度很低

如果需要建立前缀索引的字符串的前缀区分度很低,比如说身份证号,那么该怎么办呢?

一般情况下有两种方法:

第一种方式是使用倒序存储。如果将字符串倒序后,区分度比较大,可以使用这种方法。比如存储身份证号的时候把它倒过来存,每次查询的时候可以这么写:

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

第二种方式是使用 hash 字段。可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引。

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

然后每次插入新记录的时候,都同时用 crc32() 这个函数得到校验码填到这个新字段。由于校验码可能存在冲突,也就是说两个不同的身份证号通过 crc32() 函数得到的结果可能是相同的,所以查询语句 where 部分要判断 id_card 的值是否精确相同。

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

由于索引是整数字段,所以只需要占用四个字节。

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

它们的不同点主要有:

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注