MySQL索引

一、什么是索引

索引是一种帮助数据库快速查找数据的数据结构,本质是用空间来换时间(额外占用存储,增加写入成本,换来查询时不用逐行扫描整表)类似书前面的目录

常见的索引有:

  • 主键索引
  • 唯一键索引
  • 普通索引
  • 全文索引

-- ============================================================

-- 一键脚本:建库、建函数、建表、建存储过程、造测试数据

-- 用法:mysql -u root -p < this_file.sql

-- ============================================================

drop database if exists `bit_index`;

create database if not exists `bit_index` default character set utf8mb4;

use `bit_index`;

-- 避免重复执行时报函数已存在

drop function if exists rand_string;

drop function if exists rand_num;

drop procedure if exists insert_emp;


-- 1. 产生随机字符串(6 位左右)


delimiter $$

create function rand_string(n INT)

returns varchar(255)

deterministic

begin

declare chars_str varchar(100) default

'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

declare return_str varchar(255) default '';

declare i int default 0;

while i < n do

set return_str = concat(return_str,

substring(chars_str, floor(1 + rand() * 52), 1));

set i = i + 1;

end while;

return return_str;

end $$

delimiter ;


-- 2. 产生随机部门号(10 ~ 509,可按需调整)


delimiter $$

create function rand_num()

returns int

deterministic

begin

declare i int default 0;

set i = floor(10 + rand() * 500);

return i;

end $$

delimiter ;


-- 3. 雇员表 EMP(先建表,再建存储过程)


