『 MySQL篇 』:MySQL 索引相关问题

Posted 署前街的少年

tags:

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

目录

一 . 认识索引

二. 索引的数据结构

1 . B+ Tree vs Hash

2 . B+ Tree vs 二叉树/红黑树

3 . B + 树 vs B树

三. 索引的使用

1. 索引分类

2. 索引用法


一 . 认识索引

当我们在查询一本书中的内容时 , 你会选择翻页每一页去查询呢 ? 还是说按照书的目录去找 ? 答案是肯定的 , 在数据库中 , 索引就是帮助存储引擎快速获取数据的一种数据结构 , 简单来说 , 索引就是数据的目录, 索引本质上是为了加块查找的速度 .

例 : 查找 student 表中 id = 8 的学生信息

如果没有索引 , 此时查找的时候就会将整张表进行一次遍历 , 即全盘扫描 . 通过设置索引可以提高数据检索的效率 , 降低数据库 IO的成本,降低CPU的消耗.

mysql 5.5 之后 , 默认使用InnoDB 作为存储引擎 , 所谓存储引擎 , 简单来说就是如何存储数据,如何为存储的数据建立索引 , 和如何更新 ,查询数据等技术的实现方法,MySQL 存储引擎有 MyISAM 、InnoDB、Memory等, 下图即 MySQL 的结构图 , 索引和数据就是存储在存储引擎中.

二. 索引的数据结构

InnoDB 是在 MySQL 5.5 之后成为默认的 MySQL 存储引擎,B+Tree 索引类型也是 MySQL 存储引擎采用最多的索引类型。

为什么 MySQL InnoDB 选择 B+tree 作为索引的数据结构?

1 . B+ Tree vs Hash

数据库的索引可以考虑使用 Hash , 在进行等值查询的时候效率较高 , 搜索的复杂度仅为 O(1) .

