MySQL学习·第八站~
本文已收录至专栏:MySQL通关路
❤️文末附全文思维导图,感谢各位点赞收藏支持~
索引(index)是帮助MySQL高效获取数据的数据结构(有序)。数据库除了存储数据之外,还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 当我们在查找数据的时候,就可以在这些数据结构上实现高级查找算法,快速查找到我们想要的数据,这种数据结构就是索引。
假如我们要执行一条查询SQL语句 : select * from user where age = 45;
在无索引的情况下,查询数据就需要从第一行开始扫描,一直扫描到最后一行,我们称之为全表扫描,性能很低。
如果我们针对于这张表的age字段建立了索引,假设索引的数据结构就是二叉树(实际并不是,而是一种比二叉树更高效的数据结构),那么也就意味着,会对age这个字段建立一个二叉树的索引结构。此时我们在进行查询时,只需要扫描三次就可以找到数据了,极大的提高的查询的效率。
优势 | 劣势 |
---|---|
提高数据检索的效率,降低数据库的IO成本 | 索引列也是要占用空间的 |
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。 | 索引大大提高了查询效率,同时却也降低更新表的速度, 如对表进行INSERT、UPDATE、DELETE时,效率降低。 |
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构,主要包含以下几种:
索引结构 | 描述 |
---|---|
B+Tree 索引 |
最常用的索引类型,大部分引擎都支持 B+ 树索引 |
Hash 索引 |
底层数据结构是用哈希表实现的, 只有精确匹配索引列的查询才有效, 不支持范围查询,也无法利用索引完成排序,但是查询效率较高。 |
R-tree (空间索 引) |
空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类 型,通常使用较少 |
Full-text (全文 索引) |
是一种通过建立倒排索引,快速匹配文档的方式。类似于 Lucene,Solr,ES |
不同的存储引擎对于索引结构的支持情况也有所不同:
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+tree索引 | 支持 | 支持 | 支持 |
Hash 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 支持 | 支持 | 不支持 |
我们平常所说的索引,如果没有特别指明,一般都是指B+树结构组织的索引。
特别说明:MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上,增加了一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能,利于排序。
在MySQL数据库中,将索引的具体类型主要分为以下几类:
分类 | 含义 | 特点 | 关键字 |
---|---|---|---|
主键索引 | 针对于表中主键创建的索引 | 默认自动创建, 只能有一个 | PRIMARY |
唯一索引 | 避免同一个表中某数据列中的值重复 | 可以有多个 | UNIQUE |
常规索引 | 快速定位特定数据 | 可以有多个 | |
全文索引 | 全文索引查找的是文本中的关键词,而不是比 较索引中的值 | 可以有多个 | FULLTEXT |
而在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
分类 | 含义 | 特点 |
---|---|---|
聚集索引(Clustered Index ) |
将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据 | 必须有,而且只有一个 |
二级(非聚集)索引(Secondary Index ) |
将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键 | 可以存在多个 |
聚集索引选取规则:
如果存在主键,主键索引就是聚集索引。
如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
聚集索引的叶子节点下挂的是这一行的数据 。
当我们执行上述的SQL语句:
这种先到二级索引中查找数据,找到主键值,然后再到聚集索引中根据主键值,获取数据的方式,就称之为回表查询。
由于存在回表查询,我们通过聚集索引查询值的方式要比通过二级索引查询值的方式快很多。因为走聚集索引,可以直接返回数据。 而走二级索引,需要先获取id值,然后再查询聚集索引获取值要慢许多。
CREATE [ UNIQUE | FULLTEXT ] INDEX 索引名 ON table_name (字段1,字段2,... );
-- 为用户表的姓名name字段创建名为idx_user_name的唯一索引
CREATE UNIQUE INDEX idx_user_name ON user(name);
-- 为用户表的手机号phone字段创建名为idx_user_phone的普通索引
CREATE INDEX idx_user_phone ON user(phone);
-- 为用户表的profession,age,status字段创建名为idx_user_pro_age_stae的联合索引
CREATE INDEX idx_user_pro_age_sta ON user(profession,age,status);
SHOW INDEX FROM 表名;
DROP INDEX 索引名 ON 表名;
如果索引了多列(联合索引),要遵守最左前缀法则。
最左前缀法指的是:查询会从索引的最左列开始,最左边的索引列必须存在,否则索引全部失效。并且不能跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
例如我们可以查看以下几种SQL的explain执行计划,可以发现索引有效
-- 全部存在,没问题
explain select * from user where profession = '软件工程' and age = 31 and status = '0';
-- 漏了status,其索引失效
explain select * from user where profession = '软件工程' and age = 31;
-- 漏了status,age,其索引失效
explain select * from user where profession = '软件工程';
-- 漏了age,其索引失效与status索引都失效
explain select * from user where profession = '软件工程' and status = '0';
-- 与编写的先后顺序无关,存在即可
explain select * from user where age = 31 and status = '0' and profession = '软件工程';
而我们遗漏了profession,即违背最左前缀法则将会导致索引失效
-- 失效
explain select * from user where age = 31 and status = '0';
-- 失效
explain select * from user where status = '0';
-- 失效
explain select * from user where age = 31;
联合索引中,出现范围查询(>,<
),范围查询右侧的列索引失效
explain select * from user where profession = '软件工程' and age > 30 and status = '0';
根据长度key_len
我们可以知道:联合索引生效了,但是只有profession与age的索引生效了,status字段的索引没有生效。
值得注意的是:当范围查询使用>=
或 <=
时,则不会产生影响。 因此,在业务允许的情况下,进行范围查询时,为了避免索引失效,我们可以使用类似于 >=
或 <=
这类的范围查询,代替使用>
或 <
。
当我们在索引列上进行运算操作, 索引也将失效。
explain select * from user where substring(phone,10,2) = '15'
当我们对添加索引的字符串类型字段进行操作时,如果字符串不加引号,对于查询结果,没什么影响,但是数据库存在隐式类型转换,索引将失效。
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效
用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
我们发现当or连接的条件,左右两侧字段都有索引时,索引才会生效~
如果MySQL评估使用索引比全表扫描更慢,则不使用索引。
这是因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。
当我们的字段存在多个索引时,MySQL会进行评估自动选择一个索引进行使用。我们也可以借助于SQL提示指定MySQL使用哪个索引。
use index
:建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估,即或许不会遵从建议)explain select * from 表名 use index(索引名) where 查询条件....;
ignore index
:忽略指定的索引explain select * from 表名 ignore index(索引名) where 查询条件....;
force index
: 强制使用索引explain select * from 表名 force index(索引名) where 查询条件....;
在需求允许的情况下,尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少select *
的使用。
例如我们使用age索引查询数据,如果只需要id,profession,age, status,那么我们可以直接写成select id,profession,age, status from user where profession = '软件工程' and age = 31 and status = '0'
,而不是select *
.
从上述的执行计划我们可以看到,前面两天SQL的结果为 Using where; Using Index
; 而后面两条SQL的结果为: Using index condition
。
Extra | 含义 |
---|---|
Using where; Using Index | 查找使用了索引,但是需要的数据都在索引列中能找到,所以不需 要回表查询数据 |
Using index condition | 查找使用了索引,但是需要回表查询数据 |
这里便牵涉到了我们上述所介绍的回表查询,我们在user表中创建了一个联合索引 idx_user_pro_age_sta,该索引关联了三个字段 profession、age、status,而这个索引也是一个二级索引,所以叶子节点下面挂的是这一行的主键id。 所以当我们查询返回的数据在 id、profession、age、status 之中,则直接走二级索引直接返回数据了。 如果超出这个范围,就需要拿到主键id,再去扫描聚集索引,再获取额外的数据 了,这个过程就是回表。 而我们如果一直使用select * 查询返回所有字段值,很容易就会造成回表查询(除非是根据主键查询,此时只会扫描聚集索引)。
当字段类型为字符串(varchar,text,longtext等)时,有时候这些字段值会非常大(例如一段很长的文本),如果直接对此建立索引,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
create index 索引名 on 表名(字段名(索引长度)) ;
有时候我们难以确定应该创建多长的索引,这时可以根据索引的选择性来决定。选择性是指不重复的索引值(基数)和数据表的记录总数的比值, 索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
-- 可以直接计算比值
select count(distinct 字段名) / count(*) from 表名;
-- 也可以使用函数截取部分数据进行计算
select count(distinct substring(字段名,开始位置,结束位置)) / count(*) from 表名;