09 索引
索引介绍
什么是索引
索引是存储引擎中一种数据结构,或者说数据的组织方式,又称之为键key,是存储引擎用于快速找到
记录的一种数据结构。
为数据建立索引就好比是为书建目录,或者说是为字典创建音序表,如果要查某个字,如果不使用音序
表,则需要从几百页中逐页去查。
为何要用索引
一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环
境中,我们遇到最多的、也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句的优化显然
是重中之重。说起加速查询,就不得不提到索引了。
索引优化应该是对查询性能优化最有效的手段了。索引能够轻易将查询性能提高好几个数量级。
如何正确看待索引
索引是应用程序设计和开发的一个重要方面。若索引太多,应用程序的性能可能会受到影响。而索引太
少,对查询性能又会产生影响,要找到一个平衡点,这对应用程序的性能至关重要。
一些开发人员总是在事后才想起添加索引----我一直认为,这源于一种错误的开发模式。如果知道数据
的使用,从一开始就应该在需要处添加索引。开发人员往往对数据库的使用停留在应用的层面,比如编
写SQL语句、存储过程之类,他们甚至可能不知道索引的存在,或认为事后让相关DBA加上即可。DBA往
往不够了解业务的数据流,而添加索引需要通过监控大量的SQL语句进而从中找到问题,这个步骤所需
的时间肯定是远大于初始添加索引所需的时间,并且可能会遗漏一部分的索引。当然索引也并不是越多
越好,我曾经遇到过这样一个问题:某台MySQL服务器iostat显示磁盘使用率一直处于100%,经过分
析后发现是由于开发人员添加了太多的索引,在删除一些不必要的索引之后,磁盘使用率马上下降为
20%。可见索引的添加也是非常有技术含量的。
索引的分类
#===========B+树索引(等值查询与范围查询都快)
二叉树->平衡二叉树->B树->B+树
#===========HASH索引(等值查询快,范围查询慢)
将数据打散再去查询
#===========FULLTEXT:全文索引 (只可以用在MyISAM引擎)
通过关键字的匹配来进行查询,类似于like的模糊匹配
like + %在文本比较少时是合适的
但是对于大量的文本数据检索会非常的慢
全文索引在大量的数据面前能比like快得多,但是准确度很低
百度在搜索文章的时候使用的就是全文索引,但更有可能是ES
不同的存储引擎支持的索引类型也不一样
-
innoDB存储引擎
支持事务,支持行级别锁定,支持 B-tree(默认)、Full-text 等索引,不支持 Hash 索引;
-
myISAM存储引擎
不支持事务,支持表级别锁定,支持B-tree、Full-text等索引,不支持hash索引
-
Memory存储引擎
不支持事务,支持表级别的锁定,支持b-tree、hash等索引,不支持Full-text索引
innoDB的索引结构发展
二叉树
# 特点:
任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于 当前节点的键值。
顶端的节点我们成为根节点,没有子节点的我们成为叶节点
平衡二叉树
如果二叉树变得不平衡了,就是高度太高了,同样会带来查询效率不稳定的问题,所以为了保证一致平衡,就需要用到平衡二叉树了
平衡二叉树又称AVL树,在满足二叉树的查找基础上,要求每个节点的左右子树的高度不能超过1。
B树
平衡二叉树的问题
1、首先,因为内存的易失性。一般情况下,我们都会选择将user表中的数据和索引存储在磁盘这种外围
设备中。但是和内存相比,从磁盘中读取数据的速度会慢上百倍千倍甚至万倍,所以,我们应当尽量减
少从磁盘中读取数据的次数。
2、另外,从磁盘中读取数据时,都是按照磁盘块来读取的,并不是一条一条的读。 如果我们能把尽量
多的数据放进磁盘块中,那一次磁盘读取操作就会读取更多数据,那我们查找数据的时间也会大幅度降
低。
3、所以,如果我们单纯用平衡二叉树这种数据结构作为索引的数据结构,即每个磁盘块只放一个节点,
每个节点中只存放一组键值对,此时如果数据量过大,二叉树的节点则会非常多,树的高度也随即变
高,我们查找数据的也会进行很多次磁盘IO,查找数据的效率也会变得极低!
注意:
1、图中的p节点为指向子节点的指针,二叉查找树和平衡二叉树其实也有
2、途中的每个节点里面放了很多组键值对,一个节点称为一页,一页即一个磁盘块,在mysql中数据读取的基本单位都是页,一次io读取一个页的数据,所以我们这里叫做页更符合mysql中索引的底层数据结构。
B+树
# B+树与B树的不同
1、B+树非叶子节点non-leaf node上是不存储数据的,仅存储键,而B树的非叶子节点中不仅存储键,也会存储数据。B+树之所以这么做的意义在于:树一个节点就是一个页,而数据库中页的大小是固定的,innodb存储引擎默认一页为16KB,所以在页大小固定的前提下,能往一个页中放入更多的节点,相应的树的阶数(节点的子节点树)就会更大,那么树的高度必然更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。
2、B+树的阶数是等于键的数量的,例如上图,我们的B+树中每个节点可以存储3个键,3层B+树存可以
存储 3*3*3 =9个数据。所以如果我们的B+树一个节点可以存储1000个键值,那么3层B+树可以存储
1000×1000×1000=10亿个数据。而一般根节点是常驻内存的,所以一般我们查找10亿数据,只需要2次
磁盘IO,真是屌炸天的设计。
3、因为B+树索引的所有数据均存储在叶子节点leaf node,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的。而且B+树中各个页之间也是通过双向链表连接的,叶子节点中的数据是通过单向链表连接的。其实上面的B树我们也可以对各个节点加上链表。其实这些不是它们之前的区别,是因为在mysql的innodb存储引擎中,索引就是这样存储的。也就是说上图中的B+树索引就是innodb中B+树索引真正的实现方式,准确的说应该是聚集索引(聚集索引和非聚集索引下面会讲到)。
通过上图可以看到,在innodb中,我们通过数据页之间通过双向链表连接以及叶子节点中数据之间通过单向链表连接的方式可以找到表中所有的数据。
聚集索引与非聚集索引
介绍
- 聚集索引(聚簇索引、主键索引、一张表必须有且只有一个):以innodb作为存储引擎的表,表中的数据都会有一个主键,即使你不能创建主键,系统也会帮你创建一个隐式的主键,这是因为innodb是把数据存放在B+树中的,而B+树的键用的就是主键,在B+树的叶子节点中,存储了表中的所有数据,这种以主键作为B+树索引的键值而构建的B+树索引,我们成为聚集索引。
- 非聚集索引(又称非聚簇索引、辅助索引,一张表可以创建多个辅助索引):以主键以外的列值作为 键值构建的B+树索引,我们称之为非聚集索引。非聚集索引与聚集索引的区别在于非聚集索引的叶子节 点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进 行查找,这个再根据聚集索引查找数据的过程,我们称为回表。 明白了聚集索引和非聚集索引的定义,我们应该明白这样一句话:数据即索引,索引即数据。
这里辅助索引存储的格式为键值与主键,否则会占用大量的物理内存,而且数据会重复存。
覆盖索引与回表操作
- 命中了辅助索引,然后要找的字段值不存在与辅助索引的叶子节点上,则需要根据拿到的主键值再去聚 集索引中查询一遍,然后再聚集索引的叶子节点找到你想要的内容,这就叫回表操作 。
- 命中了某棵索引树,然后在其叶子节点就找到了你想要的值,即不需要回表操作,就是覆盖了索引 。
索引管理
常见的索引分类
#===========B+树索引(innodb存储引擎默认)
聚集索引:即主键索引,PRIMARY KEY
用途:
1、加速查找
2、约束(不为空、不能重复)
唯一索引:UNIQUE
用途:
1、加速查找
2、约束(不能重复)
普通索引INDEX:
用途:
1、加速查找
联合索引:
PRIMARY KEY(id,name):联合主键索引
UNIQUE(id,name):联合唯一索引
INDEX(id,name):联合普通索引
#===========HASH索引(查询单条快,范围查询慢)
将数据打散再去查询
Inodb和Myisam都不支持,设置完还是Btree
memery存储引擎支持
#===========FULLTEXT:全文索引 (只可以用在MyISAM引擎)
通过关键字的匹配来进行查询,类似于like的模糊匹配
like + %在文本比较少时是合适的
但是对于大量的文本数据检索会非常的慢
全文索引在大量的数据面前能比like快得多,但是准确度很低
百度在搜索文章的时候使用的就是全文索引,但更有可能是ES
#===========RTREE:R树索引
RTREE在mysql很少使用,仅支持geometry数据类型
geometry数据类型一般填写经纬度那样的数据
支持该类型的存储引擎只有MyISAM、BDb、InnoDb、NDb、Archive几种。
RTREE范围查找很强,但Btree也不弱.
#不同的存储引擎支持的索引类型也不一样
InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引;
NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引;
Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引;
语法
#方法一:创建表时
CREATE TABLE 表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY
[索引名] (字段名[(长度)] [ASC |DESC])
);
#方法二:CREATE在已存在的表上创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名
ON 表名 (字段名[(长度)] [ASC |DESC]) ;
#方法三:ALTER TABLE在已存在的表上创建索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX
索引名 (字段名[(长度)] [ASC |DESC]) ;
#删除索引:
DROP INDEX 索引名 ON 表名字;
alter table country drop index 索引名字;
#查看索引
方法一:
mysql> desc t1;
+-----+
| Key |
+-----+
| PRI | 主键索引
| MUL | 普通索引
| UNI | 唯一键索引
+-----+
方法二:
mysql> show index from t1;
创建索引的方法
# 建表的时候就创建
create table t1(
id int,
name char,
age int,
sex enum('male','female'),
unique key uni_id(id),
index ix_name(name) #index没有key
);
#方式二
create index ix_age on t1(age);
#方式三
alter table t1 add index ix_sex(sex);
#查看
mysql> show create table t1;
| t1 | CREATE TABLE `t1` (
`id` int(11) DEFAULT NULL,
`name` char(1) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` enum('male','female') DEFAULT NULL,
UNIQUE KEY `uni_id` (`id`),
KEY `ix_name` (`name`),
KEY `ix_age` (`age`),
KEY `ix_sex` (`sex`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
索引创建的示范
# 1)主键索引(聚集索引)
#创建主键索引
mysql> alter table student add primary key pri_id(id);
mysql> create table student(id int not null, primary key(id));
mysql> create table student(id int not null primary key auto_increment comment '学号');
#提示:
database可以写 schema
index可以写 key
# 2)唯一键索引
#创建唯一建索引
mysql> alter table country add unique key uni_name(name);
mysql> create table student(id int unique key comment '学号');
mysql> create unique key index index_name on table_name(id);
# 3)普通索引(辅助索引)
#普通索引的创建
mysql> alter table student add index idx_gender(gender);
CREATE INDEX index_name ON table_name (column_list);
# 4)创建前缀索引
按照该列数据的前n个字母创建索引
mysql> alter table student add index idx_name(name(4));
# 5)全文索引
#针对content做了全文索引:
CREATE TABLE t1 (
id int NOT NULL AUTO_INCREMENT,
title char(255) NOT NULL,
content text,
PRIMARY KEY (id),
FULLTEXT (content));
查找时:
select * from table where match(content) against('想查询的字符串');