索引的优缺点
- 优点
- 使用索引可以大大加快数据检索速度(大大减少检索的数据量)
- 通过创建唯一索引,可以保证数据库表中每一行数据的唯一性
- 缺点
- 创建索引和维护索引需要耗费许多时间,当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低SQL的执行效率
- 索引需要使用物理空间存储,会耗费一定空间
大多数情况下索引能提高查询速度,但是如果数据库数据量本身不大的情况下,那么使用索引也不一定能带来很大的提升。
索引的底层数据结构
哈希表 & B+树
哈希表是键值对的集合,可以通过key快速取出对应的value,因此哈希表可以快速检索数据。
哈希缺点
- 哈希冲突
-
B树 & B+树
B(balanced)树又称B-树,全称为多路平衡查找树,B+树是B树的一种变体。
B树和B+树的异同
B树的节点既存储key也存储数据,B+树仅有叶子节点存放key和数据,其他节点只存放key;
- B树的叶子节点是独立的,B+树的叶子节点有一条引用链指向与它相邻的叶子节点;
B树的检索过程相当于对范围内的每个节点的关键字做二分查找,可能还没到达叶子节点检索就结束了。B+树的检索效率就相对更稳定,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
MyISAM的索引
MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。
在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。InnoDB的索引
InnoDB 引擎中,其数据文件本身就是索引文件。
相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。
在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。索引类型
主键索引
二级索引/辅助索引
二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。唯一索引,普通索引,前缀索引等索引属于二级索引。
唯一索引(Unique Key) :唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
- 普通索引(Index) :普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
- 前缀索引(Prefix) :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
- 全文索引(Full Text) :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
聚集索引和非聚集索引
聚集索引
聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
在 Mysql 中,InnoDB 引擎的表的 .ibd文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。聚集索引的优点
聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。聚集索引的缺点
- 依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
非聚集索引
非聚集索引即索引结构和数据分开存放的索引。
MYISAM 引擎的表的.MYI 文件包含了表的索引,该表的索引(B+树)的每个叶子非叶子节点存储索引,叶子节点存储索引和索引对应数据的指针,指向.MYD 文件的数据。非聚集索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
非聚集索引的优点
更新代价比聚集索引要小 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的。
非聚集索引的缺点
跟聚集索引一样,非聚集索引也依赖于有序的数据;
可能会二次查询(回表) :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
非聚集索引一定会回表吗
非聚集索引不一定会回表,具体与查询的内容相关。
-- 建表create table student (id bigint primary key,name varchar(10) index not null,age tinyint not null);-- 回表查询select name,age from student where name = '张三';-- 不回表查询select name from student where name = '张三';# 以上两者的区别在于,name是二级索引,仅保存了自己的数据和主键索引的地址,当仅查询自己的数据的时候可以直接返回数据# 当查询的数据还包含其他的数据的时候,就需要回表查询了
覆盖索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。
我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。
覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了, 而无需回表查询。创建索引的注意事项
选择核实的字段创建索引
- 不为空的字段:索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代
- 被频繁查询的字段:我们创建索引的字段应该是查询操作非常频繁的字段
- 被作为条件查询的字段:被作为where条件查询的字段,应该被考虑建立索引
- 频繁需要排序的字段:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间
- 被经常频繁用于连接的字段:对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率
- 被频繁更新的字段应该慎重简历索引
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
- 尽可能的考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
- 避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
- 考虑在字符串类型上使用前缀索引替代普通索引
前缀索引仅用于字符串类型,较普通索引会占更小的空间,所以可以考虑使用前缀索引替代普通索引。
使用索引的建议
- 对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合索引
- 避免where子句中对字段史家函数,这会造成索引失效
- 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键
- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗。「MySQL5.7及之后可以通过查询sys库的
schema_unused_indexes视图来查询哪些索引从未被使用」 - 在使用limit offset查询缓慢时,可以使用索引来提高性能
MySQL如何添加索引
-- 主键索引ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` );-- 唯一索引ALTER TABLE `table_name` ADD UNIQUE ( `column` );-- 普通索引ALTER TABLE `table_name` ADD INDEX index_name ( `column` );-- 全文索引ALTER TABLE `table_name` ADD FULLTEXT ( `column`);-- 联合索引ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` );
