MySQL实战45讲 11

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

Q:如何在邮箱这样的字段上建立合理的索引?

用户表的定义:

create table SUser(
ID bigint unsigned primary key,
email varchar(64),
...

)engine=innodb;

由于您将使用邮箱登录,因此业务代码中必须出现类似以下内容的语句:

[En]

Because you are going to log in using a mailbox, a statement similar to this must appear in the business code:

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

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

A:

前缀索引

MySQL 是支持前缀索引的,也就是说,可以定义 字符串的一部分作为索引。

默认情况下,如果创建索引的语句没有指定前缀长度,则索引将包含整个字符串。

[En]

By default, if the statement in which you create the index does not specify a prefix length, the index will contain the entire string.

alter table SUser add index index1(email);

alter table SUser add index index2(email(6));

第一个语句创建的 index1 索引里面,包含了每个记录的整个字符串;

而第二个语句创建的 index2 索引里面,对于每个记录都是只取前 6 个字节。

数据结构和存储方面的差异:

[En]

Differences in data structure and storage:

MySQL实战45讲 11

MySQL实战45讲 11

由于 email(6) 这个索引结构中每个邮箱字段都只取前 6 个字节(即:zhangs),所以 占用的空间会更小,这就是使用前缀索引的优势。

与此同时,损失在于它可能会增加额外的记录扫描次数。

[En]

At the same time, the loss is that it may increase the number of additional record scans.

执行上面的select时:

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

  1. 从 index1 索引树找到满足索引值是’zhangssxyz@xxx.com’的这条记录,取得 ID2 的值;
  2. 到主键上查到主键值是 ID2 的行,判断 email 的值是正确的,将这行记录加入结果集;
  3. 取 index1 索引树上刚刚查到的位置的下一条记录,发现 已经不满足 email=’zhangssxyz@xxx.com’的条件了,循环结束。

在此过程中,您只需要返回到主键检索数据一次,因此系统认为只扫描了一行。

[En]

In this process, you only need to go back to the primary key to retrieve the data once, so the system thinks that only one row has been scanned.

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

  1. 从 index2 索引树找到满足索引值是’zhangs’的记录,找到的第一个是 ID1;
  2. 到主键上查到主键值是 ID1 的行,判断出 email 的值不是’zhangssxyz@xxx.com’,这行记录丢弃;
  3. 取 index2 上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出 ID2,再到 ID 索引上取整行然后判断,这次值对了,将这行记录加入结果集;
  4. 重复上一步, 直到在 idxe2 上取到的值不是’zhangs’时,循环结束

因此,使用前缀索引可能会导致查询语句读取数据的次数更多。

[En]

Therefore, the use of prefix indexes may result in more times for query statements to read data.

使用多长的前缀呢?

在建立指数时,要注意分化程度,分化程度越高越好。因为分化程度越高,重复键值就越少。

[En]

When establishing the index, we should pay attention to the degree of differentiation, and the higher the degree of differentiation, the better. Because the higher the degree of differentiation, the fewer repeated key values.

以通过 统计索引上有多少个不同的值来判断要使用多长的前缀。

首先计算出此列中有多少个不同的值:

[En]

First figure out how many different values there are on this column:

select count(distinct email) as L from SUser;

然后,选择不同长度的前缀以查看此值:

[En]

Then, select the prefixes of different lengths to look at this value:

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;

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

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

使用index1,由于通过email可以直接找到id,即覆盖索引,不需要回表。

使用index2(即 email(6) 索引结构)的话,就不得不回到 ID 索引再去判断 email 字段的值。

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

前缀索引就用不上覆盖索引对查询性能的优化

其他方式

Q:遇到前缀的区分度不够好的情况时,要怎么办?例如身份证号码?

我希望它不仅能占用更少的空间,还能达到同样的查询效率。

[En]

I hope it can not only occupy less space, but also achieve the same query efficiency.

A:

第一种方式是使用倒序存储。如果你存储身份证号的时候把它倒过来存,每次查询的时候,你可以这么写

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 – 计网数据库中常使用 返回字符串的 32 位循环冗余校验值。

由于校验码可能存在冲突,也就是说两个不同的身份证号通过 crc32() 函数得到的结果可能是相同的,所以你的查询语句 where 部分要判断 id_card 的值是否精确相同

使用倒序存储和使用 hash 字段这两种方法的异同点

同样的事情是不支持范围查询。

[En]

The same thing is that range queries are not supported.

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

差异主要体现在以下三个方面:

[En]

The differences are mainly reflected in the following three aspects:

  1. 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而 hash 字段方法需要增加一个字段
  2. 在 CPU 消耗方面,倒序方式 每次写和读的时候,都需要 额外调用一次 reverse 函数,而 hash 字段的方式需要 额外调用一次 crc32() 函数
  3. 从查询效率上看, 使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。

总结

  1. 直接创建完整索引,这样可能 比较占用空间
  2. 创建前缀索引,节省空间,但会增加查询扫描次数,并且 不能使用覆盖索引
  3. 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题;
  4. 创建 hash 字段索引,查询性能稳定,有额外的存储和计算消耗,跟第三种方式一样,都 不支持范围扫描

Q:如果你在维护一个学校的学生信息数据库,学生登录名的统一格式是” 学号 @gmail.com

学号规则为:15位,其中前三位为城市号,第四至第六位为校号,第七至第十位为入学年份,后五位为顺序号。

