目录
二、索引
1.索引概述
(1)介绍
(2)优缺点
2.索引结构
(1)二叉树
(2)红黑树
(3)B-Tree(多路平衡查找树)
(4)B+Tree
(5)Hash索引
1)介绍
2)Hash索引特点
3.索引分类
4.索引语法
(1)创建索引
(2)查看索引
(3)删除索引
5.SQL性能分析
(1)SQL执行频率
(2)慢查询日志
(3)profile详情
(4)explain执行计划
6.索引使用
(1)最左前缀法则
(2)范围查询
(3)索引列运算
(4)字符串不加引号
(5)模糊查询
(6)or连接的条件
(7)数据分布影响
(8)SQl提示
(9)覆盖索引
(10)前缀索引
(11)单列索引与联合索引
7.索引设计原则
索引是帮助MysQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。
优点 | 缺点 |
数据检索的效率,降低数据库的IO成本 | 索引列也要占用空间。 |
通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗 | 索引大大提高了查询效率,同时却也降低更新表的速度,如对表进行INSERT、UPDATE、DELETE时,效率降低。 |
索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
注意:平常所说的索引,未特别指明,指B+Tree结构组织索引
顺序插入,会形成一个链表,查询性能大大降低,大数据情况下,层级较深,检索速度慢
大数据量情况下,层级较深,检索速度较慢
以一棵最大度数(树的度数指一个节点的子节点个数)为5(5阶)的b-tree为例(每个节点最多存储4个key,5个指针):
蓝色表示一个节点最多存储4个key,而一个节点最多有5个指针,即5个分叉,在小于20的范围有一个指针,在20到30之间有一个指针,以此类推,若有n个Key,则有n+1个指针;在蓝色节点下方的绿色代表数据
B+Tree动态形成参考网站:B-Tree Visualization
所有元素出现在叶子节点,且形成一个单向链表,而上部分非叶子节点起到索引作用,叶子节点用来存放数据
数据结构可视化网站;
Data Structure Visualization (usfca.edu)
MySQL索引数据结构对经典的B+Tree进行了优化,在原B+Tree的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+Tree,提高区间访问的性能
哈希索引就是采用一定的hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。若两个(或多个)键值,映射到一个相同的槽位上,他们就产生了hash冲突(也称为hash碰撞),可以通过链表解决。
举例:
先计算每行数据的hash值(表左侧),再通过hash函数算出name字段的槽位值,与哈希表的槽位(蓝色)对应,再在name字段旁记录hash值,当两个人的name字段的槽位相同(金庸与杨逍),则在后面增加链表,记录name字段以及hash值,若有多个,则继续向后延长链表
①Hash索引只能用于对等比较(=,in),不支持范围查询(between,>,<,…)
②无法利用索引完成排序操作
③查询效率高,通常只需要一次检索就可以了(哈希碰撞除外,此情况检索要在链表中对比查找对应元素),效率通常要高于B+tree索引
在MySQL中,支持hash索引的是Memory引擎,而InnoDB中具有自适应hash功能(MySQL会根据查询条件,在指定条件下,自动将B+Tree索引构建为hash索引),hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的
思考:为什么InnoDB存储引擎选择使用B+tree索引结构?
①相对于二叉树,层级更少,搜索效率高;
②对于B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;③相对Hash索引,B+Tree支持范围匹配及排序操作
在InnoDB存储引擎中,根据索引的存储形式,又可分为以下两种:
聚集索引选取规则:
eg.根据表构建索引:
以id即主键为索引,构建的是主键索引,也就是聚集索引,叶子节点存放的是行数据
以name字段建立的索引为二级索引(因为聚集索引有且只有一个),叶子节点存放的是id
回表查询:先根据二级索引找到主键值,再根据主键值在聚集索引中拿到行数据
eg.select * from user where name=’Arm’.
先在二级索引里找到Arm的id,再根据id在聚集索引里找到Arm的行数据,即Arm的所有数据
CREATE [UNIQUE | FULLTEXT] INDEX 索引名称(一般形式为index_表名_字段1名_字段2名_...) ON 表名 (字段1名 排序方式,字段2名 排序方式...);
排序方式可省略,默认为升序排序
索引名称处的字段名若太长,可简写
一个索引关联一个字段称为单链索引,关联多个字段称为联合索引或组合索引
eg1.为表tb_user的phone字段创建唯一索引
create unique index idx_user_phone on tb_user(phone);
eg2.为表tb_user的name字段创建常规索引
create index idx_user_name on tb_user(name);
eg3.为表tb_user的profession、age、status字段创建联合索引
create index idx_user_pro_age_sta on tb_user(profession,age,status);
(pro,sta均为简写)
eg4.将age按升序,phone按倒序建立联合索引
create index idx_user_age_pho_ad on tb_user(age asc,phone desc);
SHOW INDEX FROM tablename;(以行的形式查看表的索引)
SHOW INDEX FROM tablename\G;(以列的形式查看表的索引)
DROP INDEX indexname ON tablename;
eg.删除表tb_user的索引idx_user_phone
drop index idx_user_phone on tb_user;
1)查看服务器状态信息
show [session | global] status;
2)查看当前数据库insert、update、delete、select的访问次数
show global status like‘com_______’;(7个下划线代表7个字符)
1)介绍:慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒,即执行的SQL执行耗时超过10s就会被认为是慢查询,然后记录在慢查询日志中)的所有SQL语句的日志。
2)查询慢日志的开启状态:
show variables like ‘slow_query_log’;
MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)最后配置如下信息:
①开启MySQL慢日志查询开关
slow_query_log=1
②设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会被视为慢查询,并被记录在慢查询日志
long_query_time=2
配置完成后,重启MySQL进行测试,查看慢日志文件中记录的信息:/var/lib/mysql/localhost-slow.log
1)查询当前MySQL是否支持profile操作(YES为支持)
select @@have_profiling;
2)默认profiling是关闭的,可通过set语句在session/global级别开启profiling
select @@profiling; # 查询profilig的开关情况,0表示关闭
set profiling=1;
3)查询耗时情况
①查询每条SQL语句的耗时情况
show profiles;
②查询指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
(query_id指的是通过show profiles;命令所得到的SQL语句对应的query_id)
③查询指定query_id的SQL语句cpu的使用情况
shoq profile cpu for query query_id;
explain或desc命令获取MySQL如何执行select语句信息,包括在select语句执行过程中表如何连接和连接顺序
语法:
explain/desc select 字段列表 from 表名 where 条件;
结果如下:
表中各字段含义:
①id
select查询的序列号,表示查询中执行select子句或者是操作表的顺序(id相同,执行顺序从上到下;id不同,值越大,越先执行)
②select_type
表示select的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、
UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等
③type
表示连接类型,性能由好到差的连接类型为NULL、system、const、eq_ref、ref、range、index、all。
一般来说,不访问任何表才为null,访问系统表为system,根据主键和唯一索引访问表为const,使用非唯一索引访问表为ref,
④possible_key
显示可能应用在这张表上的索引,一个或多个。
⑤Key
实际使用的索引,如果为NULL,则没有使用索引。
⑥Key_len
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
⑦rows
MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。
⑧filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。
⑨Extra
额外信息,展示前面字段未展示出的信息
(可提高查询的效率,大大缩短时间消耗)
(主要针对联合索引)查询从索引的最左列开始,并且不跳过索引中的列。若跳过某一列,此后的索引将失效。(索引的顺序可调换)
eg.有一个联合索引idx_user_pro_age_sta,在表tb_user中查询profession=’软件工程’,age=31,status=’0’的人的信息,可通过命令
select * from tb_user where profession=’软件工程’ and age=31 and status=’0’;
此时通过explain执行计划,可查询到该命令使用了索引idx_user_pro_age_sta
根据最左前缀法则,将条件改为 profession=’软件工程’或 profession=’软件工程’ and age=31,通过explain执行计划查询到都使用了索引idx_user_pro_age_sta,而将条件改为age=31 and status=’0’或status=’0’等,查询到没有使用索引,将条件改为profession=’软件工程’ and status=’0’,查询到使用了索引idx_user_pro_age_sta,而此时,使用了索引的只有profession,status失效了。
联合索引中,出现范围查询(>,<),范围查询的右侧的列索引失效
,但是使用>=或<=不会
eg.select * from tb_user where profession=’软件工程’ and age>31 and status=’0’;
此时,因为age使用了范围查询,故status使用索引失效
但是,将>改为>=不会导致status失效
不要在索引列上进行运算操作,否则索引将失效
eg.已知phone已建立了索引,现查询手机号后两位为15的人:
select * from tb_user where substring(phone,10,2)=’15’;
此时对phone进行了截取,导致索引失效,即该语句未使用索引,SQL性能低
字符串类型字段使用时,不加引号,索引将失效、
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引将失效
eg.select * from tb_user where phone like ‘%15’;
用or分割开的条件,如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的所有索引都不会被用到
eg.已知id有索引,age没有索引
select * from tb_user where id=10 or age=23;
该语句不会用到索引
如果MySQL评估使用索引比全表慢,则不使用索引(若满足要求的结构占表的大多数,则不使用索引)
eg.表tb_user中绝大部分人的年龄都大于等于20,且age建立了索引
select * from tb_user where age>=20;
该语句中,由于绝大部分人的年龄都大于等于20,故MySQL选择全表扫描,而不使用索引
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
select * from tb_user use index(idx_user_age) where age=25;
尽量使用覆盖索引(查询使用了索引,并且需要查询的字段在该索引中已经全部能找到),减少使用select *(因为除非所有字段都建立了索引,否则存在要查询的字段在索引中找不到,需要回表查询,性能相对较低)。
eg2.已知有id,name,age,gender字段,且id为主键(为聚集索引,叶子节点为行数据),name建立了辅助索引(叶子节点为id),
①select * from tb_user where id=2;
②select * from tb_user where name=2;
③select id,name,gender from tb_user where name=’Mike’;
③中通过name辅助索引返回id和name,但gender需要根据id返回聚集索引返回gender,即回表查询
①比②的性能高,因为①使用聚集索引,而聚集索引的叶子存放的是行数据,而②中返回的是id,还需回表查询
enplain执行计划最后一个extra结果:
using index condition:查找使用了索引,但需要回表查询数据
using where,using index:查找使用了索引,但是需要的数据都在索引列中能找到,不用回表查询数据(性能更高)
当字段类型为字符串时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率
语法:
create index idx_name on tablename(column(n));
n为前缀长度:
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,
唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
eg.email为一个索引,它的选择性计算语句如下:
select count(distinct email)/count(*) from tb_user;
得出选择性为1.0000
select count(distinct substring(email,1,5))/count(*) from tb_user;
得出选择性为0.9583
创建email索引:
create idx_email_5 on tb_user(email(5));
此时若查询email为‘[email protected]’的人的信息
select * from tb_user where email=‘[email protected]’;
前缀索引查询流程:
先截取目标email的前5个字符,再通过辅助索引返回id,最后根据id通过聚集索引返回行信息
1)单列索引:一个索引值包含单个列
2)联合索引(组合索引):一个索引包含多个列
1)针对于数据量较大,且查询比较频繁的表建立索引。
2)针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
3)尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
4)如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
5)尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
6)要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
7)如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。