MySQL索引

Posted 小猴子_X

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL索引相关的知识,希望对你有一定的参考价值。

 一.索引的概述

什么是索引?

  • 本质:索引是一种"数据结构"。索引就是为了快速查找那些具有特定值的记录。没有索引,就需要查找整个表,效率低。 索引是在存储引擎中实现的。

为什么建立索引?

  • 为了减少磁盘的IO次数,加快查询速度

索引的基本原理?

  1. 对创建索引的列进行排序,生成倒排表
  2. 在倒排表内容上填上真实数据的地址
  3. 在查询时,先拿到倒排表的内容,再取到真实数据的地址,拿到真实的数据。

简述mysql中索引的类型?

  • "普通索引": 所有运行被索引的列
  • "唯一索引":保证数据记录的唯一性
  • "主键":是一种特殊的唯一索引
  • "联合索引":多个列联合起来的索引(index(columnA,columnB)),属于非聚簇索引的一种
  • "全文索引":通过倒排索引,建立的一种全文搜索的技术,用在搜索引擎中。

索引对数据库性能的影响(利弊)?

  • 好处: 使用索引可以加快查询速度;
  • 弊端:
    • 降低增删改的速度。因为写这些数据的时候,还得考虑索引文件
    • 索引本身也需要占据大量的物理空间

mysql聚簇索引和非聚簇索引的区别?

  • 聚簇索引:把数据存储和索引放在一起,找到索引就找到数据了。并且索引的顺序和真实数据的 顺序一样,这样便于范围查询
  • 非聚簇索引(二级索引)(辅助索引):数据存储和索引的位置不在一起,索引里面放着数据的真实地址。先根据索引查出 数据的地址,再查到具体的数据
  • 优势: (聚簇索引的优势就是非聚簇索引的劣势)
    • 聚簇索引可以直接获取数据,相比非聚集索引需要进行二次查询,效率要高。(无需回表)
    • 聚簇索引对于范围查询效率高,因为数据是按照索引的顺序存储的,同样适合排序。
  • 劣势:
    • 聚簇索引维护很昂贵.比如插入新纪录的时候,整个数据和索引的位置都需要发生变化
    • 使用随机主键的时候(UUID),数据存储稀疏,可能比全表扫描还要慢, 所以建议使用auto_increment作为主键
    • 如果主键比较大的话,辅助索引也会占很大的空间。因为辅助索引存放的有主键的值,就会导致非叶子节点占用更多的物理内存。

 

二.索引的创建和设计原则

索引的创建和删除

#直接创建索引
create index 索引名 on 表名(列名);
#建表时指定索引
create table t
    tid int,
    tname varchar(20),
    index [索引名] (列名)    
;
#修改表时指定索引
alter table 表名 add unique index 索引名 (列名);

###############################################
drop index 索引名 on 表名 alter table 表名 drop index 索引名

 8.0新特性:(优化作用)

  • 支持降序索引:之前索引都是默认升序的,现在支持降序。
  • 支持隐藏索引:之前索引的要是删除后,发现效果不好或者出错,只能重新添加索引。一删一加浪费性能,现在就可以先隐藏索引,让索引不起作用。发现效果不错,再删除索引。
    • create index 索引名 on 表名(列名) invisible

索引的设计原则? 查询更快,占用空间更小

  • 适合做索引
    • 字段有唯一性限制,区分度高
    • 适合建立索引的列是经常出现在where中或者group by 和order by
    • 定义有外键的数据列一定要建立索引
    • 列的字符串过长,可以使用短索引,指定一个前缀长度
    • 在多个字段要建立索引的情况下,联合索引优于单索引
    • 最左前缀匹配原则:使用最频繁的列放在联合索引的最左边
  • 不适合做索引
    • 基数较小的表,完全没有必要建立索引
    • 对于查询中很少设计的列,没必要建立索引
    • 更新频繁的字段,不建议建立索引
    • 不能有效的区分数据的列不建议建立索引(比如性别列,只有男/女)
    • 对于image,text和Bit类型的列不要建立索引

 

