SQL索引

什么是索引?

索引就是帮助数据库管理系统高效获取数据的数据结构。数据库中的索引,就好比一本书的目录,它可以帮我们快速进行特定值的定位与查找,从而加快数据查询的效率。

索引的使用情况

索引不是万能的,在有些情况下使用索引反而会让效率变低:

当数据量少,是否使用索引对结果影响不大。

当数据重复度大,高于10%的时候,也不需要对这个字段使用索引。但有些时候也会使用,比如定位一个比例很少的字段。

索引的价值是快速定位。如果想要定位的数据有很多,那么索引就失去了它的使用价值,比如通常情况下的性别字段。不过有时候,我们还要考虑这个字段中的数值分布的情况,当性别字段的数值分布非常特殊,比如男性的比例非常少时索引就有用武之地。我们不仅要看字段中的数值个数,还要根据数值的分布情况来考虑是否需要创建索引。

索引的种类(功能逻辑和物理实现)

从功能逻辑上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引和全文索引。

普通索引是基础的索引,没有任何约束,主要用于提高查询效率。

唯一索引就是在普通索引的基础上增加了数据唯一性的约束,在一张数据表里可以有多个唯一索引。

主键索引在唯一索引的基础上增加了不为空的约束,也就是 NOT NULL+UNIQUE,一张表里最多只有一个主键索引。

全文索引用的不多,MySQL 自带的全文索引只支持英文。我们通常可以采用专门的全文搜索引擎,比如 ES(ElasticSearch) 和 Solr。

普通索引、唯一索引和主键索引都是一类索引,只不过对数据的约束性逐渐提升。

在一张数据表中只能有一个主键索引,这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。但可以有多个普通索引或者多个唯一索引。

按照物理实现方式,索引可以分为 2 种:聚集索引和非聚集索引。我们也把非聚集索引称为二级索引或者辅助索引。

mysql的innodb表,就是索引组织表,表中的所有数据行都放在索引上,这就约定了数据是严格按照顺序存放的,不管数据插入的先后顺序,都会插入到固定的物理位置。

聚集索引(决定存储顺序,一定有)

每个表都需要一个聚集索引,通常,聚集索引与主键同义。如果没有主键或是没有NOT NULL的唯一索引(作主键索引),存储引擎会自己设置隐含的聚集索引。

一个叶子节点存放一行的数据,直接通过这个聚集索引的键值找到某行;不需要回表查询

数据的物理存放顺序与索引顺序是一致的(聚集索引决定存储顺序,一张表只能有一个聚集索引),只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的,所以不管插入的先后顺序,数据都会存放到索引对应的位置。

数据行和相邻的键值紧凑地存储在一起,无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚集索引。

使用聚集索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作(所有的记录就需要重新进行排序并重新写入到磁盘中,所以效率相比于非聚集索引可能会降低。 ),效率会比非聚集索引(非聚集索引只是存储索引,我们只需要更新这个索引即可,不需要对所有的记录重新排序。)低。

非聚集索引(不一定存在):

非聚集索引的叶子节点存储的是数据位置。

叶子节点存放的是字段的值,通过这个非聚集索引的键值找到对应的聚集索引字段的值,再通过聚集索引键值找到表的某行。(回表查询)

通俗理解:

  • 聚集索引 :类似字典正文内容按照目录排列规则排序。
  • 非聚集索引:只存放某些字在字典的位置
  • 每个表只能有一个聚集索引,因为目录只能按照一种方法进行排序 。

聚集索引和非聚集索引的区别:

聚集索引决定存储顺序,非聚集索引不影响存储顺序。

聚集索引的叶子节点存储的就是我们的数据记录,非聚集索引的叶子节点存储的是数据位置。

一个表只能有一个聚集索引,因为只能有一种排序存储的方式,但可以有多个非聚集索引,也就是多个索引目录提供数据检索。

使用聚集索引的时候,数据的查询效率高,但如果对数据进行插入,删除,更新等操作,效率会比非聚集索引低。

聚集索引和主键索引的区别:

如果我们定义了主键(主键索引),那么InnoDB会选择其作为聚集索引;

如果没有显式定义主键,则InnoDB会选择第一个非空唯一索引作为主键索引;

如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。

每个表都需要有一个聚集索引。

回表查询:

当查询非聚集索引时,查询到的结果是索引字段的地址(主键),而不是一条行记录。如果查询的字段不完全在这个非聚集索引当中,就会通过主键再去查询聚集索引,得到整条行记录。第二次查询就是回表查询。

 

 什么是索引覆盖(Covering index)

将被查询的字段,建立到联合索引里去,这样就可以避免回表查询,大大提高了查询效率。

 

单一索引和联合索引

联合索引的最左匹配原则:联合索引(a,b,c),查询a、ab、abc时可以用到索引,其他查询下联合索引就会失效。

SQL 条件语句中的字段顺序并不重要,因为在逻辑查询优化阶段会自动进行查询重写。

eg:查询“a=9 AND c=8 AND b=7” ,MySQL 的优化器可以自动帮我们调整为可以使用联合索引的形式。

如果遇到了范围条件查询,比如(<)(<=)(>)(>=)和 between 等,那么范围列后的列就无法使用到索引了

eg:当查询“x=9 AND y>8 AND z=7”的时候,如果建立了 (x,y,z) 顺序的索引,这时候 z 是用不上索引的。这是因为 MySQL 在匹配联合索引最左前缀的时候,如果遇到了范围查询,比如(<)(>)和 between 等,就会停止匹配。索引列最多作用于一个范围列,对于后面的 Z 来说,就没法使用到索引了。

索引的数据结构

索引其实就是一种数据结构,存放在硬盘上(永久保存)。

索引的优化概念

当在硬盘上进行查询时,会产生了硬盘的 I/O 操作。相比于内存的存取来说,硬盘的 I/O 存取消耗的时间要高很多。所以通过索引来查找某行数据的时候,需要计算产生的磁盘 I/O 次数,当磁盘 I/O 次数越多,所消耗的时间也就越大。如果能让索引的数据结构尽量减少硬盘的 I/O 操作,所消耗的时间也就越小。

二叉树做索引

树的深度决定了节点的比较次数,继而决定硬盘 I/O 操作的次数,最坏情况下二叉树的深度很大,因此不适合做索引。

 平衡二叉树

常见的平衡二叉树有很多种,包括了平衡二叉搜索树、红黑树、数堆、伸展树。平衡二叉搜索树是最早提出来的自平衡二叉搜索树,当我们提到平衡二叉树时一般指的就是平衡二叉搜索树。它在二分搜索树的基础上增加了约束,每个节点的左子树和右子树的高度差不能超过 1,也就是说节点的左子树和右子树仍然为平衡二叉树。 

当数据量 N 大的时候,以及树的分叉数 M 大的时候,M 叉树的高度会远小于二叉树的高度。

B树(平衡M叉树)

如果用二叉树作为索引的实现结构,会让树变得很高,增加硬盘的 I/O 次数,影响数据查询的时间。因此一个节点就不能只有 2 个子节点,而应该允许有 M 个子节点。当数据量 N 大的时候,以及树的分叉数 M 大的时候,M 叉树的高度会远小于二叉树的高度。

在文件系统和数据库系统中的索引结构经常采用 B 树来实现。

 磁盘块3处的38应为一个小于35大于26的数

B 树作为平衡的多路搜索树,它的每一个节点最多可以包括 M 个子节点,M 称为 B 树的阶。同时,每个磁盘块中包括了关键字和子节点的指针。如果一个磁盘块中包括了 x 个关键字,那么指针数就是 x+1。对于一个 100 阶的 B 树来说,如果有 3 层的话最多可以存储约 100 万的索引数据。对于大量的索引数据来说,采用 B 树的结构是非常适合的,因为树的高度要远小于二叉树的高度。

