MySQL 索引

背景概念

索引可以显著提高查询速度,特别是在处理大量数据时效果更为明显,它类似于书籍的目录,而不必进行全表扫描 。不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO 。常见索引分为:

  • 主键索引(primary key)
  • 唯一索引(unique)
  • 普通索引(index)
  • 全文索引(fulltext)--解决中子文索引问题

数据库文件,本质其实就是保存在硬盘,其均存储在 /var/lib/mysql 路径下。我们可以通过指令ll /var/lib/mysql 来查看此时MySQL下的文件。

当要高频的查询硬盘中数据,就不可避免的要进行频繁的IO,硬盘I/O(Input/Output)是指数据在硬盘和内存之间的传输过程,包括数据的读取和写入,索引的出现就是为了减少IO次数,更加高效的查询。MySQL数据库的性能很大程度上依赖于硬盘的I/O操作。

局部性原理:

  • 时间局部性:如果一个数据被访问,在不久后它很可能再次被访问
  • 空间局部性:如果一个数据被访问,其周围的数据不久后很有可能再次被访问

对操作系统和MySQL来说,其加载数据时会把该数据周围一部分数据都加载进去,因为周围的数据访问的概率很高,后续访问时不必重新去硬盘读取数据,减少了IO次数。
磁盘的一个扇区大小是512 byte,对于操作系统来说,不会以扇区为单位读取,而是以块为单位读取,一般来说基本单位是4 kb。
而 MySQL 作为一款应用软件,可以想象成一种特殊的文件系统,IO单位小意味着单次读取到的数据少,进而导致IO次数多,查询效率低 。它有着更高的IO 场景,所以,为了提高基本的IO 效率, MySQL 进行 IO 的基本单位是 16KB ( 后面统一使用 InnoDB 存储引擎讲解)。这个基本数据单元,在 MySQL 这里叫做 page。
我们可以通过以下指令来查看一个page的大小。

sql 复制代码
show global status like 'innodb_page_size';

page

我们知道MySQL中不止一个page,而是有多个page,此时就要进行组织管理:


如果有 1 千万条数据,一定需要多个 Page 来保存 1 千万条数据,多个 Page 彼此使用双链表链接起
来,而且每个 Page 内部的数据也是基于链表的。那么查找特定一条记录也一定是线性查找,这效率也太低了。
我们知道当我们想要看一本书中的某一内容,我们可以先去翻目录去寻找对应的范围,这样就大大提高了查找的效率,同理,在 一个P age内部,我们也引入了目录。

当有多个 Page时,我们就需要通过多个Page 遍历, Page 内部可以通过目录来快速定位数据。可是,在Page 之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的 IO ,将下一个 Page 加载到内存,进行线性检测。
那么如何解决呢?解决方案,其实就是我们之前的思路,给 Page 也带上目录。

使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值。
和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行。**普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。**其中,每个目录项的构成是:键值+指针。
第一层page内部的 指针指向下层page的第一个条目,此时MySQL读取数据时,先读取上层page,根据每个page的第一个数据,快速锁定要读取的page。
实际上,不只有一层page目录页,可以叠加多层来提高查询的效率。

我们发现这就是一个B+树结构,上层节点存储下层节点的信息,只有最后一层叶子节点存储数据,叶子节点通过链表互相连接。

之所以叶子节点要通过链表连接起来,是因为当一个page内部的数据查询完毕,有可能接下来查询的内容就在下一张page中,此时就不再通过根节点往下查询,而是直接通过链表next指针访问下一张page。

复盘一下:

  • Page分为目录页和数据页。目录页只放各个下级Page的最小键值。
  • 查找的时候,自定向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减少了IO次数。

在InnoDB和MyISAM引擎中,都使用B+Tree作为索引结构,也有部分其它存储引擎会使用hash结构作为索引,比如NDB,但是B+Tree依然是主流。因为hash结构的索引,每次查询数据都要重新计算哈希函数,范围查找能力很差。

聚簇索引

在 InnoDB 中, 用户数据与索引数据放在一起,即索引底层的叶子节点存储数据, 这种索引方案叫做聚簇索引。

例如,我们使用如下代码建表。

sql 复制代码
create table innodb_test( id int primary key, name varchar(10) not null)engine=innodb;

建好表后在数据库中就会出现如下文件


后缀为ibd的文件存储表的索引和数据。

非聚簇索引

MyISAM 引擎同样使用 B+树作为索引结果,索引底层的叶子节点存储指向数据的指针,即索引和数据是分离存储的, 这种索引方案叫做非聚簇索引。 。

例如,我们使用如下代码建表。

