《高性能MySQL》创建高性能的索引

前言

索引是存储引擎用于快速找到记录的一种数据结构

索引基础

  • 索引是在存储引擎层而不是服务层实现
  • 不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引

索引类型

  1. B-Tree 索引

    • 存储引擎以不同的方式使用 B-Tree 索引

      • MyISAM:前缀压缩技术、数据的物理位置引用被索引的行
      • InnoDB:原数据格式进行存储、主键引用被索引的行
    • 所有的值都是按顺序存储的,每一个叶子页到根的距离相同
      image-20190119120347271

    • 叶子节点比较特别,他们的指针指向的是被索引的数据,而不是其他的节点页

      • MyISAM:主键索引与二级索引均是存储着数据的行指针
      • InnoDB:主键索引直接存储着行数据,二级索引存储着主键值
    • B-Tree 索引适用的查询类型

      • 全值匹配
      • 匹配最左前缀
      • 匹配列前缀(like ‘杨东%’
      • 精确匹配某一列并范围匹配另外一列(范围匹配后停止该索引后续列的匹配
      • 只访问索引的查询(索引覆盖扫描
    • B-Tree索引的限制

      • 如果不是按照索引的最左列开始查找,则无法使用索引
      • 不能跳过索引中列(R-Tree 可以
      • 范围查询后,后续列无法使用索引优化查找
    • 可用于排序与分组

    • 索引列的顺序非常非常重要!!!

      扫描二维码关注公众号,回复: 9501468 查看本文章
  2. 哈希索引

    • 基于哈希表实现
    • 必须精确匹配索引所有列
    • 索引自身只存储对应的哈希值,结构紧凑、查询速度非常快
    • 哈希索引的限制
      • 哈希索引只包含哈希值和行指针,不存储字段值,无法使用覆盖索引的特性
      • 非顺序存储、无法用于排序
      • 不支持索引列部分匹配查找
      • 只支持等值比较查询
      • 哈希冲突时,需遍历链表中所有的行指针,直至找到符合条件的行
      • 哈希冲突存在较多时,索引维护成本太高
    • InnoDB 自适应哈希索引
      • 某些索引值被频繁使用时,存储引擎会在内存中基于 B-Tree 索引之上再创建一个哈希索引
      • 查询效率显著提升
      • 完全自动、内部的行为,用户无法控制和配置
      • 可手动关闭
    • 常用哈希函数
      • CRC32:计算出的哈希值较短、若数据表非常大,会出现大量的哈希冲突
      • SHA1、MD5:强加密、最大限度消除冲突,计算出的哈希值更长、浪费空间、比较更慢
      • FNV64:移植自 Percona Server 函数,哈希值为 64 位,速度快、冲突较 CRC32 少很多
  3. 空间数据索引(R-Tree)

    • 从所有维度来索引数据、支持任意维度来组合查询
    • 需使用 GIS 相关函数维护数据
    • 全文索引
      • 查找的是文本中的关键字(MATCH AGAINST),非直接比较
      • 匹配方式和其他几类索引完全不同,比如:停用词、词干和复数、布尔搜索等
      • 允许在相同的列上同时创建全文索引与其他类型的所有

索引的优点

  1. 索引的三个优点
    • 减少了服务器需要扫描的数据量
    • 帮助服务器避免排序和临时表
    • 将随机 I/O 变为顺序 I/O
  2. 索引的三星系统
    • 一星:索引将相关的记录放到一起
    • 二星:索引中的数据顺序和查找中的排列顺序一致
    • 三星:索引中的列包含了查询中需要的全部列(索引覆盖
  3. 对于 TB 级别的数据,经常使用块级别元数据技术来替代索引

高性能的索引策略

  1. 独立的列

    • 含义:指索引列不能是表达式的一部分,也不能是函数的参数
    • Bad Case
      • … where id + 1 = 5
      • … where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10
  2. 前缀索引和索引选择性

    • 选择性
      • 含义:不重复的索引值(基数,cardinality)和数据表的记录总数(#T)的比值
      • 比值越高则查询效率越高,唯一索引的选择性是 1
    • 前缀索引
      • 目的:索引开始的部分字符,节约索引空间,提高索引效率
      • 常用于 BLOB、TEXT 或者很长 VARCHAR 类型的列
    • 选择足够长的前缀以保证较高的选择性
      • 计算完整列的选择性,并使前缀的选择性接近于完整列的选择性
      • 不仅需要看平均选择性,也需考虑最坏情况下的选择性
    • 无法使用前缀索引做 ORDER BY 和 GROUP BY 与 索引覆盖扫描
  3. 多列索引

    • 索引合并(index merge)
      • 含义:同时使用表上的多个单列索引来定位指定的行,并将结果进行合并
      • 算法变种
        • OR 条件的联合(union):需要耗费大量资源在算法的缓存、排序和合并操作
        • AND 条件的相交(intersection):需要一个包含所有相关列的多列索引
        • 组合前两种情况的联合及相交
      • 优化器不会将这些计算到的“查询成本”中,优化器只关心随机页面读取,使查询的成本被低估
      • 可通过 optimizer_switch 关闭索引合并功能
  4. 选择合适的索引列顺序

    • 经验法则:将选择性最高的列放到索引最前列
    • 应该根据那些运行频率最高的查询来调整索引列的顺序
    • 同时还应该考虑排序、分组和范围条件等其他因素
  5. 聚簇索引

    • 聚簇索引并不是一种单独的索引类型,而是一种数据存储方式
    • 聚簇:数据行和相邻的键值紧凑地存储在一起
    • 叶子页包含了行的全部数据,但节点页只包含了索引列
      image-20190119162335694
    • 主要的优点
      • 将相关数据保存在一起
      • 数据访问更快
      • 覆盖索引扫描可直接获取到页节点中的主键值
    • 存在的缺点
      • 提高了 I/O 密集型应用的性能,但如果数据存储在内存中,则不存在什么优势
      • 插入速度严重依赖插入顺序
      • 更新聚簇索引列的代价高
      • 插入新行或更新主键导致移动行时,可能面临“页分裂”,占用更多的磁盘空间
      • 聚簇索引可能导致全表扫描变慢
      • 二级索引因存在行的主键列,可能较大
      • 二级索引访问数据需要两次索引查找(二级索引 -> 主键索引
    • InnoDB 和 MyISAM的数据分布对比
      • MyISAM
        • 按照数据插入的数据存储在磁盘上
        • 主键索引和二级索引的叶子节点均存储着行指针
      • InnoDB
        • 按照数据的主键值递增存储在磁盘上
        • 主键索引的叶子节点存储着:主键值、事务ID、用于事务和MVCC的回滚指针以及所有剩余的列
        • 二级索引的叶子节点存储着主键值,以此减少行移动或页分裂时二级索引的维护工作
          image-20190119164428260
      • 在 InnoDB 表中按主键顺序插入行
        • 最好避免随机(UUID)的聚簇索引,特别是 I/O 密集型的应用
          • 主键字段更长,占用空间更多
          • 频繁页分裂和内存碎片
        • 顺序主键可能存在的问题
          • 高并发工作负载下,并发插入可能导致间隙锁竞争
          • AUTO_INCREMENT 锁机制
          • 解决:重新设计表或应用或更改innodb_autoinc_lock_mode配置
  6. 覆盖索引

    • 含义:索引包含(覆盖)所有需要查询的字段的值

    • 好处

      • 索引条目通常远小于数据行大小,极大减少数据访问量(MyISAM 压缩索引更有效
      • 索引是按照列值顺序存储的,范围查询会比随机从磁盘读取每一行I/O少很多
      • 部分存储引擎将索引缓存在内存中,可减少一次系统调用
      • 对应 InnoDB 二级索引的索引覆盖扫描可避免二次查询(再查一次主键索引
    • 仅 B-Tree 索引能做覆盖索引(哈希索引、空间索引、全文索引均不能

    • 使用场景:延迟关联(deferred join),利用二级索引叶节点默认存储主键值的特性

    • 改进:索引条件推送(index condition pushdown)

  7. 使用索引扫描来做排序

    • 生成有序的结果
      • 排序操作
      • 按索引顺序扫描
    • 特征:EXPLAIN - type 列为 “index” ,说明使用了索引扫描来做排序
    • 使用索引排序的条件
      • 索引列顺序和 ORDER BY 子句顺序完全一致
      • 所有列的排序方向(倒序、正序)都一致
      • 关联查询中,ORDER BY 引用的字段均来自第一张表
      • 遵守:最左前缀匹配原则
        • 例外:前导列为常量时
  8. 压缩(前缀压缩)索引

    • MyISAM 使用前缀压缩减少索引大小,减少使用空间,提高性能(默认只压缩字符串
    • 无法使用 二分查找 ,倒序排序性能较差
  9. 冗余和重复索引

    • 重复索引:相同的列上按照相同的顺序创建的相同类型的索引(傻逼
    • 冗余索引:已经创建了索引(A,B),再创建索引(A)(菜逼 or 大佬
      • 通常不需要冗余索引,但有时出于性能方面的考虑需要冗余,因扩展已有索引会导致其变大,影响查询性能
  10. 索引和锁

  • 索引可以让查询锁定更少的行
  • InnoDB 在访问二级索引上使用共享(读)锁,访问主键索引需要使用排他(写)锁

索引案例学习

  1. 支持多种过滤条件
    • in 诀窍:最左前缀匹配原则在面对范围查询时,会停止后续列的匹配,所有我们可以使用穷举法的方式使用 in (所有条件)的方式,是其能继续批号后续列,例如 sex in(“男”,“女”)
    • 不可滥用 in 诀窍,每额外增加一个 in 条件,优化器需要做的组合以指数形式增加,降低查询性能
    • 尽可能将范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列
  2. 避免多个范围查询
    • EXPLAIN - type 列为 “range” ,说明要 查询范围值 或 查询列表值
      • 举例:查询范围值:id < 2 ,查询列表值:id in (0,1)
      • 区别:前者受限于索引的最左前缀匹配原则,后者则不受其限制
    • 松散索引(未来版本):能在一个索引上使用多个范围条件
  3. 优化排序
    • 文件排序对小数据集很快,对大量数据集是灾难
    • 对于选择性非常低的列,可以增加一些特殊的索引来排序
    • limit 深度翻页问题(limit 1000000,10
      • 存在的问题:需要花费大量的时间来扫描需要废弃的时间
      • 解决
        • 限制用户能够翻页的数据(业务解决)
        • 延迟关联,通过使用覆盖索引查询需要返回的主键,再进行关联原表获取需要的行

维护索引和表

  1. 找到并修复损坏的表
    • CHECK TABLE 检查是否存在表损坏
    • REPAIR TABLE 修复损坏的表
    • 通过 ALTER 操作重建表
  2. 更新索引统计信息
    • 查询优化器通过两个 API 来了解存储引擎的索引值分布信息
      • records_in_range ( ):出入两个边界值获取这个范围大概有多少条记录
        • MyISAM 返回的是精确值
        • InnoDB 返回的是估算值
      • info ( ):返回各种类型的数据,包括索引的基数(每个键值有多少条记录
    • MySQL 优化器使用的是基于成本模型(查询需要扫描多少行
    • ANALYZE TABLE 重新生成统计信息
  3. 减少索引和数据的碎片
    • B-Tree 索引可能会碎片化,降低查询的效率
    • 表数据碎片(行碎片、行间碎片、剩余空间碎片
    • OPTIMIZE TABLE 整理数据
    • 先删除再重建(ALTER TABLE

总结

  1. 选择利用索引查询时的三个原则
    • 单行访问是很慢的(最好读取的块中包含尽可能多所需要行
    • 按顺序访问范围数据是很快的
      • 顺序 I/O 不需要多次磁盘寻道,比随机 I/O 要快很多
      • 不需再额外的排序操作
    • 索引覆盖查询是非常非常快的(不需要回表查找
  2. 尽可能使用原生顺序从而避免额外排序(三星索引是我们的目标~
  3. 理解索引是如何工作的非常重要
    • 别盲目推崇经验法则(如 :将选择性最高的列放在第一列
  4. 勤用 EXPLAIN 分析执行计划
发布了98 篇原创文章 · 获赞 197 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/YangDongChuan1995/article/details/86556256