本期我们好好唠唠索引
目录
[3.1 MySQL与磁盘交互的基本单位page](#3.1 MySQL与磁盘交互的基本单位page)
[3.2 MySQL中的数据交互过程](#3.2 MySQL中的数据交互过程)
[3.3 索引建立的过程](#3.3 索引建立的过程)
[3.3.1 page的存储形式](#3.3.1 page的存储形式)
[3.3.2 B+树的形成](#3.3.2 B+树的形成)
[3.4 为什么不用其他数据结构来建立索引](#3.4 为什么不用其他数据结构来建立索引)
[五、 辅助(普通)索引](#五、 辅助(普通)索引)
[6.1 查看表中存在的索引](#6.1 查看表中存在的索引)
[6.2 创建索引](#6.2 创建索引)
[6.2.1 创建主键索引](#6.2.1 创建主键索引)
[6.2.2 创建唯一索引](#6.2.2 创建唯一索引)
[6.2.3 创建普通索引](#6.2.3 创建普通索引)
[6.2.4 创建复合索引](#6.2.4 创建复合索引)
[6.2.5 创建全文索引](#6.2.5 创建全文索引)
[6.2.6 创建索引的原则](#6.2.6 创建索引的原则)
[6.3 删除索引](#6.3 删除索引)
[6.3.1 删除主键索引](#6.3.1 删除主键索引)
[6.3.2 删除其他类型的索引](#6.3.2 删除其他类型的索引)
一、索引的概念
索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。
常见索引分有:
主键索引(primary key)
唯一索引(unique)
普通索引(index)
全文索引(fulltext)--解决中子文索引问题。
二、索引的重要性
索引可以提高数据库的性能,是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度。
光说概念太抽象了,下面我们来直观的感受一下索引:
先整一个海量表,在查询的时候看看没有索引有什么问题:
sql
drop database if exists `bit_index`;
create database if not exists `bit_index` default character set utf8;
use `bit_index`;
-- 构建一个8000000条记录的数据
-- 构建的海量表数据需要有差异性,所以使用存储过程来创建, 拷贝下面代码就可以了,暂时不用理解
-- 产生随机字符串
delimiter $$
create function rand_string(n INT)
returns varchar(255)
begin
declare chars_str varchar(100) default
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
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 ;
-- 产生随机数字
delimiter $$
create function rand_num( )
returns int(5)
begin
declare i int default 0;
set i = floor(10+rand()*500);
return i;
end $$
delimiter ;
-- 创建存储过程,向雇员表添加海量数据
delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i + 1;
insert into EMP values ((start+i)
,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
until i = max_num
end repeat;
commit;
end $$
delimiter ;
-- 雇员表
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 '部门编号'
);
-- 执行存储过程,添加8000000条记录
call insert_emp(100001, 8000000);
上面的代码数据量较大,需要向数据库中插入800w条数据,实操时需要耐心等待一下
插入过程结束后,我们稍微看一下该表中的数据:
现在有一个要求:查询员工编号为998877的员工
我们直接使用普通的select查询,可以发现单单查这一条记录就耗费了4.92s。在实际项目中,如果放在公网中,假如同时有 1000个人并发查询,那很可能就死机。
下面演示一遍将empno构建索引后再来查询:
我们可以看到构建索引后,使用select查询相同的数据耗费的时间缩短了好几个数量级,由此可见索引在实际运用中是多么的重要
三、对于索引的理解
3.1 MySQL与磁盘交互的基本单位page
那索引底层原理到底是个啥,下面来详细跟大家来说道说道:
说到索引之前,我们要弄清楚MySQL的底层数据存取原理。我们都知道MySQL下存取数据依靠的存储引擎,下面就以InnoDB存储引擎讲解:
MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO(Input/Output,也就是输入和输出。它是指程序和运行时数据在内存中与外设之间进行数据交换的过程)场景,所以,为了提高基本的IO效率, MySQL进行IO的基本单位是 16KB:
即, MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在MySQL这里叫做page
3.2 MySQL中的数据交互过程
MySQL中的数据文件,是以page为单位保存在磁盘当中的。
MySQL的CURD操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。
而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。 所以在特定时间内,数据一定是磁盘中有,内存中也有。
后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。
为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称 为Buffer Pool的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。
下面是MySQL进行数据交互的模拟图:
为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数
3.3 索引建立的过程
为了更好的演示,我们先建立一个表:
sql
create table if not exists user (
id int primary key,
age int not null,
name varchar(16) not null
);
在这里先声明一点:MySQL会为表设置的主键自动建立索引
再向该表中插入一下数据:
sql
insert into user values(3, 18, '张三'),(4, 16, '李四'),(2, 26, '王五'),(5, 36, '赵六'),(1, 56, '钱七');
插入数据时我们可以看到id这一列的数据时无序的,但是由于系统自动会根据主键来建立索引,会将插入的数据按id列排序:
为什么被系统设置的索引的列会自动排序呢?
这是为了提高按主键查找时的效率
3.3.1 page的存储形式
下面我们来问一下大家:select这表中的数据,先要将所有要打印的数据从磁盘调到page上进行存储之后再输出到屏幕上,那这些数据在page中是如何进行存储的呢?
来看下面的示意图:
我们可以看到page是一个双向链表的存储形式
不同的 Page ,在MySQL中,都是16KB ,使用 prev 和 next 构成双向链表
因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的
通过上面的分析,我们知道,上面page的中,只有一个功能,就是在查询某条数据的时候直接将其内部的数据加载到内存中,以减少硬盘IO次数,从而提高性能。但是,我们也可以看到,现在的page的数据内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。 如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起 来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了:
为了解决这个问题,我们是不是可以像图书一样,向这些page中插入目录来提升查找效率?
当然可以:
在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的Page来保存新的数据,然后通过指针的方式,将所有的Page组织起来:
需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page上面,这里仅仅做演示。 这样,我们就可以通过多个Page遍历,Page内部通过目录来快速定位数据。
3.3.2 B+树的形成
可是,貌似上面有目录的形式也有效率问题,在Page之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的IO,需要将每一个Page加载到内存,进行线性检测。这样就显得我们之前的Page内部的目录,有点杯水车薪了。
那么如何解决呢?其实就是按我们之前的思路,给Page也带上目录。 使用一个page充当目录项来指向某一个page,而这个目录项存放的就是将要指向的page中存放的最小数据的键值和指向子page的指针。 和page内目录不同的地方在于,这种目录管理的级别是page,而page内目录管理的级别是数据:
那如果数据量更庞大呢?在上面继续加目录就可以了:
但是在这里要注意了:作为路径的中间节点就不会有前后指针来指向相邻的节点了,但是最底层的叶子节点还是有前后指针的,这样可以方便范围查找
这就是传说中的B+树啊!没错,至此,我们已经给我们的表user构建完了主键索引。 随便找一个id=?我们发现,现在查找的Page数一定减少了,也就意味着IO次数减少了,那么效率也就提高了。
3.4 为什么不用其他数据结构来建立索引
链表?线性遍历,效率太低
二叉搜索树?退化问题,可能退化成为线性结构
AVL &&红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+,意味着树整体过高,大家都是自顶向下找,层高越高,意味着系统与硬盘更多的IO交互
Hash?官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持,Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在面对范围查找就明显不行。
B树?B+树节点不存储data,这样一个节点就可以存储更多的key,可以使得树更矮,所以IO操作次数更少,而且B+树叶子节点相连,更便于进行范围查找
综上所述,索引的本质就是B+树结构!
四、聚簇索引和非聚簇索引
MyISAM引擎同样使用B+树作为索引结果,但叶节点的data域存放的是数据记录的地址,并不存储数据。下图为 MyISAM 表的主索引, Col1 为主键。
MyISAM这种数据与索引数据分离的索引方案,叫做非聚簇索引
而上面介绍的InnoDB这种数据与索引数据在一起索引方案,叫做聚簇索引
我们用实例来感受一下非聚簇索引与聚簇索引区别:
先建立一个存储引擎为innodb的表:
建完后查看一下该数据库下的目录:
可以看到t1表只有两个文件.frm是表结构文件,.ibd存储的是索引建立的B+树结构和数据
再建立一个存储引擎为MyISAM的表:
可以看到t2表有三个文件.frm是表结构文件,.MYD存储的是用户数据,.MYI索引建立的B+树结构
五、 辅助(普通)索引
当然, MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引。
对于 MyISAM,建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。
对于InnoDB,建立辅助(普通)索引中叶子节点并没有数据,而只有对应记录的key值。
为什么不给叶子节点也附上数据呢?原因就是InnoDB在主键索引中已经存有数据了,如果再给叶子节点也附上数据就太浪费空间了。
所以在InnoDB存储引擎下,通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询。
六、索引操作
6.1 查看表中存在的索引
sql
show index from 表名;
用上述语句可以查询表中存在的索引
例如:
6.2 创建索引
6.2.1 创建主键索引
创建主键索引我们不需要另外操作,只要表中有主键(建表时声明主键,追加主键等一系列操作,系统都会帮我们建立索引),系统就会自动对主键这一列创建索引
6.2.2 创建唯一索引
唯一索引和主键索引一样,创建唯一索引我们不需要另外操作,只要表中有唯一键(建表时声明唯一键,追加唯一键等一系列操作),系统就会自动对唯一键这一列创建索引
但是在这里说明一下唯一索引和普通索引一样,没有什么区别
例如,我们现在向t1表中追加一个唯一键:
6.2.3 创建普通索引
第一种方式:在表的定义最后,使用index关键字指定某列为索引
sql
create table user(id int primary key,
name varchar(20),
email varchar(30),
index(name)
);
第二种方式: 创建完表以后使用index关键字指定某列为普通索引
sql
create table user(id int primary key, name varchar(20), email varchar(30));
alter table user add index(name);
第三种方式:使用index关键字创建一个索引名为idx_name(索引名可自定义,这里只是举例)的索引
sql
create table user(id int primary key, name varchar(20), email varchar(30));
create index idx_name on user(name);
6.2.4 创建复合索引
我们创建索引不仅仅只能用一列数据来作为我们的索引B+树的key值,我们可以使用多列组合来创建一个索引,这种索引被称为复合索引
例如,我们向下面这张表中创建一个复合索引:
咦?这里怎么显示有三个索引?这表中不应该就一个主键索引和一个复合索引吗?
不用担心,2.row和3.row实际是同一个索引,索引名都为name
那这个复合索引有什么用呢?
在我们查name列的数据时,如果我们只为了找到相对应数据的email信息,这时由于name和email构成复合索引,其索引在B+树中找到的key值里面存有name数据对应的email信息,就不需要再回到主键索引中做回表查询了,直接返回key值中的email数据即可,这个过程被称为索引覆盖,可以提高查询效率。
所以复合索引一般用于多个列联系比较密切的场景中,比如:拿名字找电话号码、拿电话号码找人名。
但是要注意了,复合索引有最左匹配原则,即查询数据时优先匹配创建复合索引时最左边的列数据
例如在上面这个例子中,我们可以拿单独name列中的数据来找相对应的email数据,但是不能单独拿email列中的数据来找对应的name数据
6.2.5 创建全文索引
当对文章字段或有大量文字的字段进行查询是否包含关键语句时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx的中文版(coreseek)。
创建全文索引时我们会用到关键字FULLTEXT
下面我们来举例说明:
先来创建一个表:
sql
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL 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 ...');
再来看看该表中有什么样的索引:
可以看到有一个主键索引和一个全文索引title
现在我们来使用select查一下有没有database数据:
用上面的语句虽然查询出数据,但是没有使用到全文索引
怎么知道查询语句有没有用索引呢,我们可以使用explain工具来语句执行时的状态;
要想使用全文索引进行查询,需要用到match和against关键字:
我们再来用explain来查看一下该语句的执行状态:
可以看到此语句用到了全文索引
但需要注意的是,全文索引的底层结构并非B+树,而是倒排索引和辅助表,这里就不再过多深入
6.2.6 创建索引的原则
比较频繁作为查询条件的字段应该创建索引
唯一性太差的字段不适合单独创建索引,例如:性别
即使频繁作为查询条件,但更新非常频繁的字段不适合作创建索引
不会出现在where子句中的字段不该创建索引
6.3 删除索引
6.3.1 删除主键索引
sql
alter table 表名 drop primary key;
就是删除表中的主键
6.3.2 删除其他类型的索引
sql
alter table 表名 drop index 索引名;
或者
sql
drop index 索引名 on 表名;
本文知识点较多,如有纰漏还请各位大佬指正~
更多MySQL技能请看:http://t.csdn.cn/W9dQl
博主努力更新中~