假设中间节点节点的关键字为:Key[1], Key[2], …, Key[k-1],且关键字按照升序排序,即 Key[i]。此时 k-1 个关键字相当于划分了 k 个范围,也就是对应着 k 个指针,即为:P[1], P[2], …, P[k],其中 P[1]指向关键字小于 Key[1]的子树,P[i]指向关键字属于 (Key[i-1], Key[i]) 的子树,P[k]指向关键字大于 Key[k-1]的子树。

查找过程:

假设想要查找的关键字是 9,那么步骤可以分为以下几步:

与根节点的关键字 (17,35)进行比较,9 小于 17 那么得到指针 P1;

按照指针 P1 找到磁盘块 2,关键字为(8,12),因为 9 在 8 和 12 之间,所以得到指针 P2;

按照指针 P2 找到磁盘块 6,关键字为(9,10),然后找到了关键字 9。

在 B 树的搜索过程中,比较的次数并不少,但如果把数据读取出来然后在内存中进行比较,这个时间就是可以忽略不计的。而读取磁盘块本身需要进行 I/O 操作,消耗的时间比在内存中进行比较所需要的时间要多,是数据查找用时的重要因素,B 树相比于平衡二叉树来说磁盘 I/O 操作要少,在数据查询中比平衡二叉树效率要高。

B+树

B+ 树基于 B 树做出了改进,主流的 DBMS 都支持 B+ 树的索引方式,比如 MySQL。

B+ 树和 B 树的差异在于以下几点:

  • 有 k 个孩子的节点就有 k 个关键字。也就是孩子数量 = 关键字数,而 B 树中,孩子数量 = 关键字数 +1。
  • 非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大(或最小)。
  • 非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。而 B 树中,非叶子节点既保存索引,也保存数据记录。
  • 所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。

 每一层父节点的关键字都会出现在下一层的子节点的关键字中,因此在叶子节点中包括了所有的关键字信息,并且每一个叶子节点都有一个指向下一个节点的指针,这样就形成了一个链表。

 看起来 B+ 树和 B 树的查询过程差不多,但是 B+ 树和 B 树有个根本的差异在于,B+ 树的中间节点并不直接存储数据。这样的好处都有什么呢?

树的深度更矮

 树的阶数M最好要控制到使得每读取一个磁盘块不要超过 磁盘页 的大小,而B+树因为每个结点关键字占用空间更小(因为不用存储真正的数据记录(的指针)),M就可以设置的更大一些,这样还是B+树整体上不仅偏稳定,而且树的高度也要相对更低一些。

范围查询io操作更少

其次,关系数据库中还会大量使用范围查询、有序查询等,比如某时间范围内的用户交易数据。范围查询,这种查询的特点是会大量使用排序,比较,返回结果也往往是多条。 如果使用b树的话,需要使用中序遍历,因为数据节点不在同一层上,会频繁引起io,从而导致整体速度下降。而在b+树中,所有的数据节点都在叶子节点,相近的叶子节点之间也存在着链接,因此会节约io时间。

Hash索引

采用 Hash 进行检索效率非常高,基本上一次检索就可以找到数据,而 B+ 树需要自顶向下依次查找,多次访问节点才能找到数据,中间需要多次 I/O 操作,从效率来说 Hash 比 B+ 树更快。

Hash 索引与 B+ 树索引的区别

  • Hash 索引不能进行范围查询,而 B+ 树可以。这是因为 Hash 索引指向的数据是无序的,而 B+ 树的叶子节点是个有序的链表。
  • Hash 索引不支持联合索引的最左侧原则(即联合索引的部分索引无法使用,只能对联合索引全体使用),而 B+ 树可以。对于联合索引来说,Hash 索引在计算 Hash 值的时候是将索引键合并后再一起计算 Hash 值,所以不会针对每个索引单独计算 Hash 值。因此如果用到联合索引的一个或者几个索引时,联合索引无法被利用。
  • Hash 索引不支持 ORDER BY 排序,因为 Hash 索引指向的数据是无序的,因此无法起到排序优化的作用,而 B+ 树索引数据是有序的,可以起到对该字段 ORDER BY 排序优化的作用。同理,我们也无法用 Hash 索引进行模糊查询,而 B+ 树使用 LIKE 进行模糊查询的时候,LIKE 后面前模糊查询(比如 % 开头)的话就可以起到优化作用。