[En]

The rule of the student number is: fifteen digits, of which the first three are the city number, the fourth to the sixth is the school number, the seventh to the tenth is the year of admission, and the last five digits are sequential numbers.

当系统登录时,学生需要输入他们的登录名和密码,并验证他们是正确的,然后才能继续使用系统。如果只考虑登录身份验证的行为,如何设计登录名的索引?

[En]

When the system logs in, students are required to enter their login name and password and verify that they are correct before they can continue to use the system. If you only consider the behavior of login authentication, how do you design the index of the login name?

A:

因为维护的只是 一个学校的,因此前面 6 位(其中,前三位是所在城市编号、第四到第六位是学校编号)其实是 固定的,邮箱后缀都是 @gamil.com,因此可以 只存入学年份加顺序编号,它们的长度是 9 位。

而其实在此基础上,可以用 数字类型来存这 9 位数字。比如 201100001, 这样只需要占 4 个字节。其实这个就是一种 hash,只是它用了最简单的转换规则: 字符串转数字的规则,而刚好我们设定的这个背景,可以保证这个转换后结果的唯一性。

Original: https://www.cnblogs.com/ydssx7/p/16512781.html
Author: ydssx
Title: MySQL实战45讲 11

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/505164/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 1. SQL

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    数据库 2023年6月16日
    076
  • DistSQL 深度解析:打造动态化的分布式数据库

    一、背景 自 ShardingSphere 5.0.0 版本发布以来,DistSQL 为 ShardingSphere 生态带来了强大的动态管理能力,通过 DistSQL,用户可以…

    数据库 2023年6月16日
    063
  • day03-2无异常退出

    多用户即时通讯系统03 4.编码实现02 4.3功能实现-无异常退出系统 4.3.1思路分析 上述代码运行时,在客户端选择退出系统的时候,可以发现程序并没有停止运行,原因是: 退出…

    数据库 2023年6月11日
    073
  • 第十九章 基于注解的AOP编程

    组装切面 将2 3 4步放在切面类中: 定义切面类: @Aspect 定义额外功能: @Around 原始方法的运行: ProceedingJoinPoint.proceed() …

    数据库 2023年6月14日
    071
  • 第十四章 静态代理设计模式

    1.为什么需要代理设计模式 1.1 问题 在javaEE分层开发中,那个层对我们最重要? DAO–>Service–>Controller 在javaEE分层开发中…

    数据库 2023年6月14日
    074
  • django中的模板层简介

    1.什么是模板层 模板层可以根据视图中传递的字典数据动态生产相应的HTML页面 2.模板层的配置 1.在项目&am…

    数据库 2023年6月14日
    066
  • Django设置跨域访问

    Django设置跨域访问 pip install django-cors-headers (2) settings.py 配置如下 INSTALLED_APPS = [ # ‘dj…

    数据库 2023年6月14日
    088
  • Redis+Lua实现简易的秒杀抢购

    1 商品抢购 主要逻辑是:减库存,记录抢购成功的用户 @RestController public class DemoController { @Resource private…

    数据库 2023年6月14日
    0102
  • zabbix模板,角色,用户,权限管理

    用户管理 用户组 用户角色 用户 模板管理 模板组 模板 posted @2022-09-07 22:22 溜溜威 阅读(16 ) 评论() 编辑 Original: https:…

    数据库 2023年6月14日
    095
  • 1. SQL

    404. 抱歉,您访问的资源不存在。 可能是URL不正确,或者对应的内容已经被删除,或者处于隐私状态。 [En] It may be that the URL is incorre…

    数据库 2023年5月24日
    079
  • Pisanix v0.2.0 发布|新增动态读写分离支持

    1.动态读写分离介绍 1.1 介绍 读写分离是业界使用 MySQL 高可用最常用的方案之一,在实际场景中可以提高查询性能,降低服务器负载。本次版本在 v0.1.0 静态规则基础上增…

    数据库 2023年6月16日
    082
  • .NET nhibernate 添加新的表运行报is not mapped的问题

    最后在修改一个.NET nhibernate的项目,按照原来的表添加了一个实体和一个hbm.xml的配置文件,写好所有业务代码以后运行报以下错误 NoAuthorizationSi…

    数据库 2023年6月9日
    078
  • SMBMS(超市订单管理系统)项目从零开始搭建

    如果需要完整的系统可以加我qq:1842329236 一、搭建一个maven web项目 新建一个maven,并且使用模板 maven的详细创建,及配置请看这篇文章https://…

    数据库 2023年6月16日
    073
  • 5000+字硬核干货!Redis 分布式集群部署实战

    原理: Redis集群采用一致性哈希槽的方式将集群中每个主节点都分配一定的哈希槽,对写入的数据进行哈希后分配到某个主节点进行存储。 集群使用公式(CRC16 key)& 1…

    数据库 2023年6月9日
    086
  • 【数据结构】跳表

    一、基本概念 1.1 定义 跳表(SkipList):增加了向前指针的链表叫做指针。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质是一种可以进行二分查找的有序链表。…

    数据库 2023年6月11日
    079
  • MySQL45讲之更新缓存

    本文介绍 MySQL的更新缓存 Change Buffer,以及唯一索引和普通索引如何选择。 唯一索引和普通索引的选择 查询过程 在唯一索引下,查询索引树,找到第一个匹配的行并返回…

    数据库 2023年5月24日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球