MySQL索引优化 & 聚簇索引 & 字段选择性 & 范围查询 & 组合索引的字段顺序

索引B-Tree:

一般来说, MySQL 中的 B-Tree 索引的物理文件大多都是以 B+tree的结构来存储的,也就是所有实际需要的数据都存放于 Tree 的 Leaf Node,而且到任何一个 Leaf Node 的最短路径的长度都是完全相同的,可能各种数据库(或 MySQL 的各种存储引擎)在存放自己的 B-Tree 索引的时候会对存储结构稍作改造。如 Innodb 存储引擎的 B-Tree 索引实际使用的存储结构实际上是 B+Tree ,也就是在 B-Tree 数据结构的基础上做了很小的改造,在每一个Leaf Node 上面出了存放索引键值和主键的相关信息之外,B+Tree还存储了指向与该 Leaf Node 相邻的后一个 LeafNode 的指针信息,这主要是为了加快检索多个相邻 Leaf Node 的效率考虑。

B-Tree对索引列是顺序组织存储的,所以很适合查找范围数据,例如,在一个基于文本域的索引树上,按字母顺序传递连续的值进行查找是非常合适的,所以像 “找出所有以 A 到 K 开头的名字” 这样的查找效率会非常高。

因为索引树中的节点是有序的,所以除了按值查找之外,索引还可以用于查询中的ORDER BY(按顺序查找),GROUP BY(按分组查找)操作。一般来说,如果 B-Tree 可以按照某种方式查找到值,那么也可以按照这种方式用于排序。所以,索引对 ORDER BY 子句也可以满足对应的排序需求。

在innodb引擎中,btree索引分为两种,1,聚簇索引(主键索引),或者说叫聚集索引,因为数据的逻辑顺序与物理顺序都是紧凑的。2.二级索引(非聚簇索引),或者说叫辅助索引。InnoDB中的主键索引是聚集索引,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录(整行数据)。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主键索引。但是innodb的二级索引,保存的是索引列值以及指向主键的指针,所以我们使用覆盖索引的做优化处理就是针对mysql的innodb的索引而言的。

下面两张图显示mysql中innodb和myisam引擎的索引实现的原理

MySQL索引优化 & 聚簇索引 & 字段选择性 & 范围查询 & 组合索引的字段顺序_第1张图片MySQL索引优化 & 聚簇索引 & 字段选择性 & 范围查询 & 组合索引的字段顺序_第2张图片

看上去聚簇索引的效率明显要低于非聚簇索引,因为每次使用辅助索引检索都要经过两次B+树查找,这不是多此一举吗?聚簇索引的优势在哪?

  1. 由于行数据和叶子节点存储在一起,这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
  2. 辅助索引使用主键作为"指针" 而不是使用行地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针",使用聚簇索引可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。

关于 InnoDB,索引和锁有一些很少有人知道的细节:InnoDB 在二级索引上使用共享(读)锁,但访问主键索引需要排他(写)锁。这消除了使用覆盖索引的可能性,并且使得 SELECT FOR UPDATE 比 LOCK IN SHARE MODE 或非锁定查询要慢很多。

啊哈哈,概念扯多了。

你需要知道的:

  1. 不要求每个人一定理解 联表查询(join/left join/inner join等)时的mysql运算过程,但对于字段选择性差意味着什么,组合索引字段顺序意味着什么,要求每个人必须了解;
  2. 把mysql客户端(如SQLyog,如HeidiSQL)放在桌面上,时不时拿出来 explain 一把,这是一种美德!
  3. 确保亲手查过SQL的执行计划,一定要注意看执行计划里的 possible_keys、key和rows这三个值,让影响行数尽量少,保证使用到正确的索引,减少不必要的Using temporary/Using filesort;
  4. 不要在选择性非常差的字段上建索引,原因参见优化策略A;
  5. 查询条件里出现范围查询(如A>7,A in (2,3))时,要警惕,不要建了组合索引却完全用不上,原因参见优化策略B;

 

——字段选择性的基础知识——

引子:什么字段都可以建索引吗?

如下表所示,sort 字段的选择性非常差,你可以执行 show index from ads 命令可以看到 sort 的 Cardinality(散列程度)只有 9,这种字段上本不应该建索引:

Table

Non_unique

Key_name

Seq_in_index

Column_name

Collation

Cardinality

Sub_part

Packed

Null

Index_type

Comment

ads

1

sort

1

sort

A

9

\N

\N

 

BTREE

 

 

优化策略A:字段选择性

  • 选择性较低索引 可能带来的性能问题
    • 索引选择性=索引列唯一值/表记录数;
    • 选择性越高索引检索价值越高,消耗系统资源越少;选择性越低索引检索价值越低,消耗系统资源越多;
  • 查询条件含有多个字段时,不要在选择性很低字段上创建索引
    • 可通过创建组合索引来增强低字段选择性和避免选择性很低字段创建索引带来副作用;
    • 尽量减少possible_keys,正确索引会提高sql查询速度,过多索引会增加优化器选择索引的代价,不要滥用索引;

 

