• MySQL 45 讲 | 11 怎么给字符串字段加索引?


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

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

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

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

      mysql> alter table SUser add index index1(email);
      mysql> alter table SUser add index index2(email(6));
      
      • 1
      • 2
      • index 1 使用email全部字段创建索引,查找时回全表扫描
      • index 2 使用email的前6个字段创建前缀索引, 占用的空间更小,但会增加额外的记录扫描次数 。
    • 这两个索引定义下分别执行下面查询语句:

      select id,name,email from SUser where email='zhangssxyz@xxx.com';
      
      • 1
      • 使用的是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’时,循环结束

    使用前缀索引,定义好长度,就可以既节省空间,又不用额外增加太多的查询成本

    怎样确定应该使用多长的前缀呢?

    区分度,区分度越高越好

    • 可以使用下面这个语句,算出这个列上有多少个不同的值
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ​ 依次选取不同长度的前缀,使用前缀索引很可能会损失区分度,所以需要预先设定一个可以接受的损失比例,比如 5%。然后,在返回的L4~L7中,找出不小于 L * 95%的值,假设这里L6、L7都满足,你就可以选择前缀长度为6

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

    下面两个SQL语句:

    select id,email from SUser where email='zhangssxyz@xxx.com';
    select id,name,email from SUser where email='zhangssxyz@xxx.com';
    
    • 1
    • 2
    • 第一个语句只要求返回id和email字段。
    • 如果使用index1(即email整个字符串的索引结构)的话,可以利用覆盖索引,从index1查 到结果后直接就返回了,不需要回到ID索引再去查一次。
    • 如果使用index2(即email(6)索引结构)的话,不得不回到ID索引再去判断email字段的值 。

    使用前缀索引就用覆盖索引对查询性能的优化了。

    前缀的区分度不够好的情况时,怎么办呢?

    ​ 比如,我们国家的身份证号,一共18位,其中前6位是地址码,所以同一个县的人的身份证号前 6位一般会是相同的。还有没有别的处理 方法呢?这种方法,既可以占用更小的空间,也能达到相同的查询效率。

    • 倒序存储 : 存储身份证号候把它倒过来存,每次查询的时候,可以这么写:
    select field_list from t where id_card = reverse('input_id_card_string');
    
    • 1

    使用 reverse() 函数把数字倒过来,可以用最后6位的区分度创建前缀索引,使用count(distinct)方法去做个验证 。

    • hash字段:在表上再创建一个整数字段,来保存身份证的校验码, 同时在这个字段上创建索引
    alter table t add id_card_crc int unsigned, add index(id_card_crc);
    
    select field_list from t where id_card_crc=crc32('input_id_card_string') and id_card='input_id_card_string'
    
    • 1
    • 2
    • 3

    crc32()这个函数得到校验码填到这个新字段

    倒序存储和使用hash字段的异同点

    • 相同点: 都不支持范围查询。 倒序存储不支持范围查询;hash字段也只能支持等值查询。

    • 不同点:

      • 额外空间: 倒序存储方式在主键索引上,不会消耗额外的存储空间;而hash字段方法需要增加一个字段。
      • CPU消耗: 倒序方式每次写和读时,都需要额外调用一次reverse函数;而hash 字段的方式需要额外调用一次crc32()函数。reverse函数额外消耗的CPU资源会更小些 。
      • 查询效率 : hash字段方式的查询性能相对更稳定一些,可以认为每次查询的平均扫描行数接近1 ;倒序存储 方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。

    小结

    字符串字段创建索引的场景

    • 创建完整索引,比较占用空间
    • 创建前缀索引,节省空间,但会增加查询扫描次数,并且能使用覆盖索引
    • 倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题,不支持范围扫描
    • hash字段索引,查询性能稳定,有额外的存储和计算消耗,不支持范围扫描
  • 相关阅读:
    【数据分享】2000-2022年全球范围500m分辨率类NPP-VIIRS夜间灯光数据
    安装 DolphinDB Python API
    java毕业生设计药房药品采购集中管理系统计算机源码+系统+mysql+调试部署+lw
    在IE浏览器下fixed定位容器随着滚动条出现抖动问题(实测有效)
    STM32H750之FreeRTOS学习--------(六)FreeRTOS的列表和列表项
    RadonDB MySQL Kubernetes 2.2.1 发布!
    scrapy基本使用
    log4j2安全漏洞修复
    【算法】【递归与动态规划模块】跳跃游戏
    TDengine函数大全-时序库特有函数
  • 原文地址:https://blog.csdn.net/weixin_43806049/article/details/126131522