当前位置: 首页 > 后端技术 > Java

MySql索引分析与查询优化

时间:2023-04-01 22:09:40 Java

B-Tree核心特性:多路,非二叉树每个结点同时保存索引和数据查找时,相当于二分查找B+Tree核心特性多路非二叉只保存叶子节点查找数据时,相当于二分查找在相邻节点添加指针。B-TreeVSB+TreeB+树查询的时间复杂度固定为logn,最好的B-tree查询复杂度为O(1)。B+树的相邻节点的指针可以大大增加区间的可达性,可以用于范围查询等,而B树的每个节点的key和data在一起,区间搜索不能执行。B+树更适合外存,即磁盘存储。由于中间节点没有数据字段,每个节点可以索引更大更精确的范围。MySQLInnoDB数据存储:数据结构按照B+树存储,本身就是一个索引,也称为聚簇索引键作为主键。InnoDB要求表必须有主键。如果不指定,则自动选择唯一标识记录作为主键。如果不存在,则自动创建普通索引:叶子节点存储的是主键,而不是行的物理地址,需要两次检索,(1)检索主键(2)根据检索数据在主键上并存储主键。优点:分页或数据移动时无需更改主键原因:每个索引都会存储,主键太大浪费空间。最好对主键进行排序,以减少索引维护开销。MySQLInnoDB页面大小:显示像“innodb_page_size”这样的变量;lookfordatawithkeywords=8:根页在表空间中的位置是固定的。根页加载到内存中,找到指针P6P6指向的页并加载到内存中。二分查找在InnoDB中找到8个高度为3的B+树存储:假设数据表中一行数据为1K。假设主键ID是bigint类型,长度为8字节,InnoDB源码中设置指针大小为6字节,这样一共1占14字节。一个页存储的指针数(索引数):16KB(16*1024=16384字节)16384/14=1170高度为2的B+树可以存储1170*16=18720条数据记录。一棵高度为3的B+树可以存储:1170(索引个数)1170(索引个数)16(每页行数)=21902400(2000万)条这样的记录高性能索引策略:独立列:索引A列不能是表达式的一部分,也不能是函数的参数。如:selectx,y,zfromtablewherex+1=2;如果x是一个索引,那么上面的SQL不能使用索引,应该写成:selectx,y,zfromtablewherex=1;indexselectivity:no重复索引的个数(X,X<=T)与数据表中记录总数(T)的比值,范围在X/T到1之间。索引的选择性越高索引越大,查询效率越高。唯一索引的X=T的选择性为1,所以唯一索引的性能最好。前缀索引:对于TEXT或者VARCHAR类型的列,当该列的值的长度很大,必须要用于查询时,必须使用该列的前几位作为索引,即前缀索引,因为整列B+tree用value作为索引时会占用非常大的空间,不方便查找。前缀索引的制定原则:前缀索引的选择性需要接近整列的选择性,这样才不会对性能造成太大的影响,同时不能太长占用太多的空间。如何找到最好的前缀索引?假设:表中有某列名为testcol,类型为varchar(100)计算完整列的选择性:SELECTCOUNT(DISTINCTtestcol)/COUNT(*)FROMtable;计算前缀长度x的选择性:SELECTCOUNT(DISTINCTLEFT(testcol,x))/COUNT(*)FROMtable;改变x的值来计算不同前缀的选择性,最后在多个值中,考虑选择性接近度和前缀长度这两个方面,可以选择更合适的前缀索引。多列索引selectx,y,zfromtablewherex=1andy=1;Mysql执行查询时,如果使用多列索引键(x,y),会先查询set中匹配第一列索引的数据,再查询这部分数据中匹配第二列的数据set等,这样就可以在不扫描数据的情况下选择数据;而如果将一个多列索引拆分为多个单列索引(key(x),key(y)),Mysql在执行查询时只会选择限制性最强的一个索引使用,其他索引会被浪费掉,所以在上述情况下,多列索引的性能更好。Indexorderselectx,y,zfromtablewherex=1andy=1;x=1andy=1ory=1andx=1?将选择性高的索引列放在前面;索引列根据选择从高到低放置覆盖索引。如果一个索引包含了所有需要查询的字段的值,就称为“覆盖索引”。InnoDB存储引擎使用聚集索引,覆盖索引可以避免回表查询。因为InnoDB的二级索引的B-Tree的叶子节点存储了对应的一级索引,如果二级索引覆盖了要查询的值,就会少一次使用一级索引的查询,提高效率。当发起索引覆盖查询时,在执行计划的Extra栏中可以看到“Usingindex”信息。索引冗余当有一个键(a,b)索引时,创建另一个键(a)是多余的,因为它只是多列索引的前缀。但是当key(b)被创建的时候,它并不是冗余索引,因为上面提到的多列索引不能单独作为一个索引来查询索引相关的问题索引越多越好吗?数据更新时需要维护索引,带来开销,按需创建。哪些列适合索引?对于选择性高的列,一些常量和枚举字段(如:性别)不适合建立索引。低选择性还增加了维护成本,这是得不偿失的。索引字段类型?自增字段,当表比较大时,uuid等不规则字段不适合作为主键MySQL执行计划select_typeidselect_typedescription1SIMPLE不包含任何子查询或联合查询2PRIMARY包含子查询,最外层查询显示为PRIMARY3SUBQUERYinselect或where子句中包含的查询4DERIVEDfrom子句中包含的Query5UNION出现在union之后的查询语句中6UNIONRESULT从UNION中获取结果集typetypedescriptionALL扫描全表数据,不使用索引index来遍历索引,扫描索引中的所有数据,如:selectcount(*)fromtableArangeindexrangesearchindex_mergeindexmerge,多个单列索引,如andcondition,orconditionindex_subquery在子查询中使用reunique_subquery在子查询中使用eq_refref_or_nullsubquerytoindexNulloptimizedreffulltext使用全文索引,分词搜索ref使用非唯一索引查找数据,与eq_ref类似,区别在于索引非唯一eq_ref在join查询中使用PRIMARYKEY或UNIQUENOTNULL索引关联。const使用主键或唯一索引,匹配结果只有一条记录。systemconst连接类型的特例,查询的表是系统表,往往不需要磁盘IO。possible_keys:可能用到的索引,注意也可以不用。如果查询涉及的字段有索引,则列出索引。当列为NULL时,需要考虑当前SQL是否需要优化。key表示MySQL在查询中实际使用的索引,如果没有使用索引,则显示为NULL。TIPS:如果在查询中使用了覆盖索引(覆盖索引:被索引的数据覆盖了所有要查询的数据),该索引只出现在key列表中。当select_type为index_merge时,这里可能有两个以上的索引,其他select_type只会出现一个key_length:索引字段长度char(),varchar()索引长度计算公式:(CharacterSet:utf8mb4=4,utf8=3,gbk=2,latin1=1)*columnlength+1(nullallowed)+2(variablelengthcolumn)int索引长度计算公式:4+1(nullallowed)extra:额外的信息很丰富,常见的有:UsingindexUsingcoveringindexUsingwhere使用where子句过滤结果集Usingfilesort使用文件排序,发生在使用非索引列进行排序时,性能消耗很大,应该尽可能优化。Usingtemporary使用临时表。SQL优化建议SQL语句不要写的太复杂:一个SQL语句尽量简单,不要嵌套太多层。使用“临时表”缓存中间结果:简化SQL语句的一个重要方法是使用临时表临时存储中间结果,可以避免程序中多次扫描主表,大大减少阻塞,提高并发性能。使用like时要注意是否会造成全表扫描:有时需要进行一些模糊查询,比如selectidfromtablewhereusernamelike'%abc%'。关键字%abc%,由于abc前面使用了“%”,查询将使用全表扫描。除非必要,不要在关键字前加%,尽量避免使用notin、!=或<>操作符:在where语句中使用notin、!=或<>,引擎将放弃使用索引并执行全表扫描。尽量避免用or来连接条件:对于用or分隔的条件,如果or之前的列有索引,但后面的列没有索引,则不会使用涉及的索引。尽量避免在where子句中使用or连接条件,否则引擎会放弃使用索引,进行全表扫描,如:假设num1有索引,num2没有索引,查询语句selectidfromtwherenum1=10ornum2=20会放弃使用索引,你可以这样查询:selectidfromtwherenum1=10unionallselectidfromtwherenum2=20,这样虽然num2不使用索引,至少num1会使用索引,尽可能提高效率数值型字段:尽量不要将只包含数值信息的字段设计为字符类型,这样会降低查询和连接性能,增加存储开销。尽量不要让字段的默认值为NULL:在MySQL中,包含空值的列很难针对查询进行优化,因为它们会使索引、索引统计和比较操作变得更加复杂。索引将不包含具有NULL值的列。只要列包含NULL值,它们就不会包含在索引中。只要复合索引中有一列包含NULL值,那么该列对于复合索引就是无效的。所以我们在设计数据库的时候尽量不要让字段的默认值为NULL,应该把列指定为NOTNULL,除非你要存储NULL。您应该用0、特殊值或空字符串替换空值。如果列类型是字符串,那么一定要记得在where条件中把字符常量值用引号括起来,否则即使这个列上有索引,MySQL也不会使用它,因为MySQL会将输入的常量转换默认值检索稍后执行。如:select*fromt_studentwherestd_name=3;谨慎使用insertintoselect。语句:insertintotableAselect*fromtableBwheredate_time>'2020-07-31'问题分析:该语句会导致tableB逐渐被锁定,无法进行其他操作。解决方法:给data_time字段加索引,索引空值问题,唯一索引空值,唯一索引中允许多行有NULL值的数据,联合唯一索引中有空值,会失去唯一性,比如uniquekey(email,phone),如果phone为空,就会存在多条相同email的记录。要检索NULL值,只能使用isnull/isnotnull/<=>。不能使用=、<、>等运算符。Normalindexnullvalues:空值Existence仍然可以遍历索引