一、索引的概念
索引是一种数据结构。数据库索引是DBMS中一个排序的数据结构(有序),以协助快速查询、更新数据表中数据。索引的使用通常使用B树以及变种B+树。
索引就相当于目录,可以方便数据内容查找,本身也占用物理空间。
二、索引的优缺点
1. 索引的优点
①通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
②提高数据检索效率,降低数据库的IO成本,可以加快数据的检索速度。
③可以加速表和表之间的连接,特别是在实现数据的参考完整性方面。
④通过使用索引,可以在查询过程中,使用优化隐藏器,提高系统性能。
⑤通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
2. 索引的缺点
①耗费时间,降低维护效率。创建和维护索引都要耗费时间,时间随着数据量的增加而增加。当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样降低了数据的维护速度。
②占用物理空间。空间是,索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间。如果要建立聚簇索引,那么需要的空间就会更大。
三、索引的数据结构
(一)索引分类
1. 分类
数据库索引根据结构分类,主要有B+树索引、Hash索引、位图索引三种。
2. 不同存储引擎对应的索引结构
MYSQL的索引是在存储引擎层实现的,不同的存储引擎有不同的索引结构。
索引结构 | 描述 |
---|---|
B+Tree索引 | 最常见的索引类型,大部分引擎都支持B+树索引 |
Hash索引 | 底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询 |
R-Tree(空间索引) | 空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少 |
Full-Text(全文索引) | 是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucence、Solr、ES |
3.不同存储引擎对索引结构的支持
我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。
索引 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+树索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 不支持 |
R-Tree索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本后支持 | 支持 | 不支持 |
(二)二叉树索引(未选择,本部分只是说明为什么MySQL不选择二叉树做索引)
1.二叉树索引理想结构
2.顺序插入
如果主键是顺序插入的,则会形成一个单向链表
3.二叉树索引缺点
①顺序插入时,会形成一个链表,查询性能大大降低。
②大数据量情况下,层级较深,检索速度慢。
4.解决方案
选择红黑树,红黑树是一颗自平衡二叉树,即使是顺序插入数据,最终会得到一颗平衡的二叉树。
解决了顺序插入的问题,但是红黑树也是二叉树,仍然存在大数据量情况下层级深检索慢的问题。【这也是MySQL的索引结构中不选择二叉树或红黑树的原因。】
(三)B-Tree索引
1.B树定义
B树是一种多叉路衡查找树,相对于二叉树,B树每个节点可以有多个分支,即多叉。
2.B树特点
一颗B树的最大度数是5(5阶B树),树的度数指的是一个节点的子结点个数。那这个B树每个节点最多可以存储4个key,5个指针。
即n阶B树,每个节点最多可以存储(n-1)个key,n个指针。在B树中,非叶子节点和叶子节点都会存放数据。
(四)B+Tree索引
1.B+树定义
B+树是B树的变种。假设一颗3阶B+树,则B+树每个节点存放2个key,3个指针。
绿色框部分是索引部分,仅仅起到索引数据的作用,不存储数据。
红色框部分是数据存储部分,在其叶子节点中要存储具体的数据。
2.B+树的特点
①所有的数据都会出现在叶子节点。
②叶子节点形成一个单向链表。
③非叶子节点仅仅起到索引数据作用,具体的数据都是在叶子节点存放的。
3.MySQL优化B+树索引
MySQL索引数据结构对经典的B+树进行了优化。在原B+树基础上,增加了一个指向相邻叶子结点的链表指针,形成了带有顺序指针的B+树,提高区间访问的性能,利于排序。叶子节点形成了双向链表。
4.B+树和B树的区别
B+树 | B树 |
---|---|
①所有的非叶子节点只存储关键字信息,用做索引 | ①所有的非叶子节点存储存储数据 |
②所有的具体数据存储在叶子节点中 | ②所有的具体数据存放在全部节点中(叶子节点+非叶子节点) |
③叶子节点包含了全部元素 | ③叶子节点并不包含全部元素 |
④叶子节点之间有链指针 | ④叶子节点之间没有链指针 |
(五)Hash索引
1.哈希索引定义
哈希索引就是采用一定的Hash算法,将键值换算成新的hash值,映射到对应的槽位上,然后存储在hash表中。
如果两个(或多个)键值,映射到一个相同的槽位上,就产生了Hash冲突(hash碰撞),通过链表来解决。
2.哈希索引特点
①Hash索引只能用于对等比较\等值匹配(=,in),不支持范围查询(betweeen,<,>)。hash索引等值匹配的时候,只需要去计算对应hash值,然后去链表找到对应hash值即可。Hash索引存储的时候是没有顺序的,所以没有办法进行范围查询。
②无法利用索引完成排序操作,因为Hash计算得到的结果是无序的。
③查询效率高,通常只需要一次检索就可以了,效率通常要高于B+树索引。(前提是不出现Hash碰撞,出现了还要去链表中检索)
3.存储引擎支持
在MySQL中,支持Hash索引的是Memory存储引擎,而InnoDB中具有自适应Hash功能【指MySQL会根据查询条件,在指定的条件下,会自动地将B+树索引构建为Hash索引】,hash索引是InnoDB存储引擎根据B+树索引在指定条件下自动构建的。
为什么InnoDB存储引擎选择使用B+树索引结构?
①相对于二叉树,层级更少,搜索效率更高。
②对于B树,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页(一页大小固定为16K)中存储的键值减少、指针跟着减少,要同样保存大量数据,只能增加树的高度,层级变多,查询性能降低。而B+树的非叶子节点存放键值和指针,不存放数据,同样保存大量数据,数据可放的位置变多,树的高度相对较低,层级少,查询性能高于B树。
③相对于Hash索引,B+树支持范围匹配以及排序。Hash索引支持精确查找,不支持范围匹配,也不支持排序。
四、使用B+树的好处
①相对于二叉树,层级更少,搜索效率更高。
②对于B树,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页(一页大小固定为16K)中存储的键值减少、指针跟着减少,要同样保存大量数据,只能增加树的高度,层级变多,查询性能降低。而B+树的非叶子节点存放键值和指针,不存放数据,同样保存大量数据,数据可放的位置变多,树的高度相对较低,层级少,查询性能高于B树。
B+树的非叶子节点只存放键,不存放数据,一次读取,可以在同一页中获取更多的键,有利于更快缩小查找范围。
③相对于Hash索引,B+树支持范围匹配以及排序。Hash索引支持精确查找,不支持范围匹配,也不支持排序。
④B+树的叶子节点有链相连,进行一次全数据遍历的适合,B+树只需要适应O(logN)时间找到最小节点,然后通过链进行O(N)的顺序遍历即可;在找大于某个关键字或者小于某个关键字的数据的时候,B+树只需要找到该关键字然后沿着链表遍历即可。
五、Hash索引和B+树索引的区别
Hash索引 | B+树索引 |
---|---|
等值查询等快(通常下,没有Hash碰撞),不支持范围查询 | 支持等值查询和范围查询 |
不支持使用索引排序 | 支持使用索引排序 |
不支持模糊查询 | 支持模糊查询 |
不支持多列索引的最左前缀匹配 | 支持多列索引的最左前缀匹配 |
Hash索引避免不了回表查询 | B+树符合(聚簇索引、覆盖索引)时,可以只通过索引完成查询 |
性能不稳定(发生Hash碰撞,效率低) | 查询效率稳定,所有查询都是从根节点到叶子节点 |
六、索引的分类
(一)索引分类
(二)聚集索引&二级索引
1.InnoDB存储引擎中分类
在InnoDB存储引擎中,根据索引的存储形式,分为:
2.聚集索引选取规则
①如果存在主键,主键索引就是聚集索引。
②如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
③如果表没有主键,也没有合适的唯一索引,则InnoDB回自动生成一个rowid作为隐藏的聚集索引。
【无论如何,聚集索引一定存在,聚集索引只有一个】
3.聚集索引和二级索引具体结构
聚集索引的叶子节点下挂的是这一行的数据,也就是name+gender。
二级索引的叶子节点下挂的是该字段值对应的主键值。例如姓名为ARM,该叶子节点下挂的是对应ID10,也就是主键值。
4.回表查询
回表查询指的是先到二级索引中查找数据,找到主键值,然后到聚集索引中根据主键值,获取对应数据的方式。
解答:A语句的执行效率高于B语句。
A语句直接去聚集索引根据主键值ID查询即可,直接返回数据。B语句要去二级索引查找到名字ARM对应的主键值,然后返回到聚集索引去根据主键值ID去查找数据,需要进行回表查询。
InnoDB主键索引的B+树高度为多高呢?
解答:
①存储引擎中一页的大小为16K,即16×1024个字节。
②假设一行数据大小为1K,也就是叶子节点存放的数据,而一页是16K,那么一页可以存放16行这样的数据。
③假设InnoDB指针占用6个字节,主键Key(bigint)占用8个字节。
④假设树的高度为2。一页当中存放的指针(n)和主键Key(n-1)计算
n×6+(n-1)×8=16×1024,得到n为1171。有1171个指针,1170个Key。那么叶子节点存放的数据大小为,有1171个指针,每个指针指向存放数据的叶子节点对应页,一页可以存放16行数据,那么1171×16=18736行等于这个高度为2的树可以存放这么多行数据。
⑤假设树的高度为3呢,那么可以存放1171×1171×16=21939856行数据。
七、索引语法
(一)创建索引
CREATE [ UNIQUE | FULLTEXT ] INDEX index_name ON table_name (
index_col_name,... ) ;
UNIQUE代表创建的是一个唯一索引,要求该字段不能出现重复的数据。FULLTEXT代表创建的是全文索引。
(要为哪一张表中的哪个字段创建索引,table_name index_col_name)
一个索引是可以关联多个字段的,如果一个索引只关联一个字段,这个索引称为单列索引。
如果一个索引关联了多个字段,这个索引称为联合索引或组合索引。
(二)查看索引
SHOW INDEX FROM table_name ;
(三)删除索引
DROP INDEX index_name ON table_name ;
八、SQL性能优化
(一)SQL执行频率
1.相关指令
MySQL客户端连接成功后,通过show [session | global ] status命令可以提供服务器状态信息。
SHOW GLOBAL STATUS LIKE 'Com___';
GLOBAL是查看数据状态信息。
SESSION是查看当前会话状态信息。
Com_delete:删除次数
Com_insert:插入次数
Com_select:查询次数
Com_update:更新次数
2.作用
通过上述指令,可以查看当前数据库到底是以查询为主,还是以增删为主,如果以增删为主,我们就不考虑其优化,如果是查询为主,就要考虑对数据库的索引进行优化。
(二)慢查询日志
1.定义
慢查询日志记录了所有执行时间超过指定参数(Long_query_time,单位:秒,默认10秒)的所有SQL语句。【SQL语句执行时间超过10秒,就会被记录在慢查询日志当中】
2.语法
show variables like 'slow_query_log';
--MySQL的慢查询日志默认没有开启,查看系统变量
--如果要开启慢查询日志,需要在MySQL的配置文件中进行配置。
vi /etc/my.cnf
--开启MySQL慢查询日志开关
slow_query_log=1;
--设置慢查询日志的时间为2秒,SQL语句执行时间超过2秒,被视为慢查询,记录到慢查询日志中
long_query_time=2;
(三)profile详情
1.profile操作
show profiles 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。通过have_profiling参数,能够看到当前MySQL是否支持profile操作。
SELECT @@have_profiling ;
SELECT profiling;
--查看开关状态
可以通过set语句在session/global级别开启profiling
SET profiling = 1;
开关已经打开了,接下来,我们所执行的SQL语句,都会被MySQL记录,并记录执行时间消耗到哪儿去了。
执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时:
-- 查看每一条SQL的耗时基本情况
show profiles;
-- 查看指定query_id的SQL语句各个阶段的耗时情况
show profile for query query_id;
-- 查看指定query_id的SQL语句CPU的使用情况
show profile cpu for query query_id;
(四)explain执行计划
1.定义及语法
EXPLAIN 或 DESC 命令获取MySQL是如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序。
语法:
--直接在select语句之前加上关键字 explain /desc
EXPLAIN +查询语句;
2.explain各字段含义
尽量将type往前优化,不要是all。
九、索引使用
(一)最左前缀匹配原则
1.定义
如果所有是联合索引(多列索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将会部分失效(后面的字段索引失效)。
对于最左前缀法则指的是,查询时,最左变的列,否则索引全部失效,采用全局扫描。
最左前缀法则中指的最左边的列,是指在查询时,联合索引的最左边的字段(即是第一个字段)必须存在,与我们编写SQL时,条件编写的先后顺序无关。
(二)范围查询
1.定义
联合索引中,出现范围查询(>,<),范围查询右边的索引会失效。
在联合索引中,范围查询最好使用(>= , <=),这样就可以走联合索引,范围查询右侧的索引也不会失效,所有字段都是走索引的。
所以,在业务允许的情况下,尽可能的使用类似于 >= 或 <= 这类的范围查询,而避免使用 > 或 <
(三)索引失效情况
1.索引列运算
不要在索引列上进行运算操作,索引将失效。
2.字符串不加引号
字符串类型字段使用时,不加引号,索引将失效。字符串不加单引号,对于查询结果没有什么影响,但是数据库存在隐式类型转换,索引将失效。
3.模糊查询
如果仅仅是尾部模糊匹配(like ‘软件%'),索引不会失效。如果是头部模糊匹配(like ’%工程‘),索引失效。
在like模糊查询中,在关键字后面加%,索引可以生效。而如果在关键字前面加了%,索引将会失效。
4.or连接条件
用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。当or连接的条件,左右两侧字段都有索引时,索引才会生效。
5.数据分布影响
如果MySQL评估使用索引比扫描全表更慢,则不使用索引。
因为MySQL在查询时,会评估使用索引的效率与走全表扫描的效率,如果走全表扫描更快,则放弃索引,走全表扫描。 因为索引是用来索引少量数据的,如果通过索引查询返回大批量的数据,则还不如走全表扫描来的快,此时索引就会失效。(取决于表中数据的分布,查询对应的数据是极少部分,则走全表扫描。)
(四)SQL提示
1.定义
SQL提示是优化数据库的一个重要手段,在SQL语句中加入一些人为的提示来达到优化操作的目的。
2.use index
建议MySQL使用哪一个索引完成此次查询(仅仅是建议,mysql内部还会再次进行评估)。
3.ignore index
忽略指定的索引。
4.force index
强制使用指定的索引(MySQL不可以有自己的想法)。
(五)覆盖索引
1.定义
覆盖查询是指查询使用了索引,并且查询需要返回的列,在该索引中可以全部找到。(减少select *,改为具体的select id,name等)。
覆盖索引性能高一点,如果使用select * ,很容易出现回表查询的情况,性能低。
创建一个联合索引,满足覆盖索引,这样二级索引下挂的就是主键索引,可直接获得ID信息。
(六)前缀索引
1.定义
当字段类型为字符串(varchar,text,longtext等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO, 影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
2.创建语法
create index idx_xxxx on table_name(column(n)) ;
前缀长度也就是n的值,可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高, 唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
--计算选择性,选择性=1性能最好。
select count(distinct email) / count(*) from tb_user ;
--截取n个前缀
select count(distinct substring(email,1,n)) / count(*) from tb_user ;
3.查询流程
(七)单列索引与联合索引
1.定义
单列索引:一个索引只包含单个列。
联合索引:一个索引包含多个列。
2.单列索引及多列索引查询选择
在and连接查询的两个字段,各自有各自的单列索引,但是最终MySQL只会选择一个索引,只能走一个字段的索引,会进行回表查询。那么此时最好使用联合索引,无需回表查询。推荐使用联合索引!
八、添加索引的原则
- 针对于数据量较大,且查询比较频繁的表建立索引。
- 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
- 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
- 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
- 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
- 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
- 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。