为何能够通过 key 快速取出 value 呢? 原因在于 哈希算法(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index,找到了 index 也就找到了对应的 value。

hash = hashfunc(key)
index = hash % array_size
select * from student where id > 3 and id < 6;
#像这样的操作,哈希表无法完成

但是 Hash 仅能处理相等的情况 , 对于 < , >, <= , >= 以及 between .. and .. 的情况无法处理 .

mysql 中 ,支持hash 索引的是 Memory 引擎 , 而 InnoDB 中具有自适应 hash 功能 , hash 索引是存储引擎 根据 B+ Tree 索引 在指定条件下自动构建的

2 . B+ Tree vs 二叉树/红黑树

对于 N 个节点的B+树 , 其搜索的时间复杂度为 O(log(dN)) , 其中 d 表示节点允许的最大子节点的个数为 d 个. 我们在实际应用中 , 即使数据到达千万级别时 , B+ Tree 的高度依然维持在 3-4 层左右 .

而二叉树的每个节点的儿子节点只能为 2 个 , 搜索复杂度较高 , 因此检测到目标数据所经历的 磁盘的 I/O 次数更多 .

当进行顺序插入时 , 二叉树就会形成一个链表 , 层级较深 , 检索速度较慢 .

当我们采用红黑树时 , 虽然解决了顺序插入时层级较深的问题 , 但是红黑树也是一种特殊的二叉树,大数据量时 , 层级较深问题仍未解决 .

针对以上提出的问题 , 我们能否构造一个树可以包含多个子节点 , 来解决层级较深的问题 ?

3 . B + 树 vs B树

B 树 , 也成为 多路平衡查找树 , 下面以一颗最大度数为 5 的 B-tree 为例 (每个节点最多存储 4 个key , 5 个指针)

插入数据 : 100 65 169 368 900 556 780 35 215 1200 234 888 158 90 1000 88 120 268 250

构建出的 B 树 如下所示

由上图可知 但是 B 树的每个节点都包含数据(索引+记录),而用户的记录数据的大小很有可能远远超过了索引数据,这就需要花费更多的磁盘 I/O 操作次数来读到「有用的索引数据」。

4 . 认识 B+树

B+ 树就是对 B 树做了一个升级 , 主要区别在于 :

  • B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
  • B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
  • B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子的过程, 叶子节点的顺序检索较为明显 。

效率提升 :

1 . 单点查询 : 数据量相同的情况下,相比存储即存索引又存记录的 B 树,B+树的非叶子节点可以存放更多的索引,因此 B+ 树可以比 B 树更「矮胖」,查询底层节点的磁盘 I/O次数会更少 .

2 . 插入删除效率 : B+ 树在删除根节点的时候,由于存在冗余的节点,所以不会发生复杂的树的变形 , 而删除 B 树的根节点时 , 可能会导致较为复杂的变形等 , 插入节点时 , B+树也会自动进行平衡 , 因此 B+ 树的删除和插入的效率更高 .

3 . 范围查询 : B+ 树叶子节点之间用链表连接了起来,有利于范围查询,而 B 树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘 I/O 操作,范围查询效率不如 B+ 树。

三. 索引的使用

什么情况下适合适应索引 ?

1 . 字段有唯一性限制的 , 比如商品编码 .

2 . 经常用于 where 查询的字段 , 经常用于 group by 和 order by 的字段

1. 索引分类

索引的分类可以按照多种条件去进行分类 :

  • 聚簇索引

聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,索引结构的叶子节点保存了行数据。InnoDB 中的主键索引就属于聚簇索引。

在 MySQL 中,InnoDB 引擎的表的 .ibd 文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。

若使用 "where id = 14 "这样的条件来查找主键 , 按照  B+ 树的检索算法即可查到对应的叶子结点 , 之后获取到行数据。

聚簇索引默认为主键 ,如果表中没有定义主键 , InnoDB 会选择一个唯一且非空的(unique not null) 的索引进行代替, 如果没有这样的索引 , InnoDB 会隐式定义一个主键作为默认索引,如果已经设置了主键为聚簇索引 , 又希望单独设置聚簇索引 , 必须先删除主键 ,最后恢复设置主键即可 。 

  • 非聚簇索引

非聚簇索引指的是将索引和数据分开存储, 索引结构的叶子节点指向了数据存储的位置, 二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。

若对 Name 列进行条件搜索, 则需要两个步骤 : 第一步在辅助索引 B+ 树中检索 Name , 到达其叶子节点获取对应的主键 ,第二步使用主键在主索引 B+ 树中再执行一次 B+ 树检索操作 ,最终到达叶子节点即可获取到整行数据 。 

非聚簇索引的优缺点 :

  • 优点 :更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的
  • 缺点依赖于有序的数据 :跟聚簇索引一样,非聚簇索引也依赖于有序的数据 ,可能会二次查询(回表) :这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。

2. 索引用法

下面对主要的索引类型的用法进行介绍 :

1 . 主键索引

主键索引就是建立在主键字段上的索引,通常在创建表的时候一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值。

在创建表时,创建主键索引的方式如下:

CREATE TABLE table_name  (
  ....
  PRIMARY KEY (index_column_1) USING BTREE
);

2 . 唯一索引

唯一索引建立在 UNIQUE 字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。在创建表时,创建唯一索引的方式如下:

CREATE TABLE table_name  (
  ....
  UNIQUE KEY(index_column_1,index_column_2,...) 
);

建表后,如果要创建唯一索引,可以使用这面这条命令:

CREATE UNIQUE INDEX index_name
ON table_name(index_column_1,index_column_2,...); 

3 . 普通索引

普通索引就是建立在普通字段上的索引,既不要求字段为主键,也不要求字段为 UNIQUE。

在创建表时,创建普通索引的方式如下:

CREATE TABLE table_name  (
  ....
  INDEX(index_column_1,index_column_2,...) 
);

建表后,如果要创建普通索引,可以使用这面这条命令:

CREATE INDEX index_name
ON table_name(index_column_1,index_column_2,...); 

4 . 联合索引

通过将多个字段组合成一个索引,该索引就被称为联合索引。

比如,将商品表中的 product_no 和 name 字段组合成联合索引(product_no, name),创建联合索引的方式如下:

CREATE INDEX index_product_no_name ON product(product_no, name);

示例 : 为书本表中的图书名称添加普通索引

mysql> create index index1 on book(book_name);
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

查看索引(通过每行显示)

mysql> show index from book\\G;
*************************** 1. row ***************************
        Table: book      // 表名
   Non_unique: 1
     Key_name: index1    // 索引名称
 Seq_in_index: 1
  Column_name: book_name // 索引行名称
    Collation: A
  Cardinality: 2
     Sub_part: NULL
       Packed: NULL
         Null: YES
   Index_type: BTREE    // 采用的数据结构
      Comment:
Index_comment:
1 row in set (0.00 sec)

删除书本名称的索引

mysql> drop index index1 on book;
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

什么情况下不需要创建索引 ?

索引最大的好处是提高查询速度,但是索引也是有缺点的,比如:

  • 需要占用物理空间,数量越大,占用空间越大;

  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大;

  • 会降低表的增删改的效率,因为每次增删改索引,B+ 树为了维护索引有序性,都需要进行动态维护。

所以 , 索引也并非万能 , 需要根据情况去进行使用 . 大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大的提升 。


MySQL高级篇——索引视图存储过程和函数触发器的相关概念及操作

1.索引

MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护者满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。如下面的示意图所示 :

左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找快速获取到相应数据。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。索引是数据库中用来提高性能的最常用的工具。

1.1 索引的优势及劣势

优势:

  1. 类似于书籍的目录索引,提高数据检索的效率,降低数据库的IO成本。
  2. 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。

劣势:

  1. 实际上索引也是一张表,该表中保存了主键与索引字段,并指向实体类的记录,所以索引列也是要占用空间的。
  2. 虽然索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。

1.2 索引结构

索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型的。MySQL目前提供了以下4种索引:

  • BTREE 索引 : 最常见的索引类型,大部分索引都支持 B 树索引。
  • HASH 索引:只有Memory引擎支持 , 使用场景简单 。
  • R-tree 索引(空间索引):空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少,不做特别介绍。
  • Full-text (全文索引) :全文索引也是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从Mysql5.6版本开始支持全文索引。

我们平常所说的索引,如果没有特别指明,都是指B+树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引、复合索引、前缀索引、唯一索引默认都是使用 B+tree 索引,统称为 索引。

1.2.1 BTREE结构(B树)

BTree又叫多路平衡搜索树,一颗m叉的BTree特性如下:

  • 树中每个节点最多包含m个孩子。
  • 除根节点与叶子节点外,每个节点至少有[ceil(m/2)]个孩子。
  • 若根节点不是叶子节点,则至少有两个孩子。
  • 所有的叶子节点都在同一层。
  • 每个非叶子节点由n个key与n+1个指针组成,其中[ceil(m/2)-1] <= n <= m-1

以5叉BTree为例,key的数量:公式推导[ceil(m/2)-1] <= n <= m-1。所以 2 <= n <=4 。当n>4时,中间节点分裂到父节点,两边节点分裂。

下面以插入 C N G A H E K Q M F W L T Z D P R X Y S 数据为例,整个B树的演变过程如下:👇👇👇

1). 插入前4个字母 C N G A。n没用超过4,这里正常插入。

