Mysql索引(持续更新)

一、索引基础

Mysql索引可以包含一个或多个列的值,如果索引包含多个列,那么列的顺序很重要,因为Mysql只能高效的使用索引的最左前缀原则
无论是多么复杂的ORM工具,在精妙和复杂的索引面前都是“浮云”

索引的区别

  1. B树索引
    MyISAM使用前缀压缩技术使得索引更小,但InnoDB则按照原数据格式进行存储。MyISAM索引通过数据的物理位置引用被索引的行,而InnoDB则根据主键引用被索引的行
    B树特点:所有值都是按照顺序存储的,并且每一个叶子页到根的距离相同
B+树

根节点的槽中存放了指向子节点的指针,通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点。这些指针实际上定义了子节点页中值的上线和下线。最终存储引擎要么找到对应的值,要么记录不存在。叶子节点的指针指向的是被索引的数据。

B树对索引列是顺序组织存储的,所以很适合查找范围数据,例如:找出所有以I到K开头的名字,这样的查找效率会非常高。

B树索引适用范围

  • 全职匹配:是指和索引中的所有列进行匹配
  • 匹配最左前缀:即只使用索引的第一列
  • 匹配列前缀:也可以只匹配某一列的值的开头部分
  • 匹配范围值:例如查找姓Allen和B之间的人
  • 精确匹配某一列并范围匹配另外一列
  • 只访问索引的查询:查询只访问索引,而无须访问数据行。(覆盖索引)

B树索引的限制:

  • 所有的查询都必须以最左列为原则,否则索引失效
  • 必须都包含,不能跳过索引中的某一列,否则只能匹配最左列
  • 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找
  1. 哈希索引
    基于hash表实现,只有精确匹配索引所有列的查询才有效。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
在这里插入图片描述

哈希索引的限制:

  • 哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行,不过访问内存中的行速度很快,大部分情况下这一点对性能影响不大
  • 哈希索引是无序的
  • 哈希索引不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容计算哈希值的。
  • 哈希索引只支持等值比较查询,不支持范围查询
  • 哈希索引不能建立在选择性很低的列上,hash冲突很多代价越大。

例子:B树上自建hash索引高查询效率

二、索引的优点

  1. 索引大大减少了服务器需要扫描的数据量
  2. 索引可以帮助服务器避免排序和临时表
  3. 索引可以随机I/O变为顺序I/O

三、高性能的索引策略

  1. 索引列不能是表达式的一部分,也不能是函数的参数,简化where条件的习惯,始终将索引列单独放在比较符号的一侧
  2. 前缀索引和索引选择性
    例如:索引很长的字符列,一个策略是模拟hash索引,一个是找到最适合的索引选择性。索引选择性=不重复的索引值/记录总数。
    索引的选择性越高则查询效率越高,因为选择性高的索引可以让Mysql在查找时过滤掉更多的行。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
    诀窍:选择足够长的前缀以保证较高的选择性,同时又不能太长
    计算合适的前缀长度的另外一个办法就是计算完整列的选择性,并使用前缀的选择性接近于完整列的选择性
select count(distinct city)/count(*) from sakila.city_demo;//0.0312

如果前缀的选择性能接近0.031基本上就可以用了

select count(distinct left(city,3))/count(*) as sel3,//0.0239
count(distinct left(city,4))/count(*) as sel4,//0.0239
count(distinct left(city,5))/count(*) as sel5,//0.0305
count(distinct left(city,6))/count(*) as sel6,//0.0309
count(distinct left(city,7))/count(*) as sel7 from sakila.city_demo//0.0310

前缀索引的缺点:Mysql无法使用前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描。

有时候后缀索引也有用处,例如找到某个域名的所有电子邮件地址。MYSQL原生并不支持反向索引,但是可以把字符串反转后存储,并基于此建立前缀索引。

  1. 聚簇索引(重点)
    不是一种单独的索引类型,而是一种数据存储方式。这里首先对比B树和B+树的区别:
    B树的所有节点既存放所有节点的键key也存放数据data;而B+树只有叶子节点存放key和data,其他内节点只存放key
    B树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点
    B树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点检索就结束了;而B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。

InnoDB的聚簇索引实际上在同一个结构中保存了B树索引和数据行,当表中存在聚簇索引时,它的数据行实际上存放在索引的叶子页(leaf page)中,聚簇表示数据行和相邻的键值紧凑地存储在一起。InnoDB通过主键聚集数据,这也就是说图中被索引地列就是主键列。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。

聚集数据的优点

  • 可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户ID来聚集数据,这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引,则每封邮件都可能导致一次磁盘I/O
  • 数据访问更快。聚簇索引将索引和数据保存在同一个B树上,因此从聚簇索引中获取数据通常比在非聚簇索引中查找要快。
  • 使用覆盖索引扫描的查询可以直接用页节点的主键值

