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)

大家都在看

  • 8、IDEA提交代码出现: Fetch failed fatal: Could not read from remote repository

    转载自 第一步、确认Git公钥/密钥是否生成: 1、 首先查看本地是否生成git密钥,一般在C盘home目录下:【C:你自己的home目录.ssh】 第二步:添加Git密钥: 右键…

    数据库 2023年6月6日
    0101
  • JavaScript进阶知识点——函数和对象详解

    我们在上期内容中学习了JavaScript的基本知识点,今天让我们更加深入地了解JavaScript JavaScript函数 JavaScript函数和Java函数是有一部分相似…

    数据库 2023年6月14日
    094
  • jupyter使用虚拟环境

    为了在jupyter中使用pyTorch的虚拟环境,来记录一下怎么操作一、conda命令的使用因为使用的是jupyter,所有就使用Anaconda Prompt来创建虚拟环境(也…

    数据库 2023年6月14日
    086
  • 计算机组成原理——组成篇

    计算机的总线 1 、总线的作用及概述 解决设备间通讯问题。 总线是为了解决不同设备之间的通信问题。 总线可以连接CPU和IO设备。 2 、总线分类:片内总线、系统总线 片内总线:连…

    数据库 2023年6月16日
    066
  • 3 访问修饰符public,private,protected以及不写(默认)时的区别

    private 私有的,只对本类公开。 default 类的成员不写访问修饰符时默认为default,默认对于同一个包中的其它类相当于公开(public),对于不是同一个包中的其它…

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

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

    数据库 2023年6月16日
    094
  • mydumper备份mysql8.0 sql thread被kill掉

    这个问题是很久以前的事了。今天,我看了看我的笔记,发现了这个问题。我当时没有仔细看过它。我现在就想复制它。 [En] This problem was a long time ag…

    数据库 2023年5月24日
    0117
  • 2_JDBC

    使用客户端工具访问数据库, 需要手工建立连接, 输入用户名和密码登陆, 编写SQL语句, 点击执行, 查看操作结果(结果集或受行数影响) 在实际开发中, 当用户的数据发生改变时, …

    数据库 2023年6月11日
    066
  • SQL语句的整合

    基础语法 https://blog.csdn.net/m0_37989980/article/details/103413942 CRUD 提供给数据库管理员的基本操作,CRUD(…

    数据库 2023年5月24日
    0113
  • idea在git中拉去eclipse项目及配置环境

    1、VCS—git from version Control url:git@10.11.xx.xx:xpress 项目源码存储位置随意 clone 2、引入jar包 …

    数据库 2023年6月9日
    0100
  • 【黄啊码】MySQL入门—1、SQL 的执行流程

    大家好!我是黄啊码,鉴于大家对于学习的热情,从今天起,将连载mysql的相关知识,需要学习的可以注意我的更新学习,后期估计会开启付费专栏,但当前完全可以白嫖,希望大家珍惜! 首先我…

    数据库 2023年6月16日
    086
  • 链表的知识总结

    链式结构内存不连续的,而是一个个串起来的,每个链接表的节点保存一个指向下一个节点的指针。 ⭐ 链式结构包含:node(节点)还有value(值),由于内存不连续的,那么对于数据的插…

    数据库 2023年6月16日
    096
  • MySQL8.0 DDL原子性特性

    1. DDL原子性概述 8.0之前并没有统一的数据字典dd,server层和引擎层各有一套元数据,sever层的元数据包括(.frm,.opt,.par,.trg等),用于存储表定…

    数据库 2023年6月9日
    062
  • CMU 15-445 Project 0 实现字典树

    原文链接:https://juejin.cn/post/7139572163371073543 项目准备 代码、手册 本文对应 2022 年的课程,Project 0 已经更新为实…

    数据库 2023年6月14日
    0102
  • RadonDB MySQL on K8s 2.1.4 发布

    RadonDB MySQL Kubernetes 于 4 月 7 日正式发布新版本 2.1.4。该版本主要对可用性进行了优化,新增中英文文档,并修复一些问题。 致谢 首先感谢 @a…

    数据库 2023年6月11日
    084
  • go 切片的扩容

    slice type slice struct { array unsafe.Pointer len int cap int } func makeslice(et *_type,…

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