前言
索引是存储引擎用于快速找到记录的一种数据结构
索引基础
- 索引是在存储引擎层而不是服务层实现
- 不同存储引擎的索引的工作方式并不一样,也不是所有的存储引擎都支持所有类型的索引
索引类型
-
B-Tree 索引
-
存储引擎以不同的方式使用 B-Tree 索引
- MyISAM:前缀压缩技术、数据的物理位置引用被索引的行
- InnoDB:原数据格式进行存储、主键引用被索引的行
-
所有的值都是按顺序存储的,每一个叶子页到根的距离相同
-
叶子节点比较特别,他们的指针指向的是被索引的数据,而不是其他的节点页
- MyISAM:主键索引与二级索引均是存储着数据的行指针
- InnoDB:主键索引直接存储着行数据,二级索引存储着主键值
-
B-Tree 索引适用的查询类型
- 全值匹配
- 匹配最左前缀
- 匹配列前缀(like ‘杨东%’
- 精确匹配某一列并范围匹配另外一列(范围匹配后停止该索引后续列的匹配
- 只访问索引的查询(索引覆盖扫描
-
B-Tree索引的限制
- 如果不是按照索引的最左列开始查找,则无法使用索引
- 不能跳过索引中列(R-Tree 可以
- 范围查询后,后续列无法使用索引优化查找
-
可用于排序与分组
-
索引列的顺序非常非常重要!!!
扫描二维码关注公众号,回复: 9501468 查看本文章
-
-
哈希索引
- 基于哈希表实现
- 必须精确匹配索引所有列
- 索引自身只存储对应的哈希值,结构紧凑、查询速度非常快
- 哈希索引的限制
- 哈希索引只包含哈希值和行指针,不存储字段值,无法使用覆盖索引的特性
- 非顺序存储、无法用于排序
- 不支持索引列部分匹配查找
- 只支持等值比较查询
- 哈希冲突时,需遍历链表中所有的行指针,直至找到符合条件的行
- 哈希冲突存在较多时,索引维护成本太高
- InnoDB 自适应哈希索引
- 某些索引值被频繁使用时,存储引擎会在内存中基于 B-Tree 索引之上再创建一个哈希索引
- 查询效率显著提升
- 完全自动、内部的行为,用户无法控制和配置
- 可手动关闭
- 常用哈希函数
- CRC32:计算出的哈希值较短、若数据表非常大,会出现大量的哈希冲突
- SHA1、MD5:强加密、最大限度消除冲突,计算出的哈希值更长、浪费空间、比较更慢
- FNV64:移植自 Percona Server 函数,哈希值为 64 位,速度快、冲突较 CRC32 少很多
-
空间数据索引(R-Tree)
- 从所有维度来索引数据、支持任意维度来组合查询
- 需使用 GIS 相关函数维护数据
- 全文索引
- 查找的是文本中的关键字(MATCH AGAINST),非直接比较
- 匹配方式和其他几类索引完全不同,比如:停用词、词干和复数、布尔搜索等
- 允许在相同的列上同时创建全文索引与其他类型的所有
索引的优点
- 索引的三个优点
- 减少了服务器需要扫描的数据量
- 帮助服务器避免排序和临时表
- 将随机 I/O 变为顺序 I/O
- 索引的三星系统
- 一星:索引将相关的记录放到一起
- 二星:索引中的数据顺序和查找中的排列顺序一致
- 三星:索引中的列包含了查询中需要的全部列(索引覆盖
- 对于 TB 级别的数据,经常使用块级别元数据技术来替代索引
高性能的索引策略
-
独立的列
- 含义:指索引列不能是表达式的一部分,也不能是函数的参数
- Bad Case
- … where id + 1 = 5
- … where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10
-
前缀索引和索引选择性
- 选择性
- 含义:不重复的索引值(基数,cardinality)和数据表的记录总数(#T)的比值
- 比值越高则查询效率越高,唯一索引的选择性是 1
- 前缀索引
- 目的:索引开始的部分字符,节约索引空间,提高索引效率
- 常用于 BLOB、TEXT 或者很长 VARCHAR 类型的列
- 选择足够长的前缀以保证较高的选择性
- 计算完整列的选择性,并使前缀的选择性接近于完整列的选择性
- 不仅需要看平均选择性,也需考虑最坏情况下的选择性
- 无法使用前缀索引做 ORDER BY 和 GROUP BY 与 索引覆盖扫描
- 选择性
-
多列索引
- 索引合并(index merge)
- 含义:同时使用表上的多个单列索引来定位指定的行,并将结果进行合并
- 算法变种
- OR 条件的联合(union):需要耗费大量资源在算法的缓存、排序和合并操作
- AND 条件的相交(intersection):需要一个包含所有相关列的多列索引
- 组合前两种情况的联合及相交
- 优化器不会将这些计算到的“查询成本”中,优化器只关心随机页面读取,使查询的成本被低估
- 可通过 optimizer_switch 关闭索引合并功能
- 索引合并(index merge)
-
选择合适的索引列顺序
- 经验法则:将选择性最高的列放到索引最前列
- 应该根据那些运行频率最高的查询来调整索引列的顺序
- 同时还应该考虑排序、分组和范围条件等其他因素
-
聚簇索引
- 聚簇索引并不是一种单独的索引类型,而是一种数据存储方式
- 聚簇:数据行和相邻的键值紧凑地存储在一起
- 叶子页包含了行的全部数据,但节点页只包含了索引列
- 主要的优点
- 将相关数据保存在一起
- 数据访问更快
- 覆盖索引扫描可直接获取到页节点中的主键值
- 存在的缺点
- 提高了 I/O 密集型应用的性能,但如果数据存储在内存中,则不存在什么优势
- 插入速度严重依赖插入顺序
- 更新聚簇索引列的代价高
- 插入新行或更新主键导致移动行时,可能面临“页分裂”,占用更多的磁盘空间
- 聚簇索引可能导致全表扫描变慢
- 二级索引因存在行的主键列,可能较大
- 二级索引访问数据需要两次索引查找(二级索引 -> 主键索引
- InnoDB 和 MyISAM的数据分布对比
- MyISAM
- 按照数据插入的数据存储在磁盘上
- 主键索引和二级索引的叶子节点均存储着行指针
- InnoDB
- 按照数据的主键值递增存储在磁盘上
- 主键索引的叶子节点存储着:主键值、事务ID、用于事务和MVCC的回滚指针以及所有剩余的列
- 二级索引的叶子节点存储着主键值,以此减少行移动或页分裂时二级索引的维护工作
- 在 InnoDB 表中按主键顺序插入行
- 最好避免随机(UUID)的聚簇索引,特别是 I/O 密集型的应用
- 主键字段更长,占用空间更多
- 频繁页分裂和内存碎片
- 顺序主键可能存在的问题
- 高并发工作负载下,并发插入可能导致间隙锁竞争
- AUTO_INCREMENT 锁机制
- 解决:重新设计表或应用或更改
innodb_autoinc_lock_mode
配置
- 最好避免随机(UUID)的聚簇索引,特别是 I/O 密集型的应用
- MyISAM
-
覆盖索引
-
含义:索引包含(覆盖)所有需要查询的字段的值
-
好处
- 索引条目通常远小于数据行大小,极大减少数据访问量(MyISAM 压缩索引更有效
- 索引是按照列值顺序存储的,范围查询会比随机从磁盘读取每一行I/O少很多
- 部分存储引擎将索引缓存在内存中,可减少一次系统调用
- 对应 InnoDB 二级索引的索引覆盖扫描可避免二次查询(再查一次主键索引
-
仅 B-Tree 索引能做覆盖索引(哈希索引、空间索引、全文索引均不能
-
使用场景:延迟关联(deferred join),利用二级索引叶节点默认存储主键值的特性
-
改进:索引条件推送(index condition pushdown)
-
-
使用索引扫描来做排序
- 生成有序的结果
- 排序操作
- 按索引顺序扫描
- 特征:EXPLAIN - type 列为 “index” ,说明使用了索引扫描来做排序
- 使用索引排序的条件
- 索引列顺序和 ORDER BY 子句顺序完全一致
- 所有列的排序方向(倒序、正序)都一致
- 关联查询中,ORDER BY 引用的字段均来自第一张表
- 遵守:最左前缀匹配原则
- 例外:前导列为常量时
- 生成有序的结果
-
压缩(前缀压缩)索引
- MyISAM 使用前缀压缩减少索引大小,减少使用空间,提高性能(默认只压缩字符串
- 无法使用 二分查找 ,倒序排序性能较差
-
冗余和重复索引
- 重复索引:相同的列上按照相同的顺序创建的相同类型的索引(傻逼
- 冗余索引:已经创建了索引(A,B),再创建索引(A)(菜逼 or 大佬
- 通常不需要冗余索引,但有时出于性能方面的考虑需要冗余,因扩展已有索引会导致其变大,影响查询性能
-
索引和锁
- 索引可以让查询锁定更少的行
- InnoDB 在访问二级索引上使用共享(读)锁,访问主键索引需要使用排他(写)锁
索引案例学习
- 支持多种过滤条件
- in 诀窍:最左前缀匹配原则在面对范围查询时,会停止后续列的匹配,所有我们可以使用穷举法的方式使用 in (所有条件)的方式,是其能继续批号后续列,例如 sex in(“男”,“女”)
- 不可滥用 in 诀窍,每额外增加一个 in 条件,优化器需要做的组合以指数形式增加,降低查询性能
- 尽可能将范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列
- 避免多个范围查询
- EXPLAIN - type 列为 “range” ,说明要 查询范围值 或 查询列表值
- 举例:查询范围值:id < 2 ,查询列表值:id in (0,1)
- 区别:前者受限于索引的最左前缀匹配原则,后者则不受其限制
- 松散索引(未来版本):能在一个索引上使用多个范围条件
- EXPLAIN - type 列为 “range” ,说明要 查询范围值 或 查询列表值
- 优化排序
- 文件排序对小数据集很快,对大量数据集是灾难
- 对于选择性非常低的列,可以增加一些特殊的索引来排序
- limit 深度翻页问题(limit 1000000,10
- 存在的问题:需要花费大量的时间来扫描需要废弃的时间
- 解决
- 限制用户能够翻页的数据(业务解决)
- 延迟关联,通过使用覆盖索引查询需要返回的主键,再进行关联原表获取需要的行
维护索引和表
- 找到并修复损坏的表
- CHECK TABLE 检查是否存在表损坏
- REPAIR TABLE 修复损坏的表
- 通过 ALTER 操作重建表
- 更新索引统计信息
- 查询优化器通过两个 API 来了解存储引擎的索引值分布信息
- records_in_range ( ):出入两个边界值获取这个范围大概有多少条记录
- MyISAM 返回的是精确值
- InnoDB 返回的是估算值
- info ( ):返回各种类型的数据,包括索引的基数(每个键值有多少条记录
- records_in_range ( ):出入两个边界值获取这个范围大概有多少条记录
- MySQL 优化器使用的是基于成本模型(查询需要扫描多少行
- ANALYZE TABLE 重新生成统计信息
- 查询优化器通过两个 API 来了解存储引擎的索引值分布信息
- 减少索引和数据的碎片
- B-Tree 索引可能会碎片化,降低查询的效率
- 表数据碎片(行碎片、行间碎片、剩余空间碎片
- OPTIMIZE TABLE 整理数据
- 先删除再重建(ALTER TABLE
总结
- 选择利用索引查询时的三个原则
- 单行访问是很慢的(最好读取的块中包含尽可能多所需要行
- 按顺序访问范围数据是很快的
- 顺序 I/O 不需要多次磁盘寻道,比随机 I/O 要快很多
- 不需再额外的排序操作
- 索引覆盖查询是非常非常快的(不需要回表查找
- 尽可能使用原生顺序从而避免额外排序(三星索引是我们的目标~
- 理解索引是如何工作的非常重要
- 别盲目推崇经验法则(如 :将选择性最高的列放在第一列
- 勤用 EXPLAIN 分析执行计划