数据库索引

索引的作用:

索引类似于书籍目录,可以减少扫描的数据量,提高查询效率。

查询时如果没有索引,就会全表扫描,时间复杂度为On

建立了索引后,可以基于二分查找算法,通过索引快速定位到目标数据,MySQL索引一般是b+树,复杂度为OlogdN,其中d表示节点允许的最大子节点数为d个。

索引的分类:

  • 数据结构:B+tree索引、Hash索引、Full-text索引
  • 物理存储:聚簇索引(主键索引)、二级索引(辅助索引)
  • 字段特性:主键索引、唯一索引、普通索引、前缀索引
  • 字段个数:单列索引、联合索引
按数据结构:
索引类型 InnoDB MyISAM Memory
B+Tree索引 Yes         Yes Yes
Hash索引

No

(不支持hash索引,但会自动为频繁访问的字段创建内存中的Hash索引)

No Yes
Full-text索引 Yes Yes No
按存储内容:
  • 聚簇索引:在InnoDB中,聚簇索引一般是主键索引,叶子节点直接存储完整数据
  • 二级索引(辅助索引):叶子节点一般存放主键值,不存放完整数据

若查询的数据在二级索引中就可以查询到,则无需根据主键回表,这个过程就叫覆盖索引。否则就会根据获取的主键值检索主键索引查询其余需要的数据。

按字段特性:
  • 主键索引:建立在主键字段上的索引,一张表最多一个主键索引,索引列的值不允许为空。
  • 唯一索引:建立在UNIQUE字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但允许为null。
  • 普通索引:建立在普通字段上的索引
  • 前缀索引:对字符类型字段的前几个字符建立的索引,而不是建立在整个字段上,前缀索引可以建立在字段类型为char、varchar、binary、varbinary的列上。
  • 使用前缀索引是为了减少索引占用的存储空间,提升查询效率。
按字段个数:
  • 单列索引:建立在单列上的索引
  • 联合索引:建立在多列上的索引

区分度的定义和计算:

  • 公式​:
    区分度=COUNT(DISTINCT 索引列)/COUNT(*),即字段值的种类除于总行数​
  • 值越接近1,区分度越高(如主键的区分度为1)。
  • 示例​: 
    users表有1000行,email列有950个唯一值,则区分度为950/1000=0.95,适合建索引;而gender列仅2个值(男/女),区分度为2/1000=0.002,效果极差,所以我们一般不为性别字段建立索引。

区分度的作用:

  • 优化器决策:MySQL优先选择高区分度索引检索数据,减少扫描行数。
  • 联合索引设计:高区分度列应该放在左侧。
  • 避免索引失效:低区分度列建索引可能导致优化器放弃使用。

页的结构与功能

  • 物理组成​:
    每个页默认16KB(可配置),包含以下部分

    • 页头(Page Header)​​:存储页类型、页码、前后页指针(双向链表结构)。
    • 用户记录区(User Records)​​:按主键顺序存储数据行(聚簇索引)或主键指针(二级索引),以单向链表组织。
    • 页目录(Page Directory)​​:稀疏索引结构,通过槽(Slot)记录分组内最大键的偏移量,支持二分查找(时间复杂度O(logn))
    • 空闲空间(Free Space)​​:用于新数据插入,减少页分裂频率
  • 逻辑特性​:

    • 叶子节点​:存储实际数据(聚簇索引)或主键值(二级索引),并通过双向链表连接,优化范围查询
    • 非叶子节点​:仅存储键值(子节点的最大值或最小值)子节点指针,作为导航索引

页的动态操作

  • 页分裂(Page Split)​​:
    页空间不足时,InnoDB将页拆分为两半,中间键提升至父节点可能递归触发上层分裂,极端情况下导致树高度增加(如从3层到4层可支持约21亿条记录)

    • 示例​:叶子节点[10, 20, 30]插入25后分裂为[10, 20][25, 30],中间键25复制到父节点
  • 页合并(Page Merge)​​:  
    删除数据后,若相邻页空闲空间超过阈值(默认50%),InnoDB尝试合并页以释放空间

