vlambda博客
学习文章列表

MySql 你真的会使用字符串索引吗?

志在峰巅的攀登者,不会陶醉在沿途的某个脚印之中,在码农的世界里,优美的应用体验,来源于程序员对细节的处理以及自我要求的境界,年轻人也是忙忙碌碌的码农中一员,每天、每周,都会留下一些脚印,就是这些创作的内容,有一种执着,就是不知为什么,如果你迷茫,不妨来瞅瞅码农的轨迹。



我们这里有一张用户表,建表语句如下

CREATE TABLE `t_user`( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID' , `id_card` VARCHAR(32) DEFAULT NULL COMMENT '用户身份证' , `u_name` VARCHAR(32) DEFAULT NULL COMMENT '用户姓名' , `u_phone` VARCHAR(32) DEFAULT NULL COMMENT '用户电话' , `u_password` VARCHAR(200) DEFAULT NULL COMMENT '用户密码' , `u_age` INT(11) DEFAULT NULL COMMENT '用户年龄' , `u_male` TINYINT(1) DEFAULT NULL , PRIMARY KEY(`id`) , KEY `id_card`(`id_card`) , KEY `u_phone`(`u_phone`) ) ENGINE = INNODB


这个表中有三个索引 id 的主键索引、id_card 的身份索引、手机号的索引。


1 字符串 前缀索引


我们在实际业务开发中,会有手机号密码登录的功能,所以会常有查询语句如下:

select * from t_user where u_phone='xxx' and u_password='xxx';

所以我们在 u_phone 上添加了索引,如果不添加索引,这个查询就会走全表扫描,当用户量足够大时,这个查询会足够的慢。


在这里我们为 u_phone 添加的索引,默认是11位长度的(因为手机号一般是11位),MySQL 是支持前缀索引的,所以可以考虑定义字符串的一部分作为索引,如这里我们把 u_phone 原来 11位的长度索引修改为 4 位长度的索引:


#先删除索引ALTER TABLE t_user DROP INDEX u_phone;#再修改alter table t_user add index u_phone2(u_phone(4));


2 普通索引与前缀索引 查询过程分析


如执行以下查询语句

select * from t_user where u_phone='13309090909'

如果我们添加的是普通索引如下:

alter table t_user add index u_phone('u_phone');

那么在执行查询过程:

  1. 从 u_phone 索引树找到满足索引值是’13309090909’的这条记录对应的ID

  2. 然后回表到主键索引上 获取对应ID的这一条数据

  3. 然后在 u_phone 索引树取刚刚查到的位置的下一条记录,判断不浪花兄弟条件,循环结束。


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

如果我们使用的是前缀索引,如下

alter table t_user add index u_phone2(u_phone(4));

那么在执行查询过程:

  1. 从 u_phone2 索引树找到满足条件的第一个记录ID;

  2. 然后再去 u_phone2 索引树找到满足条件 ‘1330’ 的ID,然后回表到ID 索引上取整行然后判断,这次值对了,将这行记录加入结果集;

  3. 重复上一步,直到在 u_phone2 上取到的值不是’1330’时,循环结束。


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


MySql 你真的会使用字符串索引吗?

当我们再 u_phone 字段的前5个字节或者是前6个字节来构建索引,查询次数 可能不一样,也就是使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询 成本。

关键就是定义前缀索引的长度的权衡问题


3 前缀索引的长度的权衡


我们可以先查询这个表中不同手机号码的用户数量

select count(distinct mobile) as L from tb_user;

比如我这个测试表中的查询结果如下:

然后,依次选取不同长度的前缀来看这个值对比一下不同数据的数量:

SELECT count(DISTINCT LEFT(mobile , 4)) AS L4 , count(DISTINCT LEFT(mobile , 5)) AS L5 , count(DISTINCT LEFT(mobile , 6)) AS L6 , count(DISTINCT LEFT(mobile , 7)) AS L7FROM tb_user


如果选用前7个字节构建索引数据结构,重复的索引值有 190-124 = 56个,也就是说当查询到前缀正好在这56个重复索引中,需要回表查询56次进行判断。


如果选用前6个字节构建索引数据结构,重复的索引值有 190-78 = 112个,也就是说当查询到前缀正好在这112个重复索引中,需要回表查询112次进行判断。


所以需要选择 前缀7个字节。




完毕

不局限于思维,不局限语言限制,才是编程的最高境界。