(一)关于MySQL索引的几件小事
Last updated
Last updated
①MySQL官方对索引的定义是:索引(Index)是帮助MySQL高效获取数据的数据结构。 ②可以简单的理解为“排好序的快速查找数据结构”。 ③除了数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这种数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。 ④一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。 ⑤我们平常所说的索引,如果没有特别说明,都是值B+树结构组织的索引。其中聚集索引,稀疏索引、覆盖索引、复合索引、前缀索引、唯一索引,默认都使用B+树索引,统称索引。当然除了B+树之外还有哈希索引、BitMap索引等。
①类似图书的目录索引,提高了数据检索的效率,降低数据库的IO成本。 ②通过索引对数据进行排序,降低数据排序的成本,降低了CPU的消耗。
①实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引也是要占用空间的。 ②虽然索引大大提高了查询速度,同时却会降低更新表的数据,如对表进行INSERT、UPDATE、DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件;每次更新添加了索引列的字段,都会调整因为更新所带来的键值变化后的索引信息。 ③索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花费时间研究建立最优的索引。或者优化查询语句。
①单值索引:一个索引包含单个列,一个表可以有多个单列索引。 ②唯一索引:索引列的值必须唯一,但允许有空值。 ③复合索引:一个索引包含多个列。
二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
如上面的图所示,它存在的问题如下: ①如果数据太多而且大量数据单调,那么二叉搜索树就会在一条分支上一直进行延伸,这样基本就由二叉搜索树变成了线性表,搜索效率就会大大下降。 ②一个节点只能存储一个值,数据量太大的时候就会一颗高度特别高的二叉树,这样搜索效率就会出现大幅下降。 ③使用平衡二叉搜索树,虽然可以防止编程线性表,但是每次更新索引都会树的旋转,这样就会消耗特别大的性能,而且一个节点还是只能保存一个值,无法解决树过高的问题。
一棵m阶B树(balanced tree of order m)是一棵平衡的m路搜索树。它或者是空树,或者是满足下列性质的树: 1、根结点至少有两个子女; 2、每个非根节点所包含的关键字个数 j 满足:┌m/2┐ - 1 <= j <= m - 1; 3、除根结点以外的所有结点(不包括叶子结点)的度数正好是关键字总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m ; 4、所有的叶子结点都位于同一层。 在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶子结点时,该k-1个关键字正好是k个孩子包含的关键字的值域的分划。
①相比二叉树,他一个节点可以容纳多个节点,可以降低树的高度,增加查找效率。 ②但是一层能容纳的数量还是太少,数据量一大树的高度也会变得很高,搜索效率一样会进行大幅度的下降。 ③一个节点能存放的数据要比指针少一,数据保存率太低
B+ 树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+ 树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+ 树元素自底向上插入,这与二叉树恰好相反。 B+ 树在节点访问时间远远超过节点内部访问时间的时候,比可作为替代的实现有着实在的优势。这通常在多数节点在次级存储比如硬盘中的时候出现。通过最大化在每个内部节点内的子节点的数目减少树的高度,平衡操作不经常发生,而且效率增加了。这种价值得以确立通常需要每个节点在次级存储中占据完整的磁盘块或近似的大小。
如图是一个B+树,这时候如果我们要查询46这个节点,那么只需要在第一节点中进行查找,发现46在45和67之间,然后如何就走第二个节点,走到了(45,48,63)这个节点,然后发现46在45-48之间,走第一个节点,如何遍历,就找到了46。 ①使用B+树,一个节点可以存储的值和它指向一下个节点的指针一样多,可以多存储数据。 ②所有父节点的数据同时也保存在子节点之中,这样所有数据都在一起,比较连续。 ③叶子节点的所有值由一个链表进行连接,这样如果进行范围查询就非常简单,只需顺着链表往下走就行。
Hash索引是指,将要创建索引的字段值进行hash,按照hash值为key,数据地址为value,保存成为一个HashMap结构,如果一个hash值对应多个行数据,就进行链表存储。
①使用hash只需要进行一次hash就可以查找到值,不需要进行像二叉树一样的逐层查找,效率高。 ②由于数据是进行hash之后进行保存的,所以原数据在里面是没有顺序的,所以仅仅能满足 “=” 、“IN”这样的操作,不能使用范围查询 。 ③无法被用来避免数据的排序操作,因为不像B+树,它的存储是有顺序的,是按照索引值进行排序的,然后hash索引是hash后的数据,所以无法避免排序。 ④不能利用部分索引键查询,在B+树中,多个字段的索引是通过按照字段一次排序进行存储的,所以如果创建了(A,B,C)三个字段的联合索引,那么可以使用部分索引例如A,(A,B),但是hash的联合索引是所有字段一起进行hash的结果去映射的,所以部分字段无法使用。 ⑤不能避免全表扫描。因为查询出来的数据还是存在buckets中的,还是要进行表的遍历。 ⑥遇到大量hash值相等的情况后,也就是出现大量的hash碰撞后,链表就会非常长,变成线性结构,查询效率并不一定会比B+-Tree高。
BitMap索引使用不同的值进行分组,然后每组数据中将相同的值的位置标位1,不同的值的位置标为0,这样就会形成很多个不同的二进制串。
①BitMap是对数据进行位图存储,所以查询会很快。 ②由于每个值都需要一个位图去保存,所以只适用于值的种类比较少的情况,比如性别这样的字段。 ③进行数据的统计速度快。 ④锁的力度大,因为进行增删改的时候,位图的值的位置会发生变化,所以会锁住整个位图的对象。
①在InnoDB引擎中,数据本身就是索引文件,其表数据文件本身就是B+树组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这种索引被称为"聚集索引(或者聚族索引)"。 ②MyISAM引擎的索引不是聚集索引。 ③InnoDB有且只有一个聚集索引,选取规则如下: A:如果定义了主键,那么该主键就作为密集索引。 B: 如果没有定义主键,则选择该表的第一个唯一非空索引作为密集索引。 C:如果上面都不满足,InnoDB会在内部生成一个六字节的自增值作为隐藏主键,来作为密集索引。 ④密集索引文件中的每个搜索码值都对应一个索引值。 ⑤密集索引决定了数据的物理存放顺序,一个文件只能有一个物理存放顺序,所以只能有一个密集索引。 ⑥密集索引的叶子节点之间存放的该行数据。
①MyISAM引擎的所有索引都是稀疏索引。 ②稀疏索引文件只为搜索码的某些值建立索引。 ③叶子节点只保留了数据的地址或者主键,获取到了之后还要跟地址或者主键去再次进行查找。
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,那么这个索引就是"覆盖索引"。 ①覆盖索引会把要查询出来的列和索引进行对应,查询时不会进行回表操作(不会进行二次查询)。
①数据的物理存放不同。 InnoDB在文件存放中会存在两个文件,一个frm结尾的文件保存了这个表的定义文件,数据和索引都存放在一个以ibd结尾的文件中。 MyISAM在文件存放中存在三个文件,一个frm结尾的文件保存了这个表的定义文件,索引存放在一个以MYI结尾的文件中,数据存放在一个以MYD结尾的文件中。 ②索引不同 InnoDB存在一个密集索引,叶子节点保存的该列的所有值;MyISAM所有的索引都是稀疏索引,叶子节点保存的是数据的地址;InnoDB稀疏索引的叶子节点存放的是主键。 ③MyISAM缓存有表meta-data(行数等信息),所以在进行count(*)的时候会很快,InnoDB没有。 ④MyISAM强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快。 ⑤MyISAM不支持事务操作。InnoDB支持事务。 ⑥MyISAM不支持外键,二InnoDB支持。
①主键自动建立唯一索引。 ②频繁作为查询条件的字段应该创建索引。 ③查询中与其他表关联的字段,外键等应该建立索引。 ④频繁更新的字段不适合创建索引。 ⑤where条件里用不到的字段不创建索引。 ⑥在单值索引和复合索引之间,优先选择复合索引。 ⑦查询中排序的字段,应该建立索引。 ⑧查询中进行统计或者分组的字段应该建立索引。
①表记录太少的情况。 ②经常进行增删改的表。 ③数据重复且分布均匀的表字段。
使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈。 通过explain关键字可以查看如下的一些信息。 ①表的读取顺序。 ②数据读取操作的操作类型。 ③那些索引可以使用。 ④那些索引实际被使用。 ⑤表之间的引用关系。 ⑥每张表有多少行被执行器查询。
在SQL语句前面加入explain 关键字,执行该语句就可以获取信息。
①id select查询的序号列,包含一组数字,表示查询中执行select子句或操作表的顺序。 A : id相同,表示顺序由上而下执行。 B : id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。 C : id相同不同,同时存在。
②select_type 查询的类型,主要用于区别 普通查询、联合查询、子查询等的复杂查询。有下面几种值: A :SIMPLE 简单的select语句,查询中不包含子查询或者union。 B : PRIMARY 查询中若包含任何复杂的子查询,最外层查询则被标记为PRIMARY. C : SUBQUERY 在select或者where列表中包含子查询,会被标记为SUBQUERY。 D : DERIVED 在from列表中包含的子查询被标记为DERIVED(衍生),mysql会递归执行这些子查询,把结果放在临时表里。 E : UNION 若第二个select出现在union之后,则被标记为union,若union包含在from子句的子查询中,外层select被标记为DERIVED。 F : UNION RESULT 从UNION表获取结果的select。
③table 显示这一行的数据是关于那张表的。
④type 显示查询使用了何种类型,从走好到最差依次是下面顺序。 A : system :表示只有一行记录,这是const类型的特例,平时不会出现,这个可以忽略不计。 B : const : 表示通过索引一次就找到了,const用于表示primary key或者unique索引。因为只匹配一行数据,所以很快。 C : eq_ref :唯一性索引扫描,对应每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描。 D : ref :非唯一索引扫描,返回匹配某个单独值的所有行;本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到很多个符合条件的行,所以他应该属于查找和扫描的混合体。 E : range :只检索给定范围的值,使用一个索引选择行。key列显示使用了那个索引,一般就是在where语句中出现了between、<、>、in等的查询。这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。 F : index :Full Index Scan,index与All的区别为index类型只遍历索引树。这通常比All快,因为索引文件通常比数据文件少。也就是说虽然All和Index都是读全表,但index是从索引里面读,而all是从硬盘里面读。 G : 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操作中使用了覆盖索引,避免访问了表的数据行,效率不错;如果同时出现using where ,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作。
④Using where : 表明使用了where过滤。
⑤Using join buffer :表明使用了连接缓存。
⑥impossible where :where子句的值总是false,不能用来获取任何元组。
⑦select tables optimized away :在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化count(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
⑧distinct :优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。
①复合索引全值匹配最好。 ②最左前缀匹配原则。 ③不在索引列上做任何操作(计算、函数、类型转换),会导致索引失效而转向全表扫描。 ④存储引擎不能使用索引范围中范围条件右边的列(比如索引(A,B,C),如果查询条件为A=xxx and B > xx and C = xxx,则只会使用索引A,B使用了范围查询,不会使用C)。 ⑤尽量使用覆盖索引,减少select 。 ⑥mysql 在使用不等于(!= 或者 <>)的时候无法使用索引会导致全表扫描。 ⑦is null、is not null也无法使用索引。 ⑧like以通配符开头('%abc')mysql会使用全表扫描。 ⑨字符串不加单引号索引失效。 ⑩少用or,用它来连接会导致索引失效。 *建议 ①对于单值索引,尽量选择针对当前query过滤性更好的索引。 ②在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。 ③在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引。 ④尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的。
①mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如 a=3 and b=4 and c>5 and d=6,如果建立(a,b,c,d)顺序的索引,d就用不到索引;如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序是可以随意调整的。这就是最左匹配原则。 ②= 和 in 是可以乱序的,比如 a=1 and b=2 and c=3,建立(a,b,c)索引可以任意顺序,mysql查询优化器水帮助我们调整顺序。 ③为什么? 因为mysql对于联合索引的处理是,首先根据第一个字段进行排序,然后在排序的基础上再根据第二个字段进行排序,类似于group by 对多个字段进行排序,所以对于第一个字段(最左)来说是绝对有序的,但是对于后面的字段来说就是无序的了。这就是最左匹配原则的成因。
①驱动表的定义
当进行多表连接查询时, [驱动表] 的定义为: 1)指定了联接条件时,满足查询条件的记录行数少的表为[驱动表]。 2)未指定联接条件时,行数少的表为[驱动表](Important!)。
②为什么? 可以使用嵌套循环来进行类比
如果小的循环在外层,对于数据库连接来说就只连接5次,进行5000次操作,如果1000在外,则需要进行1000次数据库连接,从而浪费资源,增加消耗。这就是为什么要小表驱动大表。
③怎么做 下面结论都是针对in或exists的。 in后面跟的是小表,exists后面跟的是大表。 对于exists
select .....from table where exists(subquery);
可以理解为:将主查询的数据放入子查询中做条件验证,根据验证结果(true或false)来决定主查询的数据是否得以保留。
①order by子句,尽量使用index方式排序,避免使用filesort排序。 ②尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀原则。 ③如果不在索引列上,filesort有两种算法:mysql会启用双路排序和单路排序。 A : 双路排序:mysql4.1之前使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取 行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。简单来说就是:从磁盘取排序字段,然后在buffer中进行排序,再从磁盘取其它字段。 B : 从磁盘中读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率要快一些,避免了第二次读取数据,并且把随机IO变成了顺序IO,但它会使用更多的空间。
④优化策略
增大sort_offset_size 参数的设置。
增大max_length_for_sort_data 参数的设置。
3.group by优化
①group by实质是先排序再进行分组,遵照索引建的最佳左前缀原则。
②当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置。
③where高于having,能写在where限定的条件就不要去having限定了。