sql 复制代码
create table myisam_test( id int primary key, name varchar(10) not null)engine=myisam;

建好表后在数据库中就会出现如下文件

  • sdi:存储表的结构
  • MYD:存储数据
  • MYI:存储索引

索引操作

创建主键索引

  • 第一种方式:在创建表的时候,直接在字段名后指定 primary key
sql 复制代码
create table user1(id int primary key, name varchar(10));
  • 第二种方式:在创建表的最后,指定某列或某几列为主键索引
sql 复制代码
create table user2(id int, name varchar(10), primary key(id));
  • 第三种方式:创建表以后再添加主键
sql 复制代码
create table user3(id int, name varchar(10));
alter table user3 add primary key(id);

由于主键不可重复,所以主键索引的效率高。

创建唯一键索引

  • 第一种方式:在表定义时,在某列后直接指定unique唯一属性
sql 复制代码
create table user4(id int primary key, name varchar(10) unique);
  • 第二种方式:创建表时,在表的后面指定某列或某几列为unique
sql 复制代码
create table user5(id int primary key, name varchar(10), unique(name));
  • 第三种方式:创建表以后再添加唯一键
sql 复制代码
create table user6(id int primary key, name varchar(10));
alter table user6 add unique(name);

创建辅助索引

  • 第一种方式:在表的定义最后,指定某列为索引
sql 复制代码
create table user8(id int primary key, 
name varchar(10),
email varchar(30),
index(name));
  • 第二种方式:创建完表以后指定某列为辅助索引
sql 复制代码
create table user9(id int primary key, 
name varchar(10), 
email varchar(30));
alter table user9 add index(name);

第三种方式: 创建一个索引名为 idx_name 的索引

sql 复制代码
create table user10(id int primary key, 
name varchar(10), email varchar(30));
create index idx_name on user10(name);

我们知道InnoDB采用聚簇索引方案,即其索引的叶子节点存储的是数据的值,那么如果创建多个索引,岂不是每一个索引都要保存一份数据?

其实不是,对于聚簇索引而言,其辅助索引存储的是主键的值:

当通过辅助索引查询时,会通过key找到对应主键,然后再通过主键去主键索引查找正行数据。

查询索引

sql 复制代码
show index/keys from 表名\G

例如,我们使用以下代码来创建表user

sql 复制代码
create table user(id int primary key, 
name varchar(10) unique,
email varchar(30),
index(email));

我们可以看到三行索引介绍。

删除索引

  • 第一种方法:删除主键索引
sql 复制代码
alter table 表名 drop primary key;
  • 第二种方法:其他索引的删除,语法中索引名就是show index from 表名中的 Key_name 字段
sql 复制代码
alter table 表名 drop index 索引名;
  • 第三种方法方法
sql 复制代码
drop index 索引名 on 表名;

创建全文索引

当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。 MySQL 提供全文索引机制,但是有要求, 默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx 的中文版 (coreseek)。
例如,我们创建表articles如下:

sql 复制代码
CREATE TABLE articles (
id int auto_increment primary key,
title varchar(200),
body text,
fulltext (title,body)
)engine=MyISAM;
sql 复制代码
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 ...');

如果我们想要查询有关 database 的数据,使用如下查询方式,虽然查询出数据,但是没有使用到全文索引。

sql 复制代码
select * from articles where body like '%database%';


可以用 explain 工具看一下,是否使用到索引

如果想要使用索引,我们需要使用如下语句:

sql 复制代码
select * from articles where match(title,body) against ('database');
相关推荐
python机器学习建模1 小时前
科研论文必须要了解的25个学术网站
数据库
J.P.August2 小时前
Oracle DataGuard启动与关闭顺序
数据库·oracle
尚雷55802 小时前
Oracle 与 达梦 数据库 对比
数据库·oracle·达梦数据库
小猿姐4 小时前
Ape-DTS:开源 DTS 工具,助力自建 MySQL、PostgreSQL 迁移上云
数据库·mysql·postgresql·开源
百香果果ccc4 小时前
MySQL中的单行函数和聚合函数
数据库·mysql
摸摸陌陌4 小时前
Redis快速入门
数据库·redis·缓存
Elastic 中国社区官方博客4 小时前
Elasticsearch Serverless 中的数据流自动分片
大数据·数据库·elasticsearch·搜索引擎·serverless·时序数据库
Minyy114 小时前
牛客网刷题SQL--高级查询
数据库·sql
ygqygq24 小时前
ElK 8 收集 MySQL 慢查询日志并通过 ElastAlert2 告警至飞书
mysql·elk·飞书
秋意钟4 小时前
MySQL基本架构
数据库·mysql·架构