11 - 如何为字符串字段添加索引

关键词

字符串索引、前缀索引

0.引子

在生活中,我们常常需要对一个全是字符串的字段设置索引。例如,你在维护一个支持邮箱登陆的系统,用户表的部分定义如下:

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

由于要使用邮箱登陆,所以在业务中会经常使用到对邮箱的索引,其语句类似下面这样:

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));

这两种选择有何差异呢?下面我们进行分析。

1.全段索引 和 前缀索引

首先,我们看一下两个索引的结构:


11-index1结构.jpg
11-index2结构.jpg

两者的差异如下:

  • 相比于 index1,使用前缀索引的 index2 每个节点只用保存 6 个字节,占用的哦你关键更小。
  • 但是,同时带来的损失是,每个节点保存了更少的信息,因此可能会增加扫描的次数

例如,对下面这个语句,两个索引就会出现不同的执行方式:

select id,name,email from SUser where email='zhangssxyz@xxx.com';
  • 使用 index1,索引只会回表一次。
  • 使用 index2,索引会回表四次。

当然,如果你增加前缀的截取量,可以避免这种情况。如果你在定义 index2 的时候使用 email(7) 而不是 email(6),只需要回表一次。

所以,正确定义好前缀索引的长度,既可以节省空间,又不会增加额外的查询成本。

那么,如果确定前缀长度呢?以上面的例子,你可以用下面的语句查询不同长度下的区别:

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;

查询得到的值越大,它的区分值就越大,你可以用它除以数据总量,得到一个区分度。

除了可能增加扫描次数,使用前缀索引还会带来一些问题。

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

前面我们讲过,MySQL 对索引的优化之一,是可以使用覆盖索引加速查询。但是如果使用前缀索引,就无法使用覆盖索引了。看下面的例子:

select id,email from SUser where email='zhangssxyz@xxx.com';
  • 在这个例子中,如果我们使用 index1 进行查询,可以直接使用覆盖索引,而不必进行回表。
  • 如果我们使用 inxdex2,即使我们定义的时候使用了 email(18)(这差不多的相当于全段保存了)的前缀索引,MySQL 依然会进行回表,因为它无法确定在此之后是否真的完全等于 'zhangssxyz@xxx.com' 。

可以看到,使用前缀索引虽然降低了空间的使用,但是依然有很多的副作用,在使用这种索引的时候你需要考虑这些副作用。

除了前缀索引,还有一些方式可以优化字符串字段的索引。

3.其它方式

3.1倒序存储

手机号码前面的很多位都是为了区分号码归属地的,所以如果对电话号码设置前缀索引,就会涉及到多次回表。解决这种问题的四个思路是,使用倒序存储。
如果你将电话号码倒序存储,在查询的时候可以这么写:

mysql> select field_list from t where id_card = reverse('input_id_card_string');
  • 当然,你需要考虑到使用 reverse 函数的消耗。
3.2使用hash字段

哈希就不多做解释了,对于它的概念读者可以参考我在“数据结构与算法之美”的文集中的相关篇章。
举个例子,如果你的业务中需要使用身份证号,且会有与之相关的大量查询,你可以在表中添加一个字段,并建立索引:

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

在 MySQL 中,crc 函数可以进行哈希计算,我们要在查询和数据插入的时候使用到它。另外,考虑到哈希冲突,在查询语句中需要添加判断。具体查询语句如下:

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

这样,索引的长度变为 4 个字节,小了很多。

3.3以上两种方式的异同

相同:

  • 都无法支持范围查询。范围查询的前置条件是,可以将数据组织成为一个有序的序列(详情请左转
    ),显然上面两种方式都无法创建这样的序列。

差异:

  • 哈希方法会额外添加一个字段,而倒序存储不需要(当然,你需执行字符串翻转这样的骚操作)
  • 两者都会对 CPU 产生消耗,但是具体来看,reverse 函数的消耗比 crc 更小。
  • 使用哈希的查询性能更加稳定,而倒序存储依然可能出现后缀大量重复的情况。

总结

今天我们探究了一些对字符串字段索引的优化方法,具体方式有:

  • 直接创建完整索引,可能比较占用空间。
  • 创建前缀索引,节省空间,但是会增加查询次数,无法使用覆盖索引的优化。
  • 倒序存储,对字符倒序存储,然后再使用前缀索引,绕过前缀区分度过低的问题。
  • 创建哈希字段索引,查询性能稳定,需要额外消耗,不支持范围扫描。

你需要根据业务字段的特点进行选择,或者你可以尝试其它优化方法。

上期问题

在上一篇文章最后,我给你留的问题是,为什么经过这个操作序列,explain 的结果就不对了?这里,我来为你分析一下原因。

delete 语句删掉了所有的数据,然后再通过 call idata() 插入了 10 万行数据,看上去是覆盖了原来的 10 万行。

但是,session A 开启了事务并没有提交,所以之前插入的 10 万行数据是不能删除的。这样,之前的数据每一行数据都有两个版本,旧版本是 delete 之前的数据,新版本是标记为 deleted 的数据。

这样,索引 a 上的数据其实就有两份。

然后你会说,不对啊,主键上的数据也不能删,那没有使用 force index 的语句,使用 explain 命令看到的扫描行数为什么还是 100000 左右?(潜台词,如果这个也翻倍,也许优化器还会认为选字段 a 作为索引更合适)

是的,不过这个是主键,主键是直接按照表的行数来估计的。而表的行数,优化器直接用的是 show table status 的值。

这个值的计算方法,我会在后面有文章为你详细讲解。

思考题

如果你在维护一个学校的学生信息数据库,学生登录名的统一格式是”学号 @gmail.com", 而学号的规则是:十五位的数字,其中前三位是所在城市编号、第四到第六位是学校编号、第七位到第十位是入学年份、最后五位是顺序编号。

系统登录的时候都需要学生输入登录名和密码,验证正确后才能继续使用系统。就只考虑登录验证这个行为的话,你会怎么设计这个登录名的索引呢?


以上就是本节内容,希望对你有所帮助。

注:本文章的主要内容来自我对极客时间app的《MySQL实战45讲》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352