索引的官方定义是这样的:在关系数据库中,索引是一种单独的、物理的数对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。那么要如何理解索引呢?
在我们使用数据库时随着数据越来越多查找的速度会变得越来越慢这不利于我们的使用,为了解决这个问题程序员们创造了索引。我们可以把索引理解成一本书中的一个目录,在没有目录的时候我们想要找到特定的内容就只能一页页的去翻在有了目录后我们就可以直接跳转到特定的页上。那么我们就来看看索引是不是真的可以加快查找的速度。
//插入80万条记录
mysql> call insert_user(1, 8000000);
Query OK, 0 rows affected (9 min 46.71 sec)
//不使用索引查找
mysql> select * from test_user where id_number=556677;
+-----------+----------------+------+---------------------+
| id_number | name | age | create_time |
+-----------+----------------+------+---------------------+
| 556677 | Qduma Hmmbuunf | 61 | 2025-06-06 14:54:47 |
+-----------+----------------+------+---------------------+
1 row in set (2.69 sec)//耗时
mysql> select * from test_user where id_number=556677;
+-----------+----------------+------+---------------------+
| id_number | name | age | create_time |
+-----------+----------------+------+---------------------+
| 556677 | Qduma Hmmbuunf | 61 | 2025-06-06 14:54:47 |
+-----------+----------------+------+---------------------+
1 row in set (2.62 sec)
//创建id_number的索引
mysql> create index idx_test_user_id_number on test_user(id_number);
Query OK, 0 rows affected (14.71 sec)
Records: 0 Duplicates: 0 Warnings: 0
//使用索引查找
mysql> select * from test_user where id_number=556677;
+-----------+----------------+------+---------------------+
| id_number | name | age | create_time |
+-----------+----------------+------+---------------------+
| 556677 | Qduma Hmmbuunf | 61 | 2025-06-06 14:54:47 |
+-----------+----------------+------+---------------------+
1 row in set (0.00 sec)//耗时
而在MySQL中由于需要大量的进行磁盘和内存之间的交互也就是进行IO但是IO是非常消耗性能的那么想要提高性能就必须降低IO的次数。对于操纵系统来说降低IO次数也是必须所以操作系统自己就进行了优化也就是增大IO时读取的数据从而提高读取到有效数据的概率,这种方法同样遵循了计算机的局部性原理,这个原理提出了当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。所以我们将MySQL中读取一次的数据叫做一页,不同的操作系统对于页的大小也是不同的。这是操作系统自己做出的优化而索引的产生也是用来降低IO的次数但是存储引擎也有着自己的处理方法也就是在底层构建索引时使用不同的数据结构。
操作系统是通过增大读取数据的大小来减少IO次数而索引的作用就是通过排除错误的数据来减少IO次数有点类似于二分查找,每次查找时都会去除一半的错误答案。那么想要完成这点我们就需要利用一个数据结构B+树。
想要了解B+树我们就需要先了解它的原型体B树以及为什么大部分存储引擎是使用B+树而不是B树。
B树全称平衡多路查找树,它的结构是这样的
我们可以发现B树的特点是:
所以对于B树来说每个节点都是KV结构但是对于B+树来说只有叶子节点才是KV结构
那么为什么是使用B+树而不是B树呢那么我需要先从索引具体是如何实现减少IO次数的来说了,在不适用索引时对于大量的数据我们想要查找到具体的内容我们需要对这些数据进行遍历来查找所以IO的次数会很多但是在使用了索引后我们可以通过索引来指向具体的数据,就像书中的目录一样目录越多查找的难度就越低如果对于一本书里的每一个字都有目录那么查找的难道就大大降低了。
而无论是B树还是B+树都是将key作为目录value作为数据所以key的数量越多那么查找value的速度就越快。所以这就是为什么要选择B+树而不是B树因为B树的每个节点都是KV结构所以key的数量有一部分被变成了value而B+树只有叶子节点才有KV结构所以key的数量就更多查找的速度也就更快。
并且由于局部性原理很有可能读取一个数据后再读取它相邻的数据但是对于B树来说我们每次读取数据都需要从头再次查找而B+树我们可以利用叶子节点之间的指针来查找相邻的数据。这也就加快了速度。
注意:不同的存储引擎对于数据结构的选择是不一样的大部分都是选择B+树少部分选择是B树也有个例引擎选择的是hash。
在了解了实现索引的数据结构之后我们知道使用索引其实就是将数据库中的数据构造一个B+树出来(大部分的),但是对于数据的存储方式不同索引的类型也不同我们将其分为聚簇索引和非聚簇索引。这里我们的存储引擎默认为InnoDB。
对于这两种索引一般情况下聚簇索引的查找速度是高于非聚簇索引的并且对于范围查询时由于非聚簇索引需要进行回表查询导致范围查询时速度就更不如聚簇索引了,但是非聚簇索引的优势是当表发生频繁的插入删除时由于聚簇索引决定了表的物理存储顺序所以会影响它频繁发生变化,而非聚簇索引是将主键值作为指针所以只要主键值不变那么非聚簇索引的B+树就不会发生变化也就减少了维护的成本。
索引一样有着不同的种类,一般可以我们可以从存储引擎和逻辑上分别进行分类,从存储引擎上将其分为聚簇索引和非聚簇索引这部分我们在索引的原理上会提到。现在我们先用逻辑上进行分类,从逻辑上索引被分为普通索引,唯一索引,主键索引,全文索引。同样我们以InnoDB为例。
接下来就是索引的使用,我们会介绍怎么增删查改普通索引,唯一索引,主键索引以及组合索引。
想要创建一个索引有两种方法分别是自动创建和手动创建,其中自动创建是指当我们为表中增加主键,唯一键和外键时MySQL会自动使用对应列创建一个索引。
手动创建就需要我们自己使用SQL来指明对应列创建索引了
//1.创建表时创建主键
mysql> create table test1(
-> id int primary key auto_increment,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> show keys from test1\G;
*************************** 1. row ***************************
Table: test1
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
1 row in set (0.00 sec)
ERROR:
No query specified
//2.创建表时指明某列为主键
mysql> create table test2(
-> id int,
-> name varchar(20),
-> primary key(id)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> show keys from test2\G;
*************************** 1. row ***************************
Table: test2
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
1 row in set (0.01 sec)
ERROR:
No query specified
//3.创建表后将某列修改为主键
mysql> create table test3(
-> id int,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> alter table test3 add primary key(id);
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test3\G;
*************************** 1. row ***************************
Table: test3
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
1 row in set (0.00 sec)
//1.创建表时创建唯一键
mysql> create table test4(
-> id int primary key auto_increment,
-> name varchar(20) unique
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> show keys from test4\G;
*************************** 1. row ***************************
Table: test4
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: test4
Non_unique: 0
Key_name: name
Seq_in_index: 1
Column_name: name
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)
ERROR:
No query specified
//2.创建表时指明某列为唯一键
mysql> create table test5(
-> id int primary key auto_increment,
-> name varchar(20),
-> unique(name)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> show keys from test5\G;
*************************** 1. row ***************************
Table: test5
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: test5
Non_unique: 0
Key_name: name
Seq_in_index: 1
Column_name: name
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.01 sec)
ERROR:
No query specified
//创建表后修改某列为唯一键
mysql> create table test6(
-> id int,
-> name varchar(20)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> alter table test6 add unique(name);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test6\G;
*************************** 1. row ***************************
Table: test6
Non_unique: 0
Key_name: name
Seq_in_index: 1
Column_name: name
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.00 sec)
ERROR:
No query specified
//1.创建表指定索引列
mysql> create table test7(
-> id int primary key auto_increment,
-> name varchar(20) unique,
-> age int,
-> index(age)
-> );
Query OK, 0 rows affected (0.02 sec)
mysql> show keys from test7\G;
*************************** 1. row ***************************
Table: test7
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: test7
Non_unique: 0
Key_name: name
Seq_in_index: 1
Column_name: name
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 3. row ***************************
Table: test7
Non_unique: 1
Key_name: age
Seq_in_index: 1
Column_name: age
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)
ERROR:
No query specified
//2.修改表中的某列为普通索引
mysql> create table test8(
-> id int,
-> name varchar(20),
-> age int
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> alter table test8 add index(age);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test8\G;
*************************** 1. row ***************************
Table: test8
Non_unique: 1
Key_name: age
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.00 sec)
ERROR:
No query specified
//3.单独创建索引并指定索引名
mysql> create table test9(
-> id int,
-> name varchar(20),
-> age int
-> );
Query OK, 0 rows affected (0.02 sec)
mysql> show keys from test9\G;
Empty set (0.00 sec)
ERROR:
No query specified
mysql> create index myindex on test9(age);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test9\G;
*************************** 1. row ***************************
Table: test9
Non_unique: 1
Key_name: myindex
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
1 row in set (0.00 sec)
ERROR:
No query specified
//1.修改表中的列为复合索引
mysql> create table test10(
-> id int,
-> name varchar(20),
-> age int,
-> sno varchar(10)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> alter table test10 add index(age,sno);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test10\G;
*************************** 1. row ***************************
Table: test10
Non_unique: 1
Key_name: age
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: test10
Non_unique: 1
Key_name: age
Seq_in_index: 2
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 rows in set (0.00 sec)
ERROR:
No query specified
//2.创建表时指定索引列
mysql> create table test11(
-> id int,
-> name varchar(20),
-> age int,
-> sno varchar(10),
-> index (age,sno)
-> );
Query OK, 0 rows affected (0.02 sec)
mysql> show keys from test11\G;
*************************** 1. row ***************************
Table: test11
Non_unique: 1
Key_name: age
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: test11
Non_unique: 1
Key_name: age
Seq_in_index: 2
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 rows in set (0.00 sec)
ERROR:
No query specified
//3.单独创建索引并指定索引名
mysql> create table test12(
-> id int,
-> name varchar(20),
-> age int,
-> sno varchar(10)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> create index joindex on test12(age,sno);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> show keys from test12\G;
*************************** 1. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 2
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 rows in set (0.00 sec)
ERROR:
No query specified
查询所有的SQL指令很简单,在上面创建索引时我也演示过了
//1.查看一个表中的全部索引
mysql> show keys from test12\G;
*************************** 1. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 2
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 rows in set (0.00 sec)
//2.查看一个表中的所有索引
mysql> show index from test12\G;
*************************** 1. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 1
Column_name: age
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment:
Index_comment:
Visible: YES
Expression: NULL
*************************** 2. row ***************************
Table: test12
Non_unique: 1
Key_name: joindex
Seq_in_index: 2
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 rows in set (0.00 sec)
ERROR:
No query specified
//3.查看索引的大致信息
mysql> desc test7;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | UNI | NULL | |
| age | int | YES | MUL | NULL | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
这几种索引的删除方式都大差不大我们就直接举例即可
//1.删除主键索引
mysql> desc test1;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
//删除主键索引时如果主键索引有自增属性就需要先删除自增属性
mysql> alter table test1 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 test1 modify id int;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> alter table test1 drop primary key;
Query OK, 0 rows affected (0.03 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
//2.删除其他索引
mysql> desc test4;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | UNI | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> alter table test4 drop index name;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc test4;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
当我们为某列创建索引后我们查找数据就会自动使用索引,所以索引的使用没有什么可说的但是对于复合索引我们需要注意一个问题:最左匹配原则。这个原则规定了在使用复合索引时第一个列必须出现在查找语句中才会使用到复合索引。
当我们使用三个列创建一个复合索引(id,name,age)时我们实际上是创建了三个索引分别是(id),(id,name),(id,name,age)。
mysql> create table test13( id int, name varchar(20), age int, index(id,name,age) );
Query OK, 0 rows affected (0.01 sec)
mysql> show keys from test13;
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| test13 | 1 | id | 1 | id | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
| test13 | 1 | id | 2 | name | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
| test13 | 1 | id | 3 | age | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
+--------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
3 rows in set (0.00 sec)
mysql> desc test13;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | YES | MUL | NULL | |
| name | varchar(20) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
从这两个信息中我们可以发现第一列id是这个复合索引的主体,在查看表结构时复合索引是属于id列的并且它在索引中的顺序也是1。所以在我们使用索引进行查找数据时就必须带上id列不然就无法使用复合索引。
//可以使用复合索引
select * from test13 where id = 12;
select * from test13 where id = 1 and name = '张三' and age = 18;
select * from test13 where id = 2 and name = '张三';
select * from test13 where id = 3 and age = 18;
select * from test13 where age = 18 and name = '张三' and id = 1;
//不能使用复合索引
select * from test13 where name = '张三';
select * from test13 where name = '张三' and age = 18;