性能优化建议

  • 减少分裂​:避免随机主键(如UUID),采用自增主键保持顺序插入
  • 覆盖索引​:联合索引包含查询字段,避免回表操作
  • 定期维护​:使用OPTIMIZE TABLE减少碎片,降低B+树层级

使用自增主键和UUID主键的区别:

自增主键的值是顺序的,所以InnoDB可以把每一条记录存储到上一条记录的后方,所以自增主键更快的原因:
  • 主键页会近乎顺序地填满和扩充,提升了页面的最大填充率
  • 新插入数据必定在原有的最大数据的后方,mysql定位寻址的速度很快,无需为计算新可插入空间作出额外消耗
  • 减少了页分裂和页碎片的产生。
而UUID一般是非递增的,在有序的B+树中插入数据时,会导致如下后果:

由于UUID无序,新插入的数据需要重新计算可插入的位置和空间,频繁对原有的页进行分裂,导致性能下降。

且UUID太占用内存,每个UUID由36个字符组成,需要从前往后比较。字符串越长性能越差。同时字符串越长,占用的内存越大,而每个页的大小一般是固定的(默认16kb),这样就会导致每个页能存放的关键字数量减少,最终导致索引树变高增加磁盘IO次数,性能变差。

叶子节点中查询具体数据的过程:

数据库索引_第1张图片

页目录用来存储每组最后一条记录的地址偏移量(槽),每个槽相当于指针指向对应分组的最后一条记录

页目录就是由多个槽组成的,槽组相当于分组记录的索引。由于记录是按值从小到大排序的,索引我们通过槽查找记录时,可以用二分法快速定位要查询的记录在哪个槽,定位到槽后再遍历槽内的单向链表找到对应的记录。而无需从最小记录开始遍历整个页中的记录链表。

联合索引:

最左匹配原则:
  • 按照最左优先的方式进行索引的匹配,最左侧的字段全局有序的,而右侧的字段都是左侧字段相同的情况局部有序
  • 查询时where子句的顺序并不重要,只需包含最左侧字段即可。
  • 利用联合索引的前提是索引里的key是有序的,也就是说直接查询右侧的字段是无法利用联合索引的,因为右侧的字段都是局部有序。

索引失效的情况有哪些?

  • 当我们使用后缀或全模糊查询时,也就是like %xx和like %xx%这两种方式会导致索引失效
  • 原因:后缀和全模糊无法确定起始的匹配位置,无法使用索引,且模糊查询结果过于广泛会被优化为全表查询
  • 当我们在查询条件中对索引使用函数或表达式计算,就会导致索引失效
  • 原因:改变了原始值,无法与索引中存储的值进行比较
  • MySQL遇到字符串和数字比较时,会自动把字符串转为数字,然后再进行比较。如果字符串是索引列,而条件语句中的输入参数是数字的话。那么索引列会发生隐式类型转换,通过cast函数实现,相当于对索引列使用了函数,导致索引失效
  • 原因:隐式调用了函数改变了原始值
  • 联合索引的正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引匹配,否则就会导致索引失效。
  • 原因:右侧的字段都是在左侧字段相同的情况下局部有序的,否则全局无序,无法使用索引。
  • 联合索引其中一列使用范围查询或没有作为查询条件,后面的列都会索引失效
  • 原因:破坏了右侧列的有序性
  • 在Where子句中,如果or前的条件列是索引列,而在or后的条件列不是索引列,那么索引会失效
  • 原因:如果其中一个不是索引列,依然要经过全表扫描,不如直接优化为全表扫描。如果两个都是索引列,也需要进行多次查找,可能会被优化器优化为全表扫描。

索引的缺点:

  • 需要占用物理空间,数量越大,占用空间越大;
  • 创建和维护索引需要耗费时间,且随着数据量的增加而增大;
  • 会降低表的增删改效率,每次增删改索引都会导致B+树为了维持索引有序性动态维护;

索引的优化方向:

  1. 前缀索引优化
  2. 覆盖索引优化
  3. 主键优先自增
  4. 防止索引失效

你可能感兴趣的:(数据库,数据库,mysql)