继增删改查相关内容后,我继续讲解数据库相关内容,这篇介绍索引相关知识,这也是数据库十分重要的一部分内容。
一、 简介
1.1索引是什么?
MySQL的索引是一种数据结构,它可以帮助数据库高效地查询、更新数据表中的数据。索引通过一定的规则排列数据表中的记录,使得对表的查询可以通过对索引的搜索来加快速度。
MySQL索引类似于书籍的目录,通过指向数据行的位置,可以快速定位和访问表中的数据,比如汉语字典的目录(索引)页,我们可以按笔画、偏旁部首、拼音等排序的目录(索引)快速查找到需要的字。
1.2为什么要使用索引?
显而易见,使用索引的目的只有一个,就是提升数据检索的效率,在应用程序的运行过程中,查询操作的频率远远高于增删改的频率。
二、 索引应该选择哪种数据结构
2.1 HASH
时间复杂度是0(1),查询速度非常快,但是MySQL并没有选择HASH做为索引的默认数据结构,主要原因是HASH不支持范围查找
2.2 二叉搜索树
二叉搜索树的中序遍历是一个有序数组,但有几个问题导致它不适合用作索引的数据结构
- 最坏情况下时间复杂度为O(N)
- 节点个数过多无法保证树高
AVL和红黑树,虽然是平衡或者近似平衡,但是毕竟是二叉结构
在检索数据时,每次访问某个节点的子节点时都会发生一次磁盘IO,而在整个数据库系统中,IO是性能的瓶颈,减少IO次数可以有效的提升性能
2.3 N叉树
为了解决树高的问题,可以使用N叉树
相同数据量的情况下,N叉树的树高可以得到有效控制,也就意味着相同数据量的情况下可以减少IO次数,从而提升效率。但是MySQL认为N叉树作为索引的数据结构还不够好。
2.4 B+树
2.4.1简介
B+树是一种经常用于数据库和文件系统等场合的平衡查找树,MySQL索引采用的数据结构。
2.4.2 B+树的特点
- 能够保持数据稳定有序,插入与修改有较稳定的时间复杂度
- 非叶子节点仅具有索引作用,不存储数据,所有叶子节点保真实数据
- 所有叶子节点构成一个有序链表,可以按照key排序的次序依次遍历全部数据
2.4.3 B+树与B树的对比
- 叶子节点中的数据是连续的,且相互链接,便于区间查找和搜索
- 非叶子节点的值都包含在叶子节点中
- 对于B+树而言,在相同树高的情况下,查找任一元素的时间复杂度都一样,性能均衡
三、 MySQL中的页
3.1 为什么要使用页
在.ibd文件中最重要的结构体就是Page(页) ,页是内存与磁盘交互的最小单元,默认大小 为16KB,每次内存与磁盘的交互至少读取一页,所以在磁盘中每个页内部的地址都是连续的,之所以这样做,是因为在使用数据的过程中,根据局部性原理,将来要使用的数据大概率与当前访问的数据在空间上是临近的,所以一次从磁盘中读取一页的数据放入内存中,当下次查询的数据还在这个页中时就可以从内存中直接读取,从而减少磁盘I/O提高性能
局部性原理:
是指程序在执行时呈现出局部性规律,在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域,局部性通常有两种形式:时间局部性和空间局部性。
时间局部性(TemporalLocality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。
空间局部性(SpatialLocality):将来要用到的信息大概率与正在使用的信息在空间地址上是临近的
每一个页中即使没有数据也会使用16KB的存储空间,同时与索引的B+树中的节点对应,查看页的大小,可以通过系统变量innodb_page_size查看
sql
mysql> SHOW VARIABLES LIKE 'innodb_page_size';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| innodb_page_size | 16384 | # 16KB
+------------------+-------+
1 row in set, 1 warning (0.04 sec)
在MySQL中有多种不同类型的页,最常用的就是用来存储数据和索引的"索引页 ",也叫做"数据页 ",但不论哪种类型的页都会包含页头(File Header) 和页尾(File Trailer) ,页的主体信息使用**数据"行"**进行填充,数据页的基本结构如下图所示:

3.2 页文件头和页文件尾
这里我们只关注,上一页页号和下一页页号,通过这两个属性可以把页与页之间链接起来,形成一个双向链表。
3.3 页主体
页主体部分是保存真实数据的主要区域,每当创建一个新页,都会自动分配两个行,一个是页内最小行Infimun,另一个是页内最大行Supremun,这两个行并不存储任何真实信息,而是做为数据行链表的头和尾,第一个数据行有一个记录下一行的地址偏移量的区域next_record将页内所有数据行组成了一个单向链表。
当向一个新页插入数据时,将Infimun连接第一个数据行,最后一行真实数据行连接Supremun,这样数据行就构建成了一个单向链表,更多的行数据插入后,会按照主键从小到大的顺序进行链接。
3.4 页目录
当按主键或索引查找某条数据时,最直接简单的方法就是从头行infimun开始,沿着链表顺序逐个比对查找,但一个页有16KB,通常会存在数百行数据,每次都要遍历数百行,无法满足高效查询,为了提高查询效率,InnoDB采用二分查找来解决查询效率问题;
具体实现方式是,在每一个页中加入一个叫做页目录Page Directory的结构,将页内包括头行、尾行在内的所有行进行分组,约定头行单独为一组,其他每个组最多8条数据,同时把每个组最后一行在页中的地址,按主键从小到大的顺序记录在页目录中在,页目录中的每一个位置称为一个槽,每个槽都对应了一个分组,一旦分组中的数据行超过分组的上限8个时,就会分裂出一个新的分组;
后续在查询某行时,就可以通过二分查找,先找到对应的槽,然后在槽内最多8个数据行中进行遍历即可,从而大幅提高了查询效率,这时一个页的核心结构就完成了;
例如要查找主键为6的行,先比对槽中记录的主键值,定位到最后一个槽2,再从最后一个槽中的第一条记录遍历,第二条记录就是我们要查询的目标行。
3.5 数据页头
数据页头记录了当前页保存数据相关的信息
四、 索引分类
4.1主键索引
- 当在一个表上定义一个主键PRIMARY KEY 时,InnoDB使用它作为聚集索引。
- 推荐为每个表定义一个主键。如果没有逻辑上唯一且非空的列或列集可以使用主键,则添加一个自增列。
4.2普通索引
- 最基本的索引类型,没有唯一性的限制。
- 可能为多列创建组合索引,称为复合索引或组全索引。
4.3 唯一索引
- 当在一个表上定义一个唯一键UNQUE时,自动创建唯一索引。
- 与普通索引类似,但区别在于唯一索引的列不允许有重复值。
4.4全文索引
- 基于文本列(CHAR、VARCHAR或TEXT列)上创建,以加快对这些列中包含的数据查询和DML操作
- 用于全文搜索,仅MyISAM和InnoDB引擎支持。
4.5聚集索引
- 与主键索引是同义词。
- 如果没有为表定义PRIMARY KEY,InnoDB使用第一个UNIQUE和NOT NULL的列作为聚集索。
- 如果表中没有PRIMARY KEY或合适的UNIQUE索引,InnoDB会为新插入的行生成一个行号并用6字节的ROW_ID字段记录,ROW_ID单调递增,并使用ROW_ID做为索引。
4.6 非聚集索引
- 聚集索引以外的索引称为非聚集索引或二级索引。
- 二级索引中的每条记录都包含该行的主键列,以及二级索引指定的列。
- InnoDB使用这个主键值来搜索聚集索引中的行,这个过程称为回表查询。
4.7索引覆盖
- 当一个select语句使用了普通索引且查询列表中的列刚好是创建普通索引时的所有或部分列,这时就可以直接返回数据,而不用回表查询,这样的现象称为索引覆盖。
五、 使用索引
5.1自动创建
- 当我们为一张表加主键约束(Primary key),外键约束(Foreign Key),唯一约束(Unique)时,MySQL会为对应的的列自动创建一个索引
- 如果表不指定任何约束时,MySQL会自动为每一列生成一个索引并用ROW_ID进行标识
5.2手动创建
5.2.1主键索引
sql
# ⽅式⼀,创建表时创建主键
create table t_test_pk (
id bigint primary key auto_increment,
name varchar(20)
);
# ⽅式⼆,创建表时单独指定主键列
create table t_test_pk1 (
id bigint auto_increment,
name varchar(20),
primary key (id)
);
# ⽅式三,修改表中的列为主键索引
create table t_test_pk2 (
id bigint,
name varchar(20)
);
alter table t_test_pk2 add primary key (id) ;
alter table t_test_pk2 modify id bigint auto_increment;
5.2.2 唯一索引
sql
# ⽅式⼀,创建表时创建唯⼀键
create table t_test_uk (
id bigint primary key auto_increment,
name varchar(20) unique
);
# ⽅式⼆,创建表时单独指定唯⼀列
create table t_test_uk1 (
id bigint primary key auto_increment,
name varchar(20),
unique (name)
);
# ⽅式三,修改表中的列为唯⼀索引
create table t_test_uk2 (
id bigint primary key auto_increment,
name varchar(20)
);
alter table t_test_uk2 add unique (name) ;
5.2.3 普通索引
sql
# ⽅式⼀,创建表时指定索引列
create table t_test_index (
id bigint primary key auto_increment,
name varchar(20) unique
sno varchar(10),
index(sno)
);
# ⽅式⼆,修改表中的列为普通索引
create table t_test_index1 (
id bigint primary key auto_increment,
name varchar(20),
sno varchar(10)
);
alter table t_test_index1 add index (sno) ;
# ⽅式三,单独创建索引并指定索引名
create table t_test_index2 (
id bigint primary key auto_increment,
name varchar(20),
sno varchar(10)
);
create index index_name on t_test_index2(sno);
5.3 创建复合索引
创建语法与创建普通索引相同,只不过指定多个列,列与列之间用逗号隔开
sql
# ⽅式⼀,创建表时指定索引列
create table t_test_index4 (
id bigint primary key auto_increment,
name varchar(20),
sno varchar(10),
class_id bigint,
index (sno, class_id)
);
# ⽅式⼆,修改表中的列为复合索引
create table t_test_index5 (
id bigint primary key auto_increment,
name varchar(20),
sno varchar(10),
class_id bigint
);
alter table t_test_index5 add index (sno, class_id);
# ⽅式三,单独创建索引并指定索引名
create table t_test_index6 (
id bigint primary key auto_increment,
name varchar(20),
sno varchar(10),
class_id bigint
);
create index index_name on t_test_index6 (sno, class_id);
5.4 查看索引
sql
# ⽅式⼀:show keys from 表名
mysql> show keys from t_test_index6\G
*************************** 1. row ***************************
Table: t_test_index6
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: t_test_index6
Non_unique: 1
Key_name: index_name
Seq_in_index: 1
Column_name: sno
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 3. row ***************************
Table: t_test_index6
Non_unique: 1
Key_name: index_name
Seq_in_index: 2
Column_name: class_id
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
3 rows in set (0.00 sec)
# ⽅式⼆
show index from t_test_index6;
# ⽅式三,简要信息:desc 表名;
desc t_test_index6;
5.5 删除索引
5.5.1 主键索引
sql
# 语法
alter table 表名 drop primary key;
# ⽰例,删除t_test_index6表中的主键
mysql> alter table t_test_index6 drop primary key;
ERROR 1075 (42000): Incorrect table definition; there can be only one auto
column and it must be defined as a key
# 如查提⽰由于⾃增列的错误,先删除⾃增属性
mysql> alter table t_test_index6 modify id bigint;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 重新删除主键
mysql> alter table t_test_index6 drop primary key;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 查看结果
mysql> show keys from t_test_index6\G
*************************** 1. row ***************************
Table: t_test_index6
Non_unique: 1
Key_name: index_name
Seq_in_index: 1
Column_name: sno
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: t_test_index6
Non_unique: 1
Key_name: index_name
Seq_in_index: 2
Column_name: class_id
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
2 rows in set (0.00 sec)
5.5.2 其他索引
sql
# 语法
alter table 表名 drop index 索引名;
# ⽰例,删除t_test_index6表中名为index_name的索引
mysql> alter table t_test_index6 drop index index_name;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
# 查看结果
mysql> show keys from t_test_index6\G
Empty set (0.00 sec)
5.6 创建索引的注意事项
- 索引应该创建在高频查询的列上
- 索引需要占用额外的存储空间
- 对表进行插入、更新和删除操作时,同时也会修索引,可能会影响性能
- 创建过多或不合理的索引会导致性能下降,需要谨慎选择和规划索引
索引相关内容就到这里结束了,后续数据库相关内容持续更新。。。