MySQL——怎么给字符串字段加索引

news2024/10/6 22:25:13

现在,几乎所有的系统都支持邮箱登录,如何在邮箱这样的字段上建立合理的索引,是本篇文章要讨论的问题。

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

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

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

select f1, f2 from SUser where email='xxx';

如果 email 这个字段上没有索引,那么这个语句就只能做全表扫描

同时,MySQL 是支持前缀索引的,也就是说,你可以定义字符串的一部分作为索引。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。

比如,这两个在 email 字段上创建索引的语句:

mysql> alter table SUser add index index1(email);
#或者是
mysql> alter table SUser add index index2(email(6));

 第一个语句创建的 index1 索引里面,包含了每个记录的整个字符串;而第二个语句创建的 index2 索引里面,对于每个记录都是只取前 6 个字节。

如下面两图所示,就是这两个索引的示意图。

 

 可以看到,由于 email(6) 这个索引结构中每个邮箱字段都只取前 6 个字节(即:zhangs),所以占用的空间会更小,这就是使用前缀索引的优势。但,这同时带来的损失是,可能会增加额外的记录扫描次数。以以下这条扫描语句为例子:

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

如果使用的是 index1(即 email 整个字符串的索引结构),执行顺序是这样的:

  1. 从 index1 索引树找到满足索引值是’zhangssxyz@xxx.com’的这条记录,取得 ID2 的值;

  2. 到主键上查到主键值是 ID2 的行,判断 email 的值是正确的,将这行记录加入结果集;

  3. 取 index1 索引树上刚刚查到的位置的下一条记录,发现已经不满足 email='zhangssxyz@xxx.com’的条件了,循环结束。

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

如果使用的是 index2(即 email(6) 索引结构),执行顺序是这样的:

  1. 从 index2 索引树找到满足索引值是’zhangs’的记录,找到的第一个是 ID1;

  2. 到主键上查到主键值是 ID1 的行,判断出 email 的值不是’zhangssxyz@xxx.com’,这行记录丢弃;

  3. 取 index2 上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出 ID2,再到 ID 索引上取整行然后判断,这次值对了,将这行记录加入结果集;

  4. 重复上一步,直到在 idxe2 上取到的值不是’zhangs’时,循环结束。

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

可以发现,使用前缀索引后,可能会导致查询语句读数据的次数变多。但是,对于这个查询语句来说,如果你定义的 index2 不是 email(6) 而是 email(7),也就是说取 email 字段的前 7 个字节来构建索引的话,即满足前缀’zhangss’的记录只有一个,也能够直接查到 ID2,只扫描一行就结束了。也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。

就有个问题:当要给字符串创建前缀索引时,有什么方法能够确定我应该使用多长的前缀呢?

实际上,我们在建立索引时关注的是区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少

因此,我们可以通过统计索引上有多少个不同的值来判断要使用多长的前缀。首先,你可以使用下面这个语句,算出这个列上有多少个不同的值:

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。

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

使用前缀索引可能会增加扫描行数,这会影响到性能。其实,前缀索引的影响不止如此,我们再看一下另外一个场景。

有如下的SQL语句: 

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

与前面例子中的 SQL 语句:

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

相比,这个语句只要求返回 id 和 email 字段。所以,如果使用 index1(即 email 整个字符串的索引结构)的话,可以利用覆盖索引,从 index1 查到结果后直接就返回了,不需要回到 ID 索引再去查一次。而如果使用 index2(即 email(6) 索引结构)的话,就不得不回到 ID 索引再去判断 email 字段的值

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

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

其他方式

对于类似于邮箱这样的字段来说,使用前缀索引的效果可能还不错。但是,遇到前缀的区分度不够好的情况时(例如身份证,前6位代表来自同一个地方的人,区分度很低),那要怎么办呢?

按照我们前面说的方法,可能你需要创建长度为 12 以上的前缀索引,才能够满足区分度要求。

但是,索引选取的越长,占用的磁盘空间就越大,相同的数据页能放下的索引值就越少,搜索的效率也就会越低

如果我们能够确定业务需求里面只有按照身份证进行等值查询的需求就可以考虑既可以占用更小的空间,也能达到相同的查询效率的方法:

倒序存储

如果你存储身份证号的时候把它倒过来存,每次查询的时候,你可以这么写:

select field_list from t where id_card = reverse('input_id_card_string');

由于身份证号的最后 6 位没有地址码这样的重复逻辑,所以最后这 6 位很可能就提供了足够的区分度。

使用hash字段

可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引:

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

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

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

两种方法的异同点:

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

它们的区别,主要体现在以下三个方面:

  1. 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而 hash 字段方法需要增加一个字段。当然,倒序存储方式使用 4 个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个 hash 字段也差不多抵消了。

  2. 在 CPU 消耗方面,倒序方式每次写和读的时候,都需要额外调用一次 reverse 函数,而 hash 字段的方式需要额外调用一次 crc32() 函数。如果只从这两个函数的计算复杂度来看的话,reverse 函数额外消耗的 CPU 资源会更小些

  3. 从查询效率上看,使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。

总结:

字符串字段创建索引的场景可以使用的方式有:

  1. 直接创建完整索引,这样可能比较占用空间;

  2. 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引;

  3. 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;

  4. 创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都不支持范围扫描。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/86413.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive 分析银行转账风险