索引失效的几种情况?
  • 全值匹配我最爱:如果一个字段在联合索引内,那么单独建立它的索引就会失效。因为优先考虑联合索引
  • 最左前缀匹配原则:比如在一个联合索引(age,name)中,如果查询的列只有name,就不能使用该索引。
  • 计算、函数、自动/手动类型转换会导致索引失效,比如索引字段是字符串,但查询时不加单引号,就需要进行类型转换
  • 不能继续使用索引中范围条件(bettween、<、>、in等)右边的列 。比如:(age,classid,name),where age =30 and classid >20 and name = \'abc\'。name索引失效
  • 索引字段上使用 != 或者 < >判断时,失效
  • 索引字段上使用 is not null 判断时索引失效
  • like以通配符% 开头的索引失效。如"%ab"
  • OR前后存在非索引的列,失效
  • 不同字符集会进行比较前会进行转换导致索引失效

 

三.b+树

 innodb和MyISAM默认采用b+树的方式

说明:

  • 第一二层是目录页,存放的每一个数据页的最小值和数据页的地址。每一个目录页之间使用双向链表连接,目录页中的每一个数据使用单向链表连接。
  • 第三层(叶子节点)是数据页,存放真正的数据,数据页之间使用双向链表连接,每一条数据使用单向链表连接。
  • 对于Innodb来说,每一个表只能有一个聚簇索引,但是可以有多个非聚簇索引。聚簇索引是按照主键,非聚簇索引是按照自定义的列。
  • 对于MyISAM来说,叶子节点中存放的data就是数据的地址,采用直接寻址的方式(直接定位到文件的offset位置)。所以只有非聚集索引。

注意点:

  • 根页面位置万年不动:创建一个表,开始只有一个数据页,随着数据的增加数据页才开始变多,有的变成目录页,但是第一开始的位置的数据页只是后来变成目录页,而不是删除。
  • 目录项中记录的唯一性:保证目录项的唯一性,如果不唯一就可以加上主键保证唯一性。
  • 一个页面最少存储2条记录

b树的存储能力如何?为什么说一般b树不超过4层?

  • innodb的一个数据页空间为16kb,一般存放的主键和指针的大小不超过16b,所有一页存放16/16=10^3个记录,三层10^3 * 10^3 *10^3=10亿,一个索引存放10亿绝对够。
  • b+树的高度一般在2-4层,innodb引擎设计时将根节点常驻在内存中,也就是查询一条记录最多需要3次IO。
  • 因为层数越多,io次数就越多。

回表?

  • 通过非聚簇索引查询,在叶子节点找到该列的索引和主键,然后通过主键再去原生的聚簇索引查询一遍就是回表。

为什么要进行回表?

  • 因为要想不用回表,就需要把完整的信息都放在非聚簇索引的叶子节点上,就相当于把整个记录再复制一遍,太占地方了。

 

四.其他的索引结构

 b树

 

 

 说明:

  • 叶子节点和非叶子节点都存数据。

为什么数据库采用b+树而没有采用b树?

  • 空间利用率更高因为 B 树每个节点要存储键和值,而 B+ 树的内部节点只存储键,这样 B+ 树的一个节点就可以存储更多的索引,从而使树的高度变低,减少了 I/O 次数,使得数据检索速度更快。
  • 稳定性更好:B+树查询所有的关键字的io次数相同。而b树可以有的关键字在目录页就查到,有的需要的io次数多,有的少,就不稳定。
  • 便于顺序查找:B+ 树的叶子节点都是连接在一起的,所以范围查找,顺序查找更加方便。

 hash结构效率高,为什么没有设计成innodb索引?

  • 在绝大多数需求为单条查询的时候(等值判断),可以选择哈希索引,效率非常高。
  • hash结构的存储是没有顺序的,比如order by,like \'abc%\' 这样的查询都需要顺序。
  • 对于联合索引来说,hash值是将多个关键字合并一起使用的,无法对单独的一个关键字进行查询。
  • innodb虽然不支持hash索引,但是提供了自适应Hash索引
    • 如果某个数据经常被访问,当满足一定的条件时,就会将这个数据页的地址存放在Hash表中,下次查询直接从hash表中获取数据。让b+树也具有hash的优点。

 

五.innodb数据的存储结构

页的概述:

  • 页作为磁盘和内存交互的基本单位。(IO操作的最小单位)

 

 

  •  为什么要有区?
    • 如果数据在好几页,并且这几页在磁盘的位置相距很远,这时候就需要磁盘花费很多时间寻找页的位置,也叫做随机io。而我们把好多页放在一起,形成一个区(64)页,就可以顺序io,磁道加快寻找页的时间。
  • 为什么有段?
    • 不同类型的数据库对象以不同的段进行存储。比如叶子节点存在一个段,非叶子节点存在一个段。一个段内可以有好几个区。
  • 为什么有碎片区?
    • 一些数据量小的表,用不同的段分别装叶子节点和非叶子节点,太浪费空间。就可以让一些区独立出来,不属于任何一个段,直属于表空间,这个区就叫碎片区。这个碎片区可以被其他段公共使用。比如刚开始往表中存放数据的时候,就放在碎片区,当碎片区足够大的时候,才分为不同的段。
  • 表空间:
    • 系统表空间:主要记录系统操作的一些表,比如插入哪个表的时候的记录。
    • 独立表空间:就是我们创建的表