2). 插入H,此时数据为 A C G H N,n > 4,所以中间元素G字母向上分裂到新的节点

3). 插入E,K,Q不需要分裂。比G小,存到左子树中;相反存到右子树中。

4). 插入M之后,上面的右子树为 H K M N Q,其中 n > 4了,所以中间元素M字母向上分裂到父节点G

5). 插入F,W,L,T不需要分裂

6). 插入Z之后,上面的右子树为 N Q T W Z,其中 n > 4了,中间元素T向上分裂到父节点中

7). 插入D,上面的左子树为 A C D E F,其中 n > 4了,中间元素D向上分裂到父节点中。然后插入P,R,X,Y不需要分裂

8). 最后插入S,因为 M < S < T,所以走根节点中M的右下方指针,也就是NPQR这个子结点,S进入之后节点 n > 5(N P Q R S),中间节点Q向上分裂,但分裂后父节点DGMT的 n > 5(D G M Q T),所以此时中间节点M会继续向上分裂。则M成为根节点,DG、QT分裂开。

到此,该BTREE树就已经构建完成了, BTREE树 和 二叉树 相比, 查询数据的效率更高, 因为对于相同的数据量来说,BTREE的层级结构比二叉树小,因此搜索速度快。(结合这篇文章开头的截图和上面的B树截图进行对比)

1.2.2 B+TREE结构(B+树)

B+Tree为BTree的变种,B+Tree与BTree的区别为:

  • n叉B+Tree最多含有n个key,而BTree最多含有n-1个key。
  • B+Tree的叶子节点保存所有的key信息,依key大小顺序排列。
  • 所有的非叶子节点都可以看作是key的索引部分。

由于B+Tree只有叶子节点保存key信息,查询任何key都要从root走到叶子。所以B+Tree的查询效率更加稳定。

1.2.3 MySQL中的B+Tree

MySql索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能。

1.3 索引分类

1) 单值索引 :即一个索引只包含单个列,一个表可以有多个单列索引
2) 唯一索引 :索引列的值必须唯一,但允许有空值
3) 复合索引 :即一个索引包含多个列

1.4 索引语法

