为了能让索引能有更直观的效率,我在一张表里扔进了百万条数据(光靠这些数据,生成数据代码写了一个小时,解决MySQL8的文件导入权限问题解决了两个小时,导入数据花费了一个小时,我太难了~(;д;)。但是,一切不以实践数据为标准的理论都是**耍!流!氓!**o(´^`)o)。让我们一边讲解MySQL的使用一边看一下索引能为我们的查询带来的性能提升吧。
索引使用的优势
提高查询效率,简单来说就是查的快!再快!更快!外面说的什么提高表的速度、加速表连接、减少分组及排序时间、提高系统性能,说白了都是快,查得快!(顺便我发现百度出来的索引使用优势劣势貌似就那么一两套,真就天下文章一大抄呗,抄我的也欢迎,烦请注明出处或者作者Solid_lele哈)
具体会有多快呢?
这是没有索引的百万级数据查找(这个算快的了,慢的四十秒,时间不是很稳定,因为是从磁盘块中读取数据,原理参照我开头提到的那篇文章)10.797s:
这是有索引的百万级数据查找0.272s:
差了四十多倍,就相当于别人一年赚四十多万,我一年赚一万,这差距真的是太痛苦了。
索引使用的劣势
凡事具有两面性,有好就会有坏,拿时间换空间或者拿空间换时间这种操作屡见不鲜,索引就是拿空间换时间,虽然并不是那么典型(因为它核心并不是增大空间减少时间,而是通过维护类似目录的结构减少IO的读写次数,最典型的空间换时间是计数排序)。坏处自然就出现了:
1、维护成本高
索引维护了一个类似于目录的结构,你可以联想新华字典的目录,当你创建目录的时候,如果没有程序帮忙,你自己手写目录的话,需要一页一页的去翻去看确定那个字在哪儿,然后写进目录里;万一有个字被删了或者加了一个字还要重新调整一遍目录。对程序也是一样,索引的创建和维护是需要消耗性能的,所以会降低数据库修改时的性能。就以创建索引来说,创建百万级别的varchar数据BTREE索引,数据内容长度为20个汉字,消耗的时间为61.234s:
再比较一下维护索引的代价,比如无索引百万级别数据插入一条时间为0.480s:
而有索引的百万级别数据插入一条时间为1.273s:
差距其实还挺大的(请忽略我乱打插入的三个字)。
2、所占空间大
既然提到空间换时间,那么空间的浪费是不可避免的,我做了下面的这个测试(测试数据库MySQL8,数据库运行环境windows)。
首先创建了一个临时表tmp_name,其中只有一列名为c_name的字段,发现文件夹中存储的ibd文件初始大小为112k,插入百万条数据(100万条数据整哦,一个不多一个不少哦)后,大小为40960k:
然后创建了一个BTREE索引,大小变为了73728k:
大约多用了一倍的空间。但实际中不可能每个字段都加索引,生产中为索引预留的空间大概占数据大小的五分之一就够了。在这个数据为重效率至上的时代,磁盘的空间成本貌似还是比较划算的。
其实使用索引还有个劣势,就是你需要花费时间来看我这篇文章o(////▽////)q
索引的分类
单值索引:就是一个索引只包含单个列,一个表可以有多个单列索引;唯一索引:就是索引列的值必须唯一,但允许有空值(主键索引就是唯一索引,但它不能为null);复合索引:就是联合索引,也就是一个索引包含多个列;
我看网上介绍索引的都说了唯一索引和复合索引,咋,单值索引就不算索引了?互相“借鉴”的时候好歹也自己思考一下好不啦。
索引的创建规则
索引既然有这么明显的优势以及劣势,我们自然要把它的优势最大化,劣势尽可能避免。所以索引最好能做到:
1、经常作为查询或者排序条件;
2、重复值尽可能少;
3、增删改不会太多。
满足上面三条规则的就可以创建索引了(不符合规则怎么样这种数据我实在是不想贴出来了,就直接说吧,这篇文章到现在算上造数据+各种实验写了十个小时了,还没结束,又赶上LOL的无限火力,明明是周末却只能羡慕别人在玩的我很是痛苦哇)。
所以总结一下适合创建的情况(就是以上面三个条件作为参考的各种情况啦):
1、主键:主键是自动建立唯一索引的,不用咱操心;2、频繁作为查询条件的字段:毕竟就是为了查的快才创建索引的嘛;3、查询中与其它表关联的字段:这就是外键了,不仅关联查询用到了,重复值很少,很棒;4、如果有多个字段,尽量创建组合索引:当查询优化器觉得分析两个查询索引太费劲了,还不如用一个的时候,它就给你用一个,所以只要遵循最佳左前缀原则,还是组合索引更靠谱;5、查询中排序的字段:排序字段若通过索引去访问将大大提高排序速度;6、查询中统计或者分组字段:和上面情况一样啦。
那什么时候不适合创建呢:
1、表记录太少而且不会变得很多:夭寿啦,就这么几百条数据建索引不如直接查一遍;
2、频繁更新的字段不适合创建索引:维护索引很累的,查的没这么多但是总改总改,系统就像被经常需求变动的程序员,不跟你打起来就不错了;
3、Where条件里用不到的字段不创建索引:不要问为什么,问就是出门左转是电梯;
索引的CRD没有U
创建Create
#该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE [table_name] ADD PRIMARY KEY ([column_list]);
#这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE [table_name] ADD UNIQUE [index_name] ([column_list]);
#添加普通索引,索引值可出现多次。
ALTER TABLE [table_name] ADD INDEX [index_name] ([column_list]);
#该语句指定了索引为 FULLTEXT ,用于全文索引。
ALTER TABLE [table_name] ADD FULLTEXT [index_name] ([column_list]);
查看Read
SHOW INDEX FROM [table_name];
删除Drop
DROP INDEX [index_name] ON [table_name];
更新,不好意思没有,想改就是删了重加。
索引的分析(Explain)
终于走到这儿了,真刀真枪打一架吧,前面都是开胃小菜,现在才是正餐,但估计你们都快吃饱了,我也做累了,就很尴尬。
Explain是什么
话不多说,看官网API说明:
The EXPLAIN statement provides information about how MySQL executes statements. EXPLAIN works with SELECT, DELETE, INSERT, REPLACE, and UPDATE statements.
EXPLAIN returns a row of information for each table used in the SELECT statement. It lists the tables in the output in the order that MySQL would read them while processing the statement. MySQL resolves all joins using a nested-loop join method. This means that MySQL reads a row from the first table, and then finds a matching row in the second table, the third table, and so on. When all tables are processed, MySQL outputs the selected columns and backtracks through the table list until a table is found for which there are more matching rows. The next row is read from this table and the process continues with the next table.
EXPLAIN语句提供了关于MySQL怎样执行语句的信息。EXPLAIN可以用来分析SELECT、DELETE、INSERT、REPLACE和UPDATE语句。EXPLAIN为SELECT语句中使用的每个表返回一行信息。它按照MySQL在处理语句时读取的顺序,列出执行输出中的表。MySQL使用嵌套循环连接方法解析所有连接。这意味着MySQL从第一个表中读取一行,然后在第二个表、第三个表中找到匹配的行,以此类推。当处理完所有表后,MySQL将输出所选的列,并通过表列表进行回溯,直到找到一个具有更多匹配行的表为止。从该表读取下一行,然后继续处理下一个表。
说白了就是能让你看表的读取顺序、用索引情况、表之间的引用、优化器查询的情况这些信息的执行情况分析。(不允许说每个汉字都认识凑到一起不知道啥意思了)
Explain的使用及分析
使用:就是Explain+你的sql语句:
EXPLAIN SELECT * FROM t_solid_test where c_name = 'Solid';
查询出来长这个样子:
这么多信息都代表什么?咱一个个来看。
id
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序。
id值越大,优先级越高,越先执行;id如果相同,从上往下顺序执行。
select_type
查询类型
SIMPLE:简单的 select 查询,查询中不包含子查询或者UNION;
PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为;
UNION:若第二个SELECT出现在UNION之后,则被标记为UNION; 若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED;
DEPENDENT UNION:一个UNION中的第二个或更高版本的SELECT语句 ,取决于外部查询;
UNION RESULT:UNION的结果。
SUBQUERY:在SELECT或WHERE列表中包含了子查询;
DEPENDENT SUBQUERY:在子查询中的第一个SELECT,取决于外部查询
DERIVED:在FROM列表中包含的子查询被标记为DERIVED(衍生) MySQL会递归执行这些子查询, 把结果放在临时表里;
DEPENDENT DERIVED:派生表依赖于另一个表;
MATERIALIZED:物化子查询;
UNCACHEABLE SUBQUERY:子查询,其结果无法缓存,必须针对外部查询的每一行重新进行评估;
UNCACHEABLE UNION:UNION 属于不可缓存子查询的中的第二个或更高版本的选择(请参阅UNCACHEABLE SUBQUERY的参考资料 );
partitions
查询分区的匹配记录。如果未分区则为NULL;
table
显示这一行的数据是关于哪张表的;
type
访问类型排列, 显示查询使用了何种类型:
system:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。
const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快 如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。
ref:非唯一性索引扫描,返回匹配某个单独值的所有行. 本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而, 它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
fulltext:使用FULLTEXT 索引执行联接。
ref_or_null:这种连接类型类似于 ref,但是除了MySQL会额外搜索包含NULL值的行。
index_merge:此联接类型指示使用索引合并优化。在这种情况下,key输出行中的列包含所用索引的列表,并key_len包含所用索引 的最长键部分的列表。有关更多信息,请参见 第8.2.1.3节“索引合并优化”。
unique_subquery:此类型替换 以下形式的eq_ref某些 IN子查询:value IN (SELECT primary_key FROM single_table WHERE some_expr),unique_subquery 只是一个索引查找函数,它完全替代了子查询以提高效率。
index_subquery:此连接类型类似于 unique_subquery。它代替IN子查询,但适用于以下形式的子查询中的非唯一索引:value IN (SELECT key_column FROM single_table WHERE some_expr)。
range:只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引 一般就是在你的where语句中出现了between、<、>、in等的查询 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的)。
all:Full Table Scan,将遍历全表以找到匹配的行。备注:一般来说,得保证查询至少达到range级别,最好能达到ref。
possible_keys
显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
key
实际使用的索引。如果为NULL,则没有使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠。
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
ref
显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
Extra
包含不适合在其他列中显示但十分重要的额外信息:
Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”。
Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序order by 和分组查询 group by。
USING index:表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错! 如果同时出现using where,表明索引被用来执行索引键值的查找; 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
Using where:表明使用了where过滤。
using join buffer:使用了连接缓存。
impossible where:where子句的值总是false,不能用来获取任何元素。
select tables optimized away:在没有GROUPBY子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算, 查询执行计划生成的阶段即完成优化。
distinct:优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作
索引使用案例
最理想的情况
最佳左前缀原则
后台创建的索引是name_sex_age的联合索引,联合索引中,从左往右匹配,如果最开始匹配不到,则索引失效。
尽量不用函数操作索引
在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
索引中范围条件右边的列不会被使用
下面这个联合索引,当联合索引中间的值查询条件为范围查询时,右侧的索引不会被用到。
少用select *
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *。
索引失效的几个情况
不等于(!= 或者<>)、is null、is not null 、or、like以通配符开头(’%abc…’)、字符串不加单引号(类型转换)
这就不贴图了,没啥可贴的,用了type就是ALL;
索引使用的总结建议
最后,给一个索引使用的总结吧:
对于单键索引,尽量选择针对当前query过滤性更好的索引。在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引。尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的。
作者:Solid-Snaker
原文链接:https://blog.csdn.net/jcSongle/article/details/106133005