• 唯一索引、普通索引


    唯一索引、普通索引



    前言

    提到唯一索引和普通索引,相信大家都不陌生,当同事小姐姐问你这俩有什么区别时?或许你会脱口而出:“这还用问?见名知意啊,一个是允许字段重复,一个不允许存在重复数据!”

    一、在SELECT中,唯一索引和普通索引的区别

    本文测试引擎选择我们最常用的InnoDB,版本为MySQL8.0;

    假设,执行查询的语句是:

    select id from T where id_card = 666;
    
    • 1

    我们知道,MySQL的InnoDB采用的是B+树实现的索引结构,查找过程从B+树的树根起,按层搜索到666所在的叶子节点,然后取出该节点所在的数据页,把数据页读到内存后,通过二分法在数据页中定位id_card=666的行数据。

    在这里插入图片描述
    B+ 树的查找过程如上图:

    1. 将磁盘块1从磁盘加载到内存,发生一次IO ,在内存中使用二分查找方式找到 666 在600和700 之间,锁定磁盘块1的P2 指针。
    2. 通过磁盘块1 的 P2 指针地址把磁盘块3 加载到内存,发生第二次IO ,锁定磁盘块3 的 P2 指针
    3. 通过磁盘块3 的P2指针加载磁盘块7到内存,发生第三次 IO,同时根据二分查找找到666 查询结束。

    普通索引和唯一索引的定位方式:

    • 普通索引: 查到第一条id_card=666 后,然后继续往后查找直到碰到第一个 id_card<>666 的记录时,结束。
    • 唯一索引: 由于索引定义了唯一性,查找到第一个满足条件的记录后,直接结束。

    有同学问我了:普通索引为什么要继续向下查找?继续向下查找的原因是由于普通索引允许重复值,且B+Tree是天然有序的。SQL中并没有指定limit 1,所以他还要往下查,看是否有同条件的数据一起返回,直到查到第一条不满足条件的数据为止。

    两者在查询方面的性能差距微乎其微。对于普通索引多的那一次操作,因为本身就是以数据页为单位读进内存,数据页大小默认16KB(大概1000行),要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。当然,不可避免查询的数据是该数据页的最后一位,这样还要再读下一块数据页,算法会复杂一些。

    二、在DML中,唯一索引和普通索引的区别

    这是本篇文章的重点,在看之前,我们需要先了解什么是change buffer

    了解MySQL机制的同学们知道,当执行 DML(INSERT、UPDATE、DELETE)等操作时,InnoDB会利用 change buffer进行加速写操作,可以将写操作的随机磁盘访问调整为局部顺序操作,而在机械硬盘时代,随机磁盘访问(随机I/O)也是数据库操作中的最耗性能的硬伤。当普通索引(非唯一索引)的数据页发生写操作时,把操作内容写到内存中的change buffer后就可以立刻返回(执行完成)了。

    这里我以UPDATE操作为例,当需要更新某一行数据时,会先判断该行所在数据页是否在内存中,如果在就直接在内存数据页中更新,如果这个数据页没有内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些UPDATE操作缓存在 change buffer 中,这样就不需要从磁盘读入数据页,当有SQL查询需要访问这个数据页的数据时,将数据页读入内存后,然后先执行 change buffer 中与这个页的相关UPDATE操作,通过这种方式保证这个数据页的逻辑正确性。

    在这里插入图片描述
    可见,change buffer是会被从内存持久化到磁盘中的,将 change buffer 中的操作应用到原数据页,得到最新结果的过程被称为 merge。除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作,相当于刷脏页啦(把已修改的数据更新到实际数据文件中)。

    触发merge的操作主要有以下几种(你该记住的点):

    • 有SQL线程访问这个数据页;
    • master thread线程每秒或每10秒进行一次merge change buffer的操作;
    • 在数据库正常关闭的时候。

    言归正传,上文提到普通索引(非唯一索引)会使用到change buffer进行加速写操作,你是不是已经get到点了呢~

    唯一索引不会使用 Change buffer ,如果索引设置了唯一属性,在进行插入或者修改操作时,InnoDB 必须进行唯一性检查,如果不读取索引页到缓冲池,无法校验索引是否唯一,如果都把索引页读到内存了,那直接更新内存会更快,就没必要使用change buffer了。

    • 对于普通索引(非唯一索引)的DML操作来说,当待更新的数据页在内存中时,找到前值和后值的区间插入即可;当待更新的数据页在不在内存中时,直接把操作写到Change buffer就完事儿了。舒服!
    • 而对于唯一索引,当待更新的数据页在不在内存中时,索引每次都得把数据页读到内存中判断唯一性,将数据从磁盘读入内存涉及大量随机IO的访问,慢的一批,当遇到高频写操作时??唉,别想了,难受!

    到这里,相信你对普通索引和唯一索引的取舍有了一定的概念,普通索引和唯一索引在查询能力上是没差别的,主要考虑的是更新的影响。还得结合实际业务场景来判断,如果是读取远大于更新和插入的表,唯一索引和普通索引都可以,但是如果业务需求相反,个人觉得应该使用普通索引,当然如果是那种更新完要求立即可见的需求,就是刚更新完就要再查询的,这种情况下反而不推荐普通索引,因为这样会频繁的产生merge操作,起不到change buffer的作用,反而需要额外空间来维护change buffer就有点得不偿失了。

    当我们使用普通索引,尤其在使用机械盘的场景下,尽量把change buffer开大从而确保数据的写入速度。最后,通过列举一下 change buffer 的配置,结束今天的分享。

    三、change buffer 配置

    innodb_change_buffer_max_size% 配置写缓冲的大小,占整个缓冲池的比例,默认值是25%,可以通过修改该值提高InnoDB写效率,最大值是50%。

    mysql> show variables like '%innodb_change_buffer_max_size%';
    +-------------------------------+-------+
    | Variable_name                 | Value |
    +-------------------------------+-------+
    | innodb_change_buffer_max_size | 25    |
    +-------------------------------+-------+
    1 row in set (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    innodb_change_buffering配置是否缓存辅助索引页的修改,默认为 all,即缓存INSERT/DELETE/UPDATE等DML操作。

    mysql> show variables like '%innodb_change_buffering%';
    +-------------------------+-------+
    | Variable_name           | Value |
    +-------------------------+-------+
    | innodb_change_buffering | all   |
    +-------------------------+-------+
    1 row in set (0.00 sec)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

  • 相关阅读:
    数据集笔记:GeoLife GPS 数据 (user guide)
    LoRaWan模块应用于智慧城市景观灯
    数据治理-数据管理角色
    Linux篇13动静态库
    django小区居民出入申报系统Vue+flask疫情防控社区疫苗预约系统python
    216. 组合总和 III
    ES6 - 扩展运算符与Object.assign对象拷贝与合并
    tiup update
    【C++进阶之路】第十篇:C++的类型转换
    Linux系统高并发socket最大连接数所受的各种限制解决
  • 原文地址:https://blog.csdn.net/weixin_47410172/article/details/127805895