首先在这里创建一个数据库,在这个数据库下建两张表,用作测试。

create database demo_01 default charset=utf8mb4;

use demo_01;

CREATE TABLE `city` (
    `city_id` int(11) NOT NULL AUTO_INCREMENT,
    `city_name` varchar(50) NOT NULL,
    `country_id` int(11) NOT NULL,
    PRIMARY KEY (`city_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `country` (
    `country_id` int(11) NOT NULL AUTO_INCREMENT,
    `country_name` varchar(100) NOT NULL,
    PRIMARY KEY (`country_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

然后分别向表中插入几条数据。

insert into `city` (`city_id`, `city_name`, `country_id`) values(1,'西安',1);
insert into `city` (`city_id`, `city_name`, `country_id`) values(2,'NewYork',2);
insert into `city` (`city_id`, `city_name`, `country_id`) values(3,'北京',1);
insert into `city` (`city_id`, `city_name`, `country_id`) values(4,'上海',1);

insert into `country` (`country_id`, `country_name`) values(1,'China');
insert into `country` (`country_id`, `country_name`) values(2,'America');
insert into `country` (`country_id`, `country_name`) values(3,'Japan');
insert into `country` (`country_id`, `country_name`) values(4,'UK');

下面我们来创建索引,语法如下:👇👇👇

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
[USING index_type]
ON tbl_name(index_col_name,...)

index_col_name : column_name[(length)][ASC | DESC]

查看索引:show index from table_name;

删除索引:DROP INDEX index_name ON tbl_name;

  • alter table tb_name add primary key(column_list);                        该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
  • alter table tb_name add unique index_name(column_list);           这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
  • alter table tb_name add index index_name(column_list);             添加普通索引, 索引值可以出现多次。
  • alter table tb_name add fulltext index_name(column_list);           该语句指定了索引为FULLTEXT, 用于全文索引

1.5 索引设计原则

索引的设计可以遵循一些已有的原则,创建索引的时候请尽量考虑符合这些原则,便于提升索引的使用效率,更高效的使用索引。

  • 对查询频次较高,且数据量比较大的表建立索引。
  • 索引字段的选择,最佳候选列应当从where子句的条件中提取,如果where子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。
  • 使用唯一索引,区分度越高,使用索引的效率越高。
  • 索引可以有效的提升查询数据的效率,但索引数量不是多多益善,索引越多,维护索引的代价自然也就水涨船高。对于插入、更新、删除等DML操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低DML操作的效率,增加相应操作的时间消耗。另外索引过多的话,MySQL也会犯选择困难病,虽然最终仍然会找到一个可用的索引,但无疑提高了选择的代价。
  • 使用短索引,索引创建之后也是使用硬盘来存储的,因此提升索引访问的I/O效率,也可以提升总体的访问效率。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的可以有效的提升MySQL访问索引的I/O效率。
  • 利用最左前缀,N个列组合而成的组合索引,那么相当于是创建了N个索引,如果查询时where子句中使用了组成该索引的前几个字段,那么这条查询SQL可以利用组合索引来提升查询效率。

2.视图

视图(View)是一种虚拟存在的表。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图就是一条SELECT语句执行后返回的结果集。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。

视图相对于普通的表的优势主要包括以下几项。

  • 简单:使用视图的用户完全不需要关心后面对应的表的结构、关联条件和筛选条件,对用户来说已经是过滤好的复合条件的结果集。
  • 安全:使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。
  • 数据独立:一旦视图的结构确定了,可以屏蔽表结构变化对用户的影响,源表增加列对视图没有影响;源表修改列名,则可以通过修改视图来解决,不会造成对访问者的影响。

创建视图,语法如下:👇👇👇

CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]

修改视图:👇👇👇

ALTER [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]

查看视图:👇👇👇

从 MySQL 5.1 版本开始,使用 SHOW TABLES 命令的时候不仅显示表的名字,同时也会显示视图的名字,而不存在单独显示视图的 SHOW VIEWS 命令。

删除视图:👇👇👇 

DROP VIEW [IF EXISTS] view_name [, view_name] ...[RESTRICT | CASCADE] 

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

MySQL高级篇——索引解决查询相关的优化问题

MySQL高级篇——索引解决查询相关的优化问题

关于MySQL,你未必知道的!

MySQL高级篇——索引视图存储过程和函数触发器的相关概念及操作

MySQL进阶篇之MySQL索引

MySQL——索引视图事务,存储引擎MyLSAM和InnoDB(实战篇!)