总结

只有对于等值查询来说,Hash 索引的效率更高,不过也存在一种情况,就是索引列的重复值如果很多,效率就会降低。这是因为遇到 Hash 冲突时,需要遍历桶中的行指针来进行比较,找到查询的关键字,非常耗时。所以,Hash 索引通常不会用到重复值多的列上,比如列为性别、年龄的情况等。

通过索引提高SQL查询效率

哪些情况下创建索引?

1. 当字段的数值具有唯一性的限制

索引本身可以起到约束的作用,比如唯一索引、主键索引都是可以起到唯一性约束的,因此在我们的数据表中,如果某个字段是唯一性的,就可以直接创建唯一性索引,或者主键索引。

2. 频繁作为WHERE查询的字段特别是数据量大的情况下

在数据量大的情况下,某个字段在 SQL 查询的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。创建普通索引就可以大幅提升数据查询的效率。

3. 经常使用GROUP BY 或者 ORDER BY的情况下

索引就是让数据按照某种顺序进行存储或检索,因此当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,就需要对分组或者排序的字段进行索引。

实际上多个单列索引在多条件查询时只会生效一个索引(MySQL 会选择其中一个限制最严格的作为索引),所以在多条件联合查询的时候最好创建联合索引。

4..UPDATE、DELETE 的 WHERE 条件列,一般也需要创建索引

当对数据update、delete时,对where的字段加索引,也可以提高操作效率(要先查询到位置,再执行update或delete操作)。

5.DISTINCT 字段需要创建索引

注意:做多表 JOIN 连接操作时,创建索引需要注意以下的原则:

1. 连接表的数量不能超过3张

2. 其次,对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤。

3. 对用于连接外表的字段创建索引,并且该字段在多张表中的类型必须一致。

不需要加索引的情况

1.where、group by 、order by里用不到的字段不需要创建索引

2.如果表记录太少,比如少于 1000 个,那么是不需要创建索引的

3. 字段中如果有大量重复数据,也不用创建索引。(但是当比例特别悬殊时,也可以创建。)

4.最后一种情况是,频繁更新的字段不一定要创建索引。因为更新数据的时候,也需要更新索引,如果索引太多,在更新索引的时候也会造成负担,从而影响效率。

索引失效的情况

1. 索引进行了表达式计算,则会失效

如果对索引进行了表达式计算,索引就失效了。这是因为我们需要把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式,运行时间也会慢很多

eg:

SELECT comment_id, user_id, comment_text FROM product_comment WHERE comment_id+1 = 900001

2. 对索引使用函数,也会造成失效


EXPLAIN SELECT comment_id, user_id, comment_text FROM product_comment WHERE SUBSTRING(comment_text, 1,3)='abc'

EXPLAIN会分析select语句的运行性能状态。

3.在 WHERE 子句中,如果在 OR 前的条件列进行了索引,而在 OR 后的条件列没有进行索引,那么索引会失效

因为 OR 的含义就是两个只要满足一个即可,因此只有一个条件列进行了索引是没有意义的,只要有条件列没有进行索引,就会进行全表扫描,因此索引的条件列也会失效

4. 当我们使用 LIKE 进行模糊查询的时候,前面不能是 %

如果一本字典按照字母顺序进行排序,我们会从首位开始进行匹配,而不会对中间位置进行匹配,否则索引就失效了。

5. 联合索引的最左原则
不按照最左原则查询字段,也会使得索引失效

注意:索引列尽量设置为 NOT NULL 约束。

尽量将数据表的字段设置为 NOT NULL 约束,这样做的好处是可以更好地使用索引,节省空间,甚至加速 SQL 的运行。

判断索引列是否为 NOT NULL,往往需要走全表扫描,因此我们最好在设计数据表的时候就将字段设置为 NOT NULL 约束

比如你可以将 INT 类型的字段,默认值设置为 0。将字符类型的默认值设置为空字符串 ('')。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值