目录
- 一、索引介绍
- 二、MySQL索引结构
- 二、索引优化
一、索引介绍
1、索引简介
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
可以得到索引的本质:索引是数据结构。索引的目的在于提高查询效率,就好像我们平常使用的字典一样。我们可以简单理解为索引是排好序的快速查找结构:
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,Col1 是数据记录的物理地址,Col2 是记录的数据,为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上.
我们平常所说的索引,如果没有特别指明,都是指B+树结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
2、索引的优缺点
优点
- 类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗
缺点
- 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的
- 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息
- 索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句
3、MySQL索引分类
- 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引
- 唯一索引:索引列的值必须唯一,但允许有空值
- 复合索引:即一个索包含多个列
4、索引相关语句
-
创建:
CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length)); ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length));
-
删除:
DROP INDEX [indexName] ON mytable;
-
查看:
SHOW INDEX FROM table_name\G;
-
使用 ALTER 命令:
-- 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL ALTER TABLE tbl_name ADD PRIMARY KEY (column_list); -- 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次) ALTER TABLE tbl_name ADD UNIQUE index_name (column_list); -- 添加普通索引,索引值可出现多次 ALTER TABLE tbl_name ADD INDEX index_name (column_list); -- 该语句指定了索引为 FULLTEXT ,用于全文索引 ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);
二、MySQL索引结构
1、MySQL 的索引结构包含有:
- B/B+Tree索引
- Hash索引
- full-text全文索引
- R-Tree索引
2、B+Tree 索引的检索原理
B+Tree索引的检索原理图如下:
【初始化介绍】
一颗b+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3。
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点,即3、5、9、10、13、15、28、29、36、60、75、79、90、99,非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
【查找过程】
- 如果要查找数据项为29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO;
- 在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO;
- 29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
三、索引优化
1、索引创建场景
需要创建索引的情况
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其它表关联的字段,外键关系建立索引
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或者分组字段
- 单值/组合索引的选择问题:在高并发下倾向创建组合索引
不需要创建索引的情况
- 表记录太少
- 经常增删改的表:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件
- 频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引,加重了IO负担
- Where条件里用不到的字段不创建索引
- 数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引(如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果)。
2、避免索引失效
- 尽量全值匹配,对于字符串来说,全值匹配时命中索引几率是最高的
- 遵循最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
- 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
- 避免出现存储引擎不能使用索引中范围条件右边的列
- 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少使用 select *
- mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
- is null,is not null 也无法使用索引
- like 如果以通配符开头(‘%abc…’),会导致mysql索引失效,而变成全表扫描的操作
- 字符串不加单引号索引失效
- 少用or,用它来连接时会索引失效
3、索引优化建议
- 永远小表驱动大表,即小的数据集驱动大的数据集
- 对于单键索引,尽量选择针对当前query过滤性更好的索引
- 在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
- 在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引
- 尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的
- 少用 Hint 强制索引
4、关键命令优化
1、order by关键字优化
- ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序。MySQL支持二种方式的排序,FileSort和Index,Index效率高,因为它指示MySQL扫描索引本身完成排序,而FileSort方式效率较低。
- ORDER BY 满足两情况,会使用Index方式排序:
- 1、ORDER BY 语句使用索引最左前列;
- 2、使用Where子句与Order BY子句条件列组合满足索引最左前列。
- 尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
- 如果不在索引列上,filesort 有两种算法:双路排序和单路排序
- 双路排序:MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
- 单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
- 优化策略:
- 增大max_length_for_sort_data参数的设置:不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的
- 增大sort_buffer_size参数的设置:提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率
2、GROUP BY关键字优化
- group by 实质是先排序后进行分组,遵照索引建的最佳左前缀
- 当无法使用索引列,增大 max_length_for_sort_data 参数的设置 + 增大 sort_buffer_size 参数的设置
- where高于having,能写在where限定的条件就不要去having限定了。