聚簇索引的缺点

  • 插入速度严重依赖插入顺序
  • 更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置
  • 基于聚簇索引的表再插入新行,或者主键被更新导致需要移动行的时候,可能面临“页分裂”问题(页分裂:当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳行,也分裂会导致表占用更多的磁盘空间)
  • 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。
  • 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引中的叶子节点包含了引用行的主键列。(为什么索引需要两次索引查找?答案是保存的行指针的实质。要记住,二级索引叶子节点保存的不是行的物理位置的指针,而是行的主键值这就意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中找到对应的行。两次B树查找而不是一次。对于InnoDB自适应哈希索引能够减少这样的重复工作。

InnoDB和MyISAM的数据分布对比
MyISAM数据分布非常简单,按照数据插入的顺序存储在磁盘上,在行的旁边显示了行号,从0开始递增。因为行是定长的,所以MyISAM可以从表的开头跳过所需要的字节找到需要的行

数据分布

在这里插入图片描述

用UUID来作为聚簇索引则会很糟糕,使用InnoDB时应该尽可能地按主键顺序插入数据,并且尽可能地使用单调增加的聚簇键地值来插入新行

  • 覆盖索引(重点)
    通常大家都会根据查询的where条件来创建合适的索引,不过这只是一个方面。优秀的索引应该考虑到整个查询,而不单是where条件部分。索引确实是一种查找数据的高效方式,但是MySQL也可以使用索引来直接获取列数据,这样就不再需要读取数据行。如果索引的叶子节点中已经包含要查询的数据,那么就没有必要回表查询了。如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称为“覆盖索引”。覆盖索引的好处:
  • 索引条目通常小于数据行大小,所以如果只需要读取索引,那MySQL就会极大地减少数据访问量。这对缓存的负载非常重要,因为这种情况下响应时间大部分花费在数据拷贝上。覆盖索引对于I/O密集型的应用也有帮助,因为索引比数据更小,更容易全部放入内存。
  • 因为索引是按照列值顺序存储的(至少在单个页内是如此),所以对于I/O密集型的范围查询会比随机从磁盘读取一行数据的I/O要少得多
  • 一些存储引擎如MyISAM在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用。这可能会导致严重的性能问题,尤其是那些系统调用占了数据访问的最大开销的场景。
  • 由于InnoDB的聚簇索引,覆盖索引对于InnoDB表特别有用。InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级索引主键能够覆盖查询,则可以避免对主键索引的二次查询。

覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B树索引做覆盖索引。
当发起一个被索引覆盖的查询时,在EXPLAIN的Extra列可以看到Using index的信息。例如,表sakila.inventry有一个多列索引(store_id,film_id)。MySQL如果只需访问这两列,就可以使用这个索引做覆盖索引。

mysql>EXPLAIN SELECT store_id, film_id FROM sakila.inventory\G
*********************************************************
id: 1
select_type:SIMPLE
table:inventory
type:index
possible_keys:NULL
key:idx_store_id_film_id
key_len:3
ref:NULL
rows:4673
Extra:Using index

例子:假设索引覆盖了WHERE条件中的字段,但不是整个查询涉及的字段。如果条件为假,Mysql总是会回表获取数据行,尽管并不需要这一行且最终会被过滤掉。

select * from products where actor = 'SEAN CARRY' and title like '%APOLLO%'\G

索引无法覆盖该查询,原因:

  • 没有任何索引能够覆盖这个查询。因为查询从表中选择了所有的列,而没有任何索引覆盖了所有的列。不过,理论上MySQL还有一个捷径可以利用:WHERE条件中的列有索引可以覆盖的,因此MySQL可以使用该索引找到对应的actor并检查title是否匹配,过滤之后再读取需要的数据行。
  • MySQL不能在索引中执行LIKE操作,MySQL5.5和更早的版本中只允许在索引中做简单比较操作。MySQL能在索引中做最左前缀匹配原则的LIKE查询,因为该操作可以转换为简单的比较操作,但是如果是通配符开头的LIKE查询,存储引擎就无法做匹配。这种情况下,MySQL服务器只能提取数据行的值而不是索引值来比较。

优化:扩展索引覆盖三个数据列(artist, title,prod_id)然后使用如下查询

select * from products 
//先用子查询查询到满足主查询条件的相关的prod_id,再根据join查询主查询相关语句,真的非常巧妙。使用了部分覆盖索引
JOIN(select prod_id from products where actor='SEAN CARRY' and title like '%APOLLO%')AS t1 ON 
(t1.prod_id = products.prod_id)

在查询的第一阶段MySQL可使用覆盖索引,在FROM子句的子查询中找到匹配的prod_id,然后根据这些prod_id值在外层查询匹配获取需要的所有列值。虽然无法使用覆盖索引覆盖整个查询,但总算比完全无法利用索引覆盖的好。

优化结果:这样的优化效果取决于WHERE条件匹配返回的行数。假设这个products表有100w行,我们来看一下上面两个查询在三个不同的数据集上的表现。
1.Sean 出演了30000部作品,其中20000部的标题包含了Apollo
2.Sean 出演了30000部作品,其中40部的标题包含了Apollo
3.Sean 出演了50部作品,其中10部的标题包含了Apollo

| 数据集| 原查询 |优化后
|1|每秒5次|每秒5次
| 2 | 每秒7次|每秒35次
|3|每秒2400|每秒2000

示例一查询返回了一个很大的结果集,因此看不到优化效果。大部分时间都花在读取和发送数据上了。
示例二查询性能提高了五倍
示例三效果下降因为索引的结果集已经很小,所以子查询带来的成本反而比从表中直接提取完整行更高。

你可能感兴趣的:(Mysql索引(持续更新))