页的内部结构:

 

 

  • 文件头:描述各种页的通用信息文件尾部:主要有fil_page_space_or_chksum和fil_page_lsn,和文件头进行首尾验证,保证页的完整性
    • fil_page_offset:页号
    • fil_page_type:页的类型,比如数据页/系统页/日志页
    • fil_page_prev,fil_page_next:上一页下一页
    • fil_page_space_or_chksum:校验和,如果两个字符串太长,通过一定的算法计算出短的字符串进行比较。与文件尾对应
    • fil_page_lsn:最后修改对应的日志序列
  • 用户记录:每一个记录生成一个单链表
  • 空闲记录:没有记录
  • 最大和最小记录:是一种特别的用户记录
  • 页目录:每一页中的记录按照单链表连在一起,不方便查询。就可以分层几个组(slot)单独拿出来,进行二分法查找。第一组只有一个元素,就是最小记录,中间的组有4-8个元素,最后一组有1-8个元素(包含最大记录)。slot是记录一组中最大的元素
  • 页面头部:存放一些数据页中记录的状态信息。比如存了多少记录等

行格式:
指定行格式的语法:create table 表明() row_format=行格式名称。有4种行格式。

  • compact行格式:dynamic行格式(动态行格式):mysql8.0默认,其他与compact基本一样,解决行溢出有差别
    • 变长字段长度列表:比如一些类型varchar,text,默认开辟一定的空间,但实际用不了那么多,该属性就是记录真实的字段长度。(倒序存储)
    • null值列表:用二进制表示null列表。
      • 为什么定义null列表?因为数据要求对齐,防止出现凌乱。但是如果用特定的符号放在相应的数据位表示空值,就很浪费空间,所以使用null值列表
    • 记录头信息:
      • delete_mask:0代表没有删除,1代表被删除。正在的数据其实没有被删除,只是做了一下标记,这些被标记删除的记录最后 自己连起来,生成一个垃圾链表,可以被重复使用。主要是为了防止删除元素后重新排列,消耗性能。
      • min_rec_mask:非叶子节点为1,叶子节点为0
      • record_type:0表示普通记录,1表示非叶子节点,2最小记录,3最大记录
      • heap_no:记录在本页中的位置。但是真实的数据是从2开始记录,0和1分别表示最大记录和最小记录的位置
      • n_owned:每一个组(slot)的最后一个记录,记录该组一共有多少个记录
      • next_record:记录下一条记录的地址偏移量
    • 真实数据:除了真实的表中数据,还有三个隐藏的列表
  • compressed行格式(压缩行格式):使用zlib库进行压缩,其他与compact基本一样,解决行溢出有差别。
  • redundant行格式(冗余行格式):把变长字段长度列表变为字段长度列表,记录所有字段的长度,所以冗余。也不需要null值列表。其他与compact基本一样,解决行溢出有差别

行溢出:

  • 插入的行的容量比页的容量都大,就会出现行溢出。
  • 数据页默认大小为16KB,换算成byte: 16*1024 = 16384 byte。一个表的列varchar类型最大能存储65535b。如果你存放一个在16384和65535之间的数据就会出现行溢出,大于65535干脆就插入不进去。
  • compact和redundant:部分行溢出方法:把剩余的数据放在其他页,真实数据处使用20个字节存放剩余的地址
  • dynamic和compressed:把溢出的行所有数据都放在其他页,数据处只存放20个字节的地址

B+树是如何进行记录检索?

  • 从根节点开始,逐层搜索,直到找到叶子节点,然后将所在的数据页加载到内存中,页目录的solt采用二分法的方式找到一个粗略的记录分组,然后在分组中采用遍历链表的方式查找记录。

 

寄语:时间是什么?时间是用来打造你的核心竞争堡垒的工具!

以上是关于MySQL索引的主要内容,如果未能解决你的问题,请参考以下文章

mysql索引类型和索引方式

mysql 索引

mysql索引(九)索引合并

mysql---索引优化

MySQL索引

mysql索引?