CREATE TABLE `EMP` (

`empno` int(6) unsigned zerofill NOT NULL COMMENT '雇员编号',

`ename` varchar(10) DEFAULT NULL COMMENT '雇员姓名',

`job` varchar(9) DEFAULT NULL COMMENT '雇员职位',

`mgr` int(4) unsigned zerofill DEFAULT NULL COMMENT '雇员领导编号',

`hiredate` datetime DEFAULT NULL COMMENT '雇佣时间',

`sal` decimal(7,2) DEFAULT NULL COMMENT '工资月薪',

`comm` decimal(7,2) DEFAULT NULL COMMENT '奖金',

`deptno` int(2) unsigned zerofill DEFAULT NULL COMMENT '部门编号',

PRIMARY KEY (`empno`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='雇员表';


-- 4. 存储过程:批量插入 EMP 数据

-- start 起始编号偏移

-- max_num 插入条数


delimiter $$

create procedure insert_emp(in start int, in max_num int)

begin

declare i int default 0;

set autocommit = 0;

repeat

set i = i + 1;

insert into EMP(empno, ename, job, mgr, hiredate, sal, comm, deptno)

values (

start + i,

rand_string(6),

'SALESMAN',

0001,

curdate(),

2000,

400,

rand_num()

);

until i = max_num

end repeat;

commit;

set autocommit = 1;

end $$

delimiter ;


-- 5. 造数据

CALL insert_emp(100000, 5000000);

我这里创建了5,000,000个数据的表

(1)查询编号为356677的员工

mysql> select * from EMP where empno=356677;

+--------+--------+----------+------+---------------------+---------+--------+--------+

| empno | ename | job | mgr | hiredate | sal | comm | deptno |

+--------+--------+----------+------+---------------------+---------+--------+--------+

| 356677 | HNQTUR | SALESMAN | 0001 | 2026-10-24 00:00:00 | 2000.00 | 400.00 | 252 |

+--------+--------+----------+------+---------------------+---------+--------+--------+

1 row in set (1.95 sec)

可以看到花费了1.95s,这还是一个人操作,如果多人操作很容易导致死机

解决方法,创建索引

mysql> alter table EMP add index(empno);

Query OK, 0 rows affected (16.72 sec)

Records: 0 Duplicates: 0 Warnings: 0

然后再测试查询时间

mysql> select * from EMP where empno=356617;

+--------+--------+----------+------+---------------------+---------+--------+--------+

| empno | ename | job | mgr | hiredate | sal | comm | deptno |

+--------+--------+----------+------+---------------------+---------+--------+--------+

| 356617 | tylHEY | SALESMAN | 0001 | 2026-10-24 00:00:00 | 2000.00 | 400.00 | 67 |

+--------+--------+----------+------+---------------------+---------+--------+--------+

1 row in set (0.00 sec)

二、MySQL与磁盘交互

Mysql作为一款应用软件,可以想象成一种特殊的文件系统、有着更高的io场景,所以为了提高基本的IO效率,MySQL进行IO的基本单位是16kb

mysql> SHOW GLOBAL STATUS LIKE 'innodb_page_size';

+------------------+-------+

| Variable_name | Value |

+------------------+-------+

| Innodb_page_size | 16384 |

+------------------+-------+

1 row in set (0.01 sec)

16*1024=16384

也就是说,磁盘的这个硬件设备的基本单位是512字节,而MySQL InnoDB引擎使用16kb进行IO交互。即MySQL和硬盘进行数据交互的基本单位是16kb。这个基本数据单元,在MySQL里叫Page(和系统的Page不同)

MySQL中的数据文件都是以page为单位保存在磁盘当中的

MySQL的CURD操作,都需要计算来找到对应的位置

由于涉及到计算,就需要CPU参与,而为了CPU参与,一定要能够先将数据移动到内存当中。

所以在特定时间里,数据一定是磁盘中有,而内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘,这就涉及到磁盘和内存的数据交互了,也就是IO,而此时的IO的基本单位就是Page。

为了更好的进行上面的操作,MySQL服务器在内存中运行的时候,就申请了内存空间来进行缓存

为了更高的效率,一定要尽可能的减少系统和磁盘IO的次数

三、索引

建立测试表

mysql> create table user(

-> id int primary key,

-> age int not null,

-> name varchar(16) not null

-> );

Query OK, 0 rows affected (0.08 sec)

mysql> show create table user \G

*************************** 1. row ***************************

Table: user

Create Table: CREATE TABLE `user` (

`id` int NOT NULL,

`age` int NOT NULL,

`name` varchar(16) NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

1 row in set (0.00 sec)

插入

mysql> insert into user (id, age, name) values(3, 18, '杨过');

Query OK, 1 row affected (0.02 sec)

mysql> insert into user (id, age, name) values(4, 16, '小龙女');

Query OK, 1 row affected (0.02 sec)

mysql> insert into user (id, age, name) values(2, 26, '黄蓉');

Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(5, 36, '郭靖');

Query OK, 1 row affected (0.02 sec)

mysql> insert into user (id, age, name) values(1, 56, '欧阳锋');

Query OK, 1 row affected (0.02 sec)

查看插入数据 发现是有序

mysql> select * from user;

+----+-----+-----------+

| id | age | name |

+----+-----+-----------+

| 1 | 56 | 欧阳锋 |

| 2 | 26 | 黄蓉 |

| 3 | 18 | 杨过 |

| 4 | 16 | 小龙女 |

| 5 | 36 | 郭靖 |

+----+-----+-----------+

5 rows in set (0.00 sec)

中断一下---为何IO交互要是Page

为了提高缓存命中率

理解单个Page

MySQL中要管理很多数据表文件,而要管理好这些文件,就需要先描述在组织,我们可以理解为一个个独立文件是有一个或多个Page构成的

不同的Page,在MySQL中,都是16Kb,使用prev和next构造双向链表

因为有主键的问题,MySQL会默认根据主键来给我们的数据进行排序

为什么数据库在插入数据时要对其进行排序呢?我们按正常顺序插入数据不是也挺好的吗?
插入数据时排序的目的,就是优化查询的效率。
页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询
的效率是必须的。
正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是
可以提前结束查找过程的。

理解多个Page

通过上面分析,我知道了,上面页模式中,只有一个功能,就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。
如果有很多数据,一定要用多个Page来保存数据,多个Page彼此使用双链表连接起来,而且每个Page内部的数据也是基于链表的,那么查找特定一条记录,也一定是线性查找。

单页情况

在一个page内部,如果我们要查找id=4,之前必须进行遍历,但是现在直接通过目录23,进行定位新的起始数据,提高了效率。

多页情况

MySQL中每一页大小只有16kb,单个Page大小固定,随着数据量不断增大,16kb不可能存下所有数据,那么必定会有多个页来存储数据

在单表数据不断被插入的情况下,MySQL会在容量不足的时候,自动开辟新的数据,然后通过指针的方式,组织起来所有的Page。

由于page遍历也会很多,因此我们会给page也带上目录

其实目录页本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。

所以目录页也可被管理起来

这就类似于B+树,现在再进行寻找特定id的人,就会发现查找次数大幅减少,IO次数减少,效率提高。

B+vsB

B树

B+树

  • B树结点,既有数据,又有Page指针,而B+,只有叶子结点有数据,其他目录页,只有键值和Page结点
  • B+叶子结点,全部相连,而B没有

为什么选择B+

  • 节点不存储data,这样一个节点就可以存储更多key。可以使得树更矮,所以IO操作次数更少。
  • 叶子结点相连,更便于进行范围查找

聚簇索引VS非聚簇索引

非聚簇索引

MyISAM存储引擎-主键索引

MyISAM引擎同样使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址

其中,MyISAM最大的特点就是,将索引page和数据的page分离,也就是叶子结点没有数据,只有对应的地址

聚簇索引

相较于Innodb索引,Innodb是将索引和数据放在一起的。

InnoDB索引这种用户数据和索引数据分离的索引方案,叫做聚簇索引

当然,mysql除了会默认建立主键索引,还会根据用户的其他列信息建立索引,一般这种索引叫普通索引。

对于MyISAM,建立普通索引和主键索引没有什么差别,无非就是主键不能重复,而非主键可以重复。
下图就是基于 MyISAM 的 Col2 建立的索引,和主键索引没有差别

普通索引

同时,InnoDB除了主键索引,用户也会建立普通索引

普通索引,找到目标记录,需要两遍索引,先通过辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询。

四、索引操作

1.创建主键索引

(1)在创建表的时候,直接在字段后面加primary key

create table user1(id int primary key, name varchar(30));

(2)在创建表的最后,指定某列或某几列为主键索引

create table user2(id int, name varchar(30), primary key(id));

(3)创建表后在加主键

create table user3(id int, name varchar(30));
-- 创建表以后再添加主键
alter table user3 add primary key(id);
主键索引的特点:

  • 一个表中,最多有一个主键索引,当然可以使用复合主键
  • 主键索引的效率高(不能重复)
  • 创建主键索引的列,它的值不能为null,且不能重复
  • 主键索引的列基本上是int

2.唯一索引的创建

(1)在表定义的时候,后面跟unique

create table user4(id int primary key, name varchar(30) unique);

(2)创建表的时候,在表后面指定某列或几列

create table user5(id int primary key, name varchar(30), unique(name));

(3)创建表后进行添加

create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);

唯一索引的特点:

  • 一个表中,可以有多个唯一索引
  • 查询效率高
  • 如果在某一列建立唯一索引,必须保证这列不能有重复数据
  • 如果一个唯一索引上指定not null,等价于主键索引

3.普通索引的创建

(1)在表的定义最后

create table user8(id int primary key,
name varchar(20),
email varchar(30),
index(name) --在表的定义最后,指定某列为索引
);

(2)创建完后指定某列为普通索引

create table user9(id int primary key, name varchar(20), email
varchar(30));
alter table user9 add index(name);

(3)创建一个索引为 idx_name 的索引

create table user10(id int primary key, name varchar(20), email
varchar(30));
-- 创建一个索引名为 idx_name 的索引
create index idx_name on user10(name);
普通索引的特点:

  • 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
  • 如果某列需要创建索引,但是该列有重复的值,就可以考虑普通索引

4.全文索引的创建

当对文段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文检索机制。

mysql> CREATE TABLE articles (

NSIGNED AUTO_INCREMENT NOT NULL -> id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,

-> title VARCHAR(200),

-> body TEXT,

-> FULLTEXT (title,body)

-> )engine=MyISAM;

Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO articles (title,body) VALUES

-> ('MySQL Tutorial','DBMS stands for DataBase ...'),

-> ('How To Use MySQL Well','After you went through a ...'),

-> ('Optimizing MySQL','In this tutorial we will show ...'),

-> ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),

-> ('MySQL vs. YourSQL','In the following database comparison ...'),

-> ('MySQL Security','When configured properly, MySQL ...');

Query OK, 6 rows affected (0.04 sec)

Records: 6 Duplicates: 0 Warnings: 0

mysql> select * from articles where body like '%SQL%';

+----+-------------------+-------------------------------------+

| id | title | body |

+----+-------------------+-------------------------------------+

| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... |

| 6 | MySQL Security | When configured properly, MySQL ... |

+----+-------------------+-------------------------------------+

2 rows in set (0.01 sec)

5.explain

explain用来查看是否用到索引

mysql> explain select * from articles where body like '%SQL%' \G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: articles

partitions: NULL

type: ALL

possible_keys: NULL

key: NULL <= key 为null 表示没有用到

key_len: NULL

ref: NULL

rows: 6

filtered: 16.67

Extra: Using where

1 row in set, 1 warning (0.01 sec)

使用全文索引

mysql> explain select * from articles where match(title,body) against ('SQL')\G

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: articles

partitions: NULL

type: fulltext

possible_keys: title

key: title

key_len: 0

ref: const

rows: 1

filtered: 100.00

Extra: Using where

1 row in set, 1 warning (0.00 sec)

6.查询索引

(1)show keys from 表名

mysql> show keys from articles\G

*************************** 1. row ***************************

Table: articles

Non_unique: 0

Key_name: PRIMARY

Seq_in_index: 1

Column_name: id

Collation: A

Cardinality: 6

Sub_part: NULL

Packed: NULL

Null:

Index_type: BTREE

Comment:

Index_comment:

Visible: YES

Expression: NULL

*************************** 2. row ***************************

Table: articles

Non_unique: 1

Key_name: title

Seq_in_index: 1

Column_name: title

Collation: NULL

Cardinality: NULL

Sub_part: NULL

Packed: NULL

Null: YES

Index_type: FULLTEXT

Comment:

Index_comment:

Visible: YES

Expression: NULL

*************************** 3. row ***************************

Table: articles

Non_unique: 1

Key_name: title

Seq_in_index: 2

Column_name: body

Collation: NULL

Cardinality: NULL

Sub_part: NULL

Packed: NULL

Null: YES

Index_type: FULLTEXT

Comment:

Index_comment:

Visible: YES

Expression: NULL

3 rows in set (0.00 sec)

为什么2和3都是title呢?

这是因为在匹配的过程中,会先进行最左匹配

(2)show index from 表名;

(3)desc 表名

7.删除索引

(1)删除主键索引

mysql> alter table articles drop primary key;

Query OK, 6 rows affected (0.03 sec)

Records: 6 Duplicates: 0 Warnings: 0

(2)删除其他索引

mysql> alter table articles drop index title;

Query OK, 6 rows affected (0.04 sec)

Records: 6 Duplicates: 0 Warnings: 0

mysql> show index from articles;

Empty set (0.01 sec)

(3)drop index 索引名 on 表名

mysql> show index from articles\G

*************************** 1. row ***************************

Table: articles

Non_unique: 1

Key_name: idx

Seq_in_index: 1

Column_name: title

Collation: A

Cardinality: NULL

Sub_part: NULL

Packed: NULL

Null: YES

Index_type: BTREE

Comment:

Index_comment:

Visible: YES

Expression: NULL

1 row in set (0.00 sec)

mysql> drop index idx on articles;

Query OK, 6 rows affected (0.04 sec)

Records: 6 Duplicates: 0 Warnings: 0

索引创建原则

  • 比较频繁作为查询条件的字段应该创建索引
  • 唯一性太差的不适合创建
  • 更新非常频繁的字段也不适合
  • 不会出现在where子句里的字段不应该创建索引
相关推荐
爱读书的小胖1 小时前
LeetCode 55 跳跃游戏 C 语言解题分析
c语言·数据结构·算法·leetcode·1024程序员节
Sakura_j_h_1 小时前
Three.js 单文件离线机械臂工作台:爆炸图、透明外壳与剖面
前端·机械臂·webgl·three.js·1024程序员节·esbuild
花间相见1 小时前
【计算基础|MySQL14】MySQL 锁(上):种类与加锁规则
1024程序员节
花间相见1 小时前
【计算基础|MySQL15】MySQL 锁(下):锁全表与死锁
1024程序员节
sdm0704271 小时前
back_insert_iterator-尾插迭代器类
1024程序员节
DisonTangor2 小时前
MOSS-Image-Base 实战:从文生图到 JSON 布局精准控图
人工智能·ai作画·开源·aigc·1024程序员节
TechVoyager_82462 小时前
十六个调试引脚的两副面孔:IT66322 切换芯片的引脚复用与功耗分档
1024程序员节·切换芯片·引脚复用·功耗分档
lunar_coder2 小时前
【C++】如何解决智能指针的析构函数,如何实现shared_ptr的引用计数以及解决shared_ptr的循环引用
c++·1024程序员节
瑞克-桑切斯2 小时前
脚本陷入自循环的解决方法
1024程序员节