文章目录创建数据源基于转账记录,计算 7 天内各个账号的转账金额找出相同收付款人 5 天内连续转账3次或以上的记录创建数据源 数据来源:数据源 drop table transfer_log;CREATE TABLE transfer_log ( log_id bigint, -- idlog_ts timestamp, -- 操作时…

128-152-spark-核心编程-源码

128-spark-核心编程-源码(主要以了解基本原理和流程为主): 总体相关 ​ 1.环境准备(Yarn 集群) ​ (1) Driver, Executor ​ 2.组件通信 ​ (1) Driver > Executor ​ (2) Executor > Driver ​ (3) Executor > Executor ​ 3.应用…

Unity异步加载AB包

Unity异步加载AB包写在前面效果关键讲解项目地址写在后面写在前面 最近项目需要在Unity中完成一个非常耗时的工作,所以学习了下异步加载的流程,这里做了一个demo,异步加载AB包,其实异步加载场景等,原理差不多。 效果…

Tomcat的Maven插件使用方法(在idea里面运行Tomcat)

目录 一、概述 二、下载和导入插件 三、测试使用方式 四、总结 一、概述 使用这个插件可以快速的运行Tomcat,比在本地配置快得多。 二、下载和导入插件 1.下载插件Maven Helper ps:已经有下载过这个插件的可以跳过此步骤 (1&#xff…

一、导论——可解释性机器学习(DataWhale组队学习)

目录导言一、什么是可解释人工智能?二、学可解释机器学习有什么用?2.1学习可解释机器学习的原因2.2 Machine Teaching :人工智能教人类学习2.3 细粒度图像分类2.4前沿AI三、本身可解释性好的机器学习模型四、传统机器学习算法的可解释性分析五、卷积神经网络的可解释性分析5.…

前端基础(二)_HTML常用标签(块级标签、行级标签、行块级标签)

HTML常用标签 我们可以分为三类&#xff1a; 1.块级标签 2.行级标签 3.行块级标签 一、块级标签 1.1 h系类标签 标题标签 H1~h6 大到小 H1 在同一个页面中只能使用一次 其他标签可以重复 特点&#xff1a;默认宽度100% 高度自适应 独立成行 自带间距加粗 <body><…

java计算机毕业设计ssm制造型企业仓储管理系统i0180(附源码、数据库)

java计算机毕业设计ssm制造型企业仓储管理系统i0180&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&…

[附源码]计算机毕业设计的项目管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis MavenVue等等组成&#xff0c;B/S模式…

基于小波变换的图像压缩解压缩的matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 图像压缩的类别 对于图像压缩&#xff0c;主要有两类方法&#xff1a;无损的图像压缩以及有损的图像压缩&#xff0c;分别称为lossless image compression and lossy image compression。 对于无…

[附源码]Node.js计算机毕业设计动漫网站Express

项目运行 环境配置&#xff1a; Node.js最新版 Vscode Mysql5.7 HBuilderXNavicat11Vue。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等等。 环境需要 1.运行环境&#xff1a;最好是Nodejs最新版&#xff0c;我…

第37篇 网络(七)TCP(一)

导语 TCP即TransmissionControl Protocol&#xff0c;传输控制协议。与UDP不同&#xff0c;它是面向连接和数据流的可靠传输协议。也就是说&#xff0c;它能使一台计算机上的数据无差错的发往网络上的其他计算机&#xff0c;所以当要传输大量数据时&#xff0c;我们选用TCP协议…

ssm+Vue计算机毕业设计校园生活服务预约管理系统(程序+LW文档)

ssmVue计算机毕业设计校园生活服务预约管理系统&#xff08;程序LW文档&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;…

spring切入点表达式(一)

前面写到切入点表达式&#xff0c;如果把全部方法都作为切入点的话&#xff0c;用execution(* *(..))表达式&#xff0c;这个表达式代表什么意思呢&#xff1f; public void login (String name,String address){} * * ( . . ) * *(..)对应方法如上图 * -------->代表修…

【GRU回归预测】基于鲸鱼算法优化门控循环单元WOA-GRU神经网络实现多输入单输出回归预测附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

网络协议—应用层的HTTPS协议

用 HTTP 协议&#xff0c;看个新闻还没有问题&#xff0c;但是换到更加严肃的场景中&#xff0c;就存在很多的安全风险。例如&#xff0c;你要下单做一次支付&#xff0c;如果还是使用普通的 HTTP 协议&#xff0c;那你很可能会被黑客盯上。例如在点外卖的环境中&#xff0c;发…

面试题 —— 真实面试题分享

文章目录 一、对BFC的理解。二、CSS中”::“和”:”的区别&#xff1f;三、对vue生命周期的理解&#xff1f; 四、vue组件通信的方式 五、vue中给data中的对象添加一个新的属性会发生什么&#xff0c;如何解决? 六.微信小程序组件的生命周期 七、javascript原型与继承的理解…

【vue基础】关于组件之间的通信

目录 &#xff08;1&#xff09;父组件向子组件传递信息 1.props&#xff1a; 2.第二种是直接从子组件里面利用&#xffe5;parent和root引用&#xff0c;获取根组件和父组件中的数据 &#xff08;2&#xff09;子组件数据传入父组件 1.通过自定义事件 2.通过$refs引用集合…

Docker-自定义镜像上传阿里云

目录 一、Docker制作jdk镜像 jdkv.1.0的制作 步骤 二&#xff0c;alpine制作jdk镜像 2.1 alpine Linux简介 2.2 基于alpine制作JDK8镜像 前期准备 2.3步骤 2.3.1.下载镜像 2.3.2.创建并编辑dockerfile 2.3.3.执行dockerfile创建镜像 2.3.4.创建并启动容器(可略) 2.3.5.进…

考虑碳交易机制的园区综合能源系统电热协同运行优化研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

【加油站会员管理小程序】05 充值套餐功能

上一篇我们讲解了轮播图的开发,本篇我们讲解充值优惠功能的开发。 开发之前我们要思考如何展示信息,在我们的功能规划里,在首页部分是要展示三个充值的套餐信息。那这个套餐呢最好是存在数据源中,便于日后维护信息。 在应用的编辑器里,点击数据源的图标,我们创建一个数…