——组合索引字段顺序与范围查询之间的关系——

引子:范围查询 city_id in (0,8,10) 能用组合索引 (ads_id,city_id) 吗?

举例,

ac 表有一个组合索引(ads_id,city_id)。

那么如下 ac.city_id IN (0, 8005) 查询条件能用到 ac表的组合索引(ads_id,city_id) 吗?

EXPLAIN

SELECT ac.ads_id

FROM ads,  ac

WHERE

      ads.id = ac.ads_id

      AND ac.city_id IN (0, 8005) 

      AND ads.status = 'online'

      AND ac.start_time

      AND ac.end_time>UNIX_TIMESTAMP()

优化策略B:

由于 mysql 索引是基于 B-Tree 的,所以组合索引有“字段顺序”概念。

所以,查询条件中有 ac.city_id IN (0, 8005),而组合索引是 (ads_id,city_id),则该查询无法使用到这个组合索引。

DBA总结道:

组合索引查询的各种场景

兹有 Index (A,B,C) ——组合索引多字段是有序的,并且是个完整的BTree 索引。

  • 下面条件可以用上该组合索引查询:
    • A>5
    • A=5 AND B>6
    • A=5 AND B=6 AND C=7
    • A=5 AND B IN (2,3) AND C>5
  • 下面条件将不能用上组合索引查询:
    • B>5 ——查询条件不包含组合索引首列字段
    • B=6 AND C=7 ——查询条件不包含组合索引首列字段
  • 下面条件将能用上部分组合索引查询:
    • A>5 AND B=2 ——当范围查询使用第一列,查询条件仅仅能使用第一列
    • A=5 AND B>6 AND C=2 ——范围查询使用第二列,查询条件仅仅能使用前二列

 

组合索引排序的各种场景

兹有组合索引 Index(A,B)。

  • 下面条件可以用上组合索引排序:
    • ORDER BY A——首列排序
    • A=5 ORDER BY B——第一列过滤后第二列排序
    • ORDER BY A DESC, B DESC——注意,此时两列以相同顺序排序
    • A>5 ORDER BY A——数据检索和排序都在第一列
  • 下面条件不能用上组合索引排序:
    • ORDER BY B ——排序在索引的第二列
    • A>5 ORDER BY B ——范围查询在第一列,排序在第二列
    • A IN(1,2) ORDER BY B ——理由同上
    • ORDER BY A ASC, B DESC ——注意,此时两列以不同顺序排序

 

顺着组合索引怎么建继续往下延伸,请各位注意“索引合并”概念:

  • MySQL 5,0以下版本,SQL查询时,一张表只能用一个索引(use at most only one index for each referenced table),
  • 从 MySQL 5.0开始,引入了 index merge 概念,包括 Index Merge Union Access Algorithm(多个索引并集访问),包括Index Merge Intersection Access Algorithm(多个索引交集访问),可以在一个SQL查询里用到一张表里的多个索引。
  • MySQL 在5.6.7之前,使用 index merge 有一个重要的前提条件:没有 range 可以使用。

索引合并的简单说明:

  • MySQL 索引合并能使用多个索引
    • SELECT * FROM TB WHERE A=5 AND B=6
      • 能分别使用索引(A) 和 (B) 或 索引合并;
      • 创建组合索引(A,B) 更好;
    • SELECT * FROM TB WHERE A=5 OR B=6
      • 能分别使用索引(A) 和 (B) 或 索引合并;
      • 组合索引(A,B)不能用于此查询,分别创建索引(A) 和 (B)会更好;
      • 或者使用 UNION ALL,SELECT * FROM TB WHERE A=5 UNION ALL SELECT * FROM TB WHERE A=6

优化 LIMIT 分页:

  • 优化大偏移量的性能,尽可能地使用索引覆盖扫描,而不是查询所有的列,然后根据需要做一次关联操作再返回所需的列
  • 如“延迟关联”:SELECT * FROM TB INNER JOIN (SELECT id FROM TB ORDER BY id LIMIT 50,5) AS TB1 USING(id)
  • 有时候也可以将 LIMIT 查询转换为已知位置的查询,让 MySQL 通过范围扫描获得对应的结果
  • LIMIT 和 OFFSET 的问题,其实是 OFFSET 的问题,它会导致 MySQL 扫描大量不需要的行然后再抛弃掉。所以我们应该尽可能地避免这种大量扫描行的行为来优化分页查询

最后的总结:

仍然是强调再强调:

记住,explain 后再提测是一种美德!

关注公众号,分享干货,讨论技术

MySQL索引优化 & 聚簇索引 & 字段选择性 & 范围查询 & 组合索引的字段顺序_第3张图片

你可能感兴趣的:(MySQL,聚簇索引,字段选择性,范围查询,组合索引,MySQL)