索引介绍
作用: 针对精确查询提升查询速度。
没有索引: 一般查询都是遵循逐行遍历,若数据量大的情况下,就会比较费时,查询效率低;
有索引: 类似与二分法查询,可以迅速缩小范围。
磁盘IO & 预读
每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分:
- 1、寻道时间: 指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在
5ms
以下; - 2、旋转延迟: 就是我们经常听说的磁盘转速,比如一个磁盘
7200
转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms
; - 3、传输时间:指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。
一次磁盘IO的时间约等于
5+4.17 = 9ms
左右,一台500 -MIPS(Million Instructions Per Second)
的机器每秒可以执行5亿
条指令,换句话说执行一次IO的时间可以执行约450万
条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难.
考虑到磁盘IO成本是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。
Btree索引数据结构
B-Tree 索引是 MySQL 数据库中使用最为频繁的索引类型,除了 Archive 存储引擎之外的其他所有的存储引擎都支持 B-Tree 索引。
不仅仅在 MySQL 中是如此,实际上在其他的很多数据库管理系统中B-Tree 索引也同样是作为最主要的索引类型,这主要是因为 B-Tree 索引的存储结构在数据库的数据检 索中有非常优异的表现。
InnoDB引擎的btree索引查找过程
浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示) 和 指针(黄色所示),如磁盘块1包含数据项17
和35
,包含指针P1
、P2
、P3
,P1
表示小于17
的磁盘块,P2
表示在17
和35
之间的磁盘块,P3
表示大于35
的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点(根节点和树枝节点)不存储真实的数据,只存储指引搜索方向的数据项,如17
、35
并不真实存在于数据表中。
查找过程
- 如果要查找数据项29,那么首先会把
磁盘块1
由磁盘加载到内存,此时发生一次IO; - 在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计;
- 通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO;
- 29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO;
- 在内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的;如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
MyIasm引擎的btree索引查找过程
过程类似InnoDB引擎的btree索引查找过程,只不过叶子节点存储的是行指针,需要再根据行指针定位对应的行,而InnoDB叶子节点存储的是完整的数据行。
hash索引
Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。
缺点
- 1、Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询。由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样,就变成全表扫描。
- 2、Hash 索引无法被用来避免数据的排序操作。
由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且Hash值的大小关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算; - 3、Hash 索引不能利用部分索引键查询。
对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用。 - 4、Hash 索引在任何时候都不能避免表扫描。
前面已经知道,Hash 索引是将索引键通过 Hash 运算之后,将 Hash运算结果的 Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。 - 5、Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高。
索引种类
-
- 辅助索引
index
:加速查找;
- 辅助索引
-
唯一索引: 运用innoDB记得要创建主键;
主键索引:
primary key
:加速查找+约束(不为空且唯一)
唯一索引:unique
:加速查找+约束 (唯一)
-
联合索引
primary key(id,name)
:联合主键索引
-unique(id,name)
:联合唯一索引
-index(id,name)
:联合普通索引
-
- 全文索引
fulltext
: 用于搜索很长一篇文章的时候,效果最好。
- 全文索引
-
- 空间索引
spatial
:了解就好,几乎不用
- 空间索引
创建& 删除索引
# 方法一:创建表时;
create table t1(
id int,
name char,
age int,
sex enum('male','female'),
unique key uni_id(id),
index ix_name(name) # index 索引名+字段名
);
# 方法二:CREATE在已存在的表上创建索引;
create index ix_age on t1(age);
# 方法三:ALTER TABLE在已存在的表上创建索引;
alter table t1 add index ix_sex(sex);
# 删除索引:DROP INDEX 索引名 ON 表名字;
alter table s1 drop index ix_sex;
drop index 索引名称 on 表名(字段名)
# 查看表结构
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=utf8 |
测试索引
# 1.准备表
create table s1(
id int,
name varchar(20),
age int,
email varchar(50)
);
# 2.创建存储过程
# 声明存储过程的结束符号为$$
delimiter $$
create procedure auto_insert()
BEGIN
declare i int default 1;
while (i<3000000) do
insert into s1 values(i,'lynn',10,concat('lynn',i,'@oldboy'));
set i = i+1;
end while;
END$$
delimiter ;
# 重新声明分号为结束符号
# 3.查看存储过程
show create procedure auto_insert\G
# 4.调用存储过程,可能需十来分钟!
call auto_insert();
# 5. 查看结果
select count(id) from s1;
-------+
| count(id) |
+-----------+
| 2999999 |
+-----------+
1 row in set (1.68 sec)
# 对比有无索引的查询速度======================================
# 1.没有索引
select * from s1 where id=3333333;
Empty set (2.16 sec)
# 2.有索引
# 给id字段增加索引
alter table s1 modify id int primary key;
Query OK, 0 rows affected (11.60 sec)
# 根据索引方式查找
select * from s1 where id=3333333;
Empty set (0.00 sec)
# 总结
对比可以看出,没有索引花了2.16秒,但是给id设置索引后,查询速度为0,因为直接排除了错误,最大的id为3000000;
索引选择
字段明确优先,若条件不明确,条件中出现这些符号或关键字:>、>=、<、<=、!= 、between...and...、like、大于号、小于号
,那么索引作用就不明显.
# 文件扫描
mysql> select count(id) from s1 where id > 1000;
+-----------+
| count(id) |
+-----------+
| 2998999 |
+-----------+
1 row in set (2.36 sec)
# 查询大范围扫描,查询速度没有提升, 查询范围小,速度就很快
mysql> select count(*) from s1 where id > 1000;
+----------+
| count(*) |
+----------+
| 2998999 |
+----------+
1 row in set (2.30 sec)
# 范围准确,瞬间查到===========================
mysql> select count(*) from s1 where id = 1000;
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
- 字段区分度高的优先,表示字段不重复的比例.
mysql> select count(*) from s1 where gender='malea';
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)
# 所有行都符合条件, 区分度为0, 相当于全文扫描,但是速度还是比没有索引的快
mysql> select count(*) from s1 where gender='male';
+----------+
| count(*) |
+----------+
| 2999999 |
+----------+
1 row in set (1.10 sec)
# 当符合条件的项不存在btree中时, 那么瞬间查询出结果
mysql> select count(*) from s1 where gender='male1';
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)
设计索引的原则
- 1、搜索的索引列,不一定是所要选择的列。换句话说,最适合索引的列是出现在 WHERE
子句中的列,或连接子句中指定的列,而不是出现在 SELECT 关键字后的选择列表中的列; - 合理的建立索引能够加速数据读取效率,不合理的建立索引反而会拖慢数据库的响应速度。
- 2、使用唯一索引,即区分度大的列;
- 3、使用短索引。如果对字符串列进行索引,应该指定一个前缀长度,只要有可能就应该这样做。例如,如果有一个 CHAR(200)列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。对前10个或20个字符进行索引能够节省大量索引空间,也可能会使查询更快。较小的索引涉及的磁盘 IO较少,较短的值比较起来更快。更为重要的是,对于较短的键值,索引高速缓存中的块能容纳更多的键值;
- 4、不要过度索引。不要以为索引“越多越好”,什么东西都用索引是错误的。每个额
外的索引都要占用额外的磁盘空间,并降低写操作的性能。在修改表的内容时,索引必须进
行更新,有时可能需要重构,因此,索引越多,所花的时间越长。 - 5、当你的程序和数据库结构/SQL语句已经优化到无法优化的程度,而程序瓶颈并不能顺利解决,那就是应该考虑使用诸如memcached这样的分布式缓存系统的时候了。
- 6、习惯和强迫自己用EXPLAIN来分析你SQL语句的性能。
- 7、对于 InnoDB 存储引擎的表,记录默认会按照一定的顺序保存,如果有明确定义的主
键,则按照主键顺序保存。如果没有主键,但是有唯一索引,那么就是按照唯一索引的顺序
保存。如果既没有主键又没有唯一索引,那么表中会自动生成一个内部列,按照这个列的顺
序保存。按照主键或者内部列进行的访问是最快的,所以 InnoDB 表尽量自己指定主键,当
表中同时有几个列都是唯一的,都可以作为主键的时候,要选择最常作为访问条件的列作为
主键,提高查询的效率。另外,还需要注意,InnoDB 表的普通索引都会保存主键的键值,
所以主键要尽可能选择较短的数据类型,可以有效地减少索引的磁盘占用,提高索引的缓存
效果。