MySql优化随笔

  每次百度sql优化都是那么几个东西,此次本身来写一下本身的总结html

1、数据库是怎么存数据的

  首先你要知道数据库中的数据是怎么存储的,数据库将数据放置在一个叫作数据页的结构中,这个数据页中的数据用单链表串起来,ok,到这,我只要找到这个数据页就能遍历他,也就是能查到全部数据了,可是一个页放不了全部数据,因此呢,得在增长一个页,咱们成为页溢出,或者叫页分裂,一大堆数据得用多个页储存,多个页用双向链表串起来,这样数据就都能查到了.mysql

  光查到还不行,咱们还得更快的查到,因此数据存放就是个问题了,不能乱放,得有顺序的放,有顺序的放就会致使放的时候比较麻烦(好比像arraylist同样,插入得把后面的数据所有移动才能完成中间的插入),可是这样方便查找,因此咱们得按顺序去存放数据,可是刚才也说了咱们不能把全部数据都放到一块,因此数据放到页中,多个页散乱起来也很差管理,因此还要有一个管理页的东西,把页管理起来,天然而然就会造成一个叫树的结构,这样咱们只要有根节点在手,全部数据都能查找到了(这个根在mysql中是位置不变的,固定的),因为有序,这就是一个搜索树.sql

 

2、树

  刚才说到树,咱们听过的有二叉树,二叉搜索树,平衡二叉树,红黑树,树就是一种能更快的查找数据的数据结构,那么mysql中用的是哪一种树呢?数据库

  上面的都不是(因此上面那个图没画成二叉树),咱们知道,树的查找跟树的深度有关系,因此为了更快的查找咱们不能一个节点只能有2个儿子,咱们让他有多个,这样下来,一个瘦高的树就会变的矮胖,更加方便查找,这样的一棵树咱们称之为多路搜索树(传说中的B-树,不念B减树,就是B树,中间是杠不是减号,就是多路搜索树),B树有个特色,他的每一个节点都能储存数据,这样的话咱们要遍历树中的内容,必须使用中序遍历,这样搞就很麻烦,因而出现了B-树另一种变种,B+树,B+树比B-树更加矮胖,说明他查找效率更高,并且B+树规定因此内容必须储存在叶子节点中,就是树的最下面一层,并且这最下面一层还得用链表串起来这样咱们要遍历全部数据就更好查找了,直接遍历最下面一层的链表就好了,因此咱们的mysql用的就是这种树,叫B+树json

  具体树的数据结构能够本身去看关于数据结构的书,这里参考小灰的漫画数据结构数据结构

  B-树    https://mp.weixin.qq.com/s/rDCEFzoKHIjyHfI_bsz5Rwapp

  B+树   https://mp.weixin.qq.com/s/jRZMMONW3QP43dsDKIV9VQ函数

   

B+树示意图工具

 

2、索引

  B+树是搜索树,因此上面那些没有存值的节点,天然就相似于一个目录,这个目录,就是咱们说的索引,一个索引就对应一颗B+树测试

  数据库在存储数据时,会根据主键,创建一颗向上面同样的B+树(若是没有主键,数据库会在储存数据的时候自动给你一个隐藏列做为主键),而咱们说的优化,就是如何更好的使用这个树,这才是sql优化的关键点.

  这个树是有序的,因此你要是随便在中间插入数据,必定会调整整个的树的结构,(为了保证有序性嘛),因此咱们要知道的第一点,索引会影响插入,修改,删除的效率.

  接下来咱们说说如何利用这个树(索引)

  索引的分类

  1.聚簇索引(就是主键索引)

  表自动会建立这个索引,这个B+树,会把主键的值做为索引KEY按顺序储存,每一个叶子节点会将整个数据内容所有储存下来,也就是你一条数据里有什么,我这个叶子节点中就有什么,最慢的查询,全表扫描,就光用我这颗树就行,我啥都有

  2.非聚簇索引(二级索引)

  除了主键的这个索引,其余索引都是非聚簇索引,也就是你本身创建的那个索引,就至关于在表空间下,新创建了一个B+树,这个树跟上面不同,这个树是用你指定的列的值做为KEY按顺序储存,你指定的  列的值和主键的id 做为值储存在叶子节点中,也就是说,你要是根据我来查询,查完以后还得拿着主键id再去聚簇索引中查找一遍,才能拿到全部的值,这个过程称之为回表,那就又有一个优化点,尽可能减小回表操做

  查询非聚簇索引是顺序io / 回表是随机io,顺序io要比随机io快不少 ,若是回表查询量大,偏向于使用全表扫描,直接访问聚簇索引

  3.联合索引(这个本质仍是个二级索引)

  这颗树关键点以下:

    假如按照A/B/C三列来创建了一个联合索引,联合索引是一棵树,   分别给ABC三列创建二级索引,是三棵树

    假如按照A/B/C三列来创建了一个联合索引,会先按照A的值做为KEY,当A相同时,再按照B的值做为KEY,当B相同时,再按照C的值做为KEY的顺序来储存数据,叶子节点中数据是这三列的值和主键id

    看到这里是否是感受跟order by字段好类似,这个确实跟order by有关系

3、索引的优化

  1.索引不是万能的,不能随便加索引

  上面说索引会影响插入,修改,删除的效率,这个影响不止一点点,由于一个索引就是一棵树,你要是在某列上创建了多个索引,当修改这列时,会从新调整相关的好几颗树,效率就浪费在这里了,因此索引对查询有好处,对修改没有一点好处,不能随便创建索引

  2.能用主键作条件,就别用别的

  根据主键查询只会访问聚簇索引,并且聚簇索引中包含其余全部数据,因此说这是最快的访问方式

  3.根据列中数据的分散性,来决定是否添加索引

  列中数据越分散,经过条件筛选后留下来的数据就会越少,好比身份证号,等值查询一次匹配一个,性别,等值查询一次匹配一坨,咱们知道咱们二级索引是可能须要回表的,你查询出来一坨的时间其实并很少,可是拿着你返回的一坨id去回表,这就浪费时间了,因此,列中数据越分散,加索引后的查询效率会越高

  4.在有索引的列中少使用null,或者说少用null做为条件去查询

  由于数据库中null不等于null,因此即使是惟一约束,都没法限制多个null的插入,因此通常某列设置为惟一约束,都要限制非空,不然可能没有惟一的意义了

  5.巧用联合索引

    第一点 : 联合索引创建的顺序就是按顺序一级一级的创建,因此咱们查询的时候有个最左匹配特性,好比按照name,age,sex创建联合索引,你按照name,age,sex三列查询(顺序无所谓,查询优化器会帮你作优化)就能用上索引,你按照name,age两列也能用到索引,你按照name单列也能够,由于这些都是联合索引的所有或者局部,可是你要是按照age或者sex单列,或者age,sex两列这就不行了,由于我name是不知道是否是有序的,这时优化器可能就更偏向于使用全表扫描了

    第二点 : 咱们上面说了,联合索引的叶子节点中数据是这三列的值和主键id,若是你查询后显示的条件也是这三列,那么数据在二级索引的B+树中就有,就不会去回表了,少一步回表操做,固然更快,这叫覆盖索引

  6.利用索引覆盖

  缘由如上,同时,咱们也要求显示的列尽可能不要用*,生产环境中,须要什么查什么,不要随便写*,目的就是为了碰上覆盖索引

  7.关于排序

  单列排序能直接使用索引,可是若是是多列排序就要考虑一下了,mysql中规定规定使用联合索引的各个排序列的排序顺序必须是一致的.在多列排序中,若是要用就只能使用联合索引,而联合索引创建的B+树有特定顺序,若是sql中多列排序顺序与B+树中顺序统一,则可使用,可是若是创建了ABC联合索引,可是order by A,B desc ,C  这样就没法使用联合索引了

  mysql8的新特性,降序索引  https://www.cnblogs.com/ivictor/p/9072361.html

  8.关于模糊查询

  咱们都知道模糊查询会全表扫描,可是为何呢?由于建树的时候我是根据数据的值一个一个比较出顺序创建的树,可是你给我一个%开头的数据,好比%aaa%,那就是匹配谁均可以了啊,因此只能用全表扫描了.可是索引能够匹配列前缀,若是你给个人是 aaa% 以固定的字符串开头的数据,那我也能利用索引,由于 aaa开头的数据必定是存放在二级索引某一片连续的区域的,可是若是你要是%aaa就不行了,若是非要这样作,能够在入库时将数据的逆序字符串也存入数据库,也创建索引这样就能利用这个逆序列作 aaa%的匹配了

  9.只为用于搜索、排序或分组的列建立索引

  也就是只为  where  order by  group by 后面的列创建索引,不要为查询出来显示的列创建索引

  10.不要冗余索引

  好比咱们创建了联合索引ABC,和单列的非聚簇索引A,若是单纯查找A='aaa'的数据,其实这两个索引理论上咱们都能利用到,而联合索引的功能比单列的A的索引还广,因此彻底能够删除掉A索引,功能没有差异,还少维护一颗B+树

  11.保证索引列是以单独列的形式出现

  a = 5+3 能够利用索引, 可是 a-3 = 5 就没法利用索引了 ,由于没有一棵树中的值对应着 a-3的值,同时优化器也不会自动优化这种写法

  12.索引中的数据占用的空间尽可能小

  好比bitint效率就会高于int,由于你占用数据空间越小,表示我一样大小的地方就能多存数据,同一个节点数据越多,B+树就越矮胖,反之,就越瘦高,影响查找的层数,因此某些大字段又不须要全值匹配的,能够在索引上限制只对前多少个字符创建索引

   13. or 的使用,不要和没有索引的条件or在一块儿

  or使用起来要当心,由于有优化器的存在,好比咱们有一条语句select * from table where a>1 or b='a',这里的a有索引,而b没有索引,优化器在优化SQL时,会把没有索引的条件做为true(由于筛选要进行全表扫描才行),那么sql会被优化成为select * from table where a>1 or  TRUE,  or TRUE ,那就是true ,接着优化就是select * from table where true,因此要全表扫描,a的索引也没用了,也就是说使用到索引的搜索条件和没有使用该索引的搜索条件使用or链接起来后是没法使用该索引的.

  14.联表查询时,尽可能让数据量小的表作主表

  用小表去驱动大表,减小子表被匹配次数

  15.关于子查询的order 和 group

  子查询中order by 是没用的,不要加

  子查询中,sql中若是没有聚合函数以及HAVING子句,group by也是没有用的

  以上两个写了也会被查询优化器优化掉

  16.关于不相关子查询

  不相关就是说子查询能够单独执行,不依赖外部条件,想要优化,就按照单表去优化就行

  17.in 后面的集合不要太大

  in 后面的集合中数据若是用到索引,优化器会一个一个扫描,可是若是太大了,就会本身直接估算一个值,可能就不会用到索引了,这个阈值跟eq_range_index_dive_limit参数有关

  查询eq_range_index_dive_limit : SHOW VARIABLES LIKE '%dive%'    默认是200,也就是说集合结果超过200个,mysql会本身估算一个成本

4、查询优化器

  如今的优化器策略选择是基于成本优化,在sql真正查询以前,优化器会根据咱们sql携带的条件和表中数据量(这是一个估值),来选择使用哪一种执行计划(好比数据量会影响执行计划的选择,表中一共10条数据,某种状况下,优化器可能就不用索引,直接全表扫描了),因此你的sql具体执行的时候是使用的哪一个索引,单纯看sql是看不出来的,须要查询一下执行计划,也就是使用 EXPLAIN 关键字

  查询优化器有时候会对sql进行索引合并,既然是索引合并,就得在条件中使用多个索引,可是具体是否合并索引,得看查询优化器作出的成本分析

  索引合并(index merge) , 有三种合并索引状况

    1.intersection(取交集) , 好比  select * from table where a=? and b=?  , ab两列分别创建二级索引,正常状况是按照a筛选数据,回表,而后再回表结果中按照b筛选数据,若是使用了索引合并, 会先查询a条件中的主键,再查询b条件中的主键,而后对两份主键取交集,最后回表,这样作的好处是:索引是按照顺序排好的,因此只须要两次顺序IO,回表时随机IO便可,顺序IO贼快,目的是减小回表的操做

      (原文)使用条件:

        状况一:二级索引列是等值匹配的状况,对于联合索引来讲,在联合索引中的每一个列都必须等值匹配,不能出现只匹配部分列的状况。好比联合索引abc,条件中只用了a没有用bc,就不能索引合并

        状况二:主键列能够是范围匹配,聚簇索引和二级索引合并的时候,二级索引必须是等值匹配,二级索引值相同时,是按照主键从小到大排序的,因此能够索引合并

    2.union(取并集), , 好比  select * from table where a=? or b=?  , ab两列分别创建二级索引,正常状况是按照a筛选数据,回表,而后再回表结果中按照b筛选数据,若是使用了索引合并, 会先查询a条件中的主键,再查询b条件中的主键,而后对两份主键取并集,最后回表,这样作的好处是:索引是按照顺序排好的,因此只须要两次顺序IO,回表时随机IO便可,顺序IO贼快

      (原文)使用条件:

        状况一:二级索引列是等值匹配的状况,对于联合索引来讲,在联合索引中的每一个列都必须等值匹配,不能出现只匹配部分列的状况。好比联合索引abc,条件中只用了a没有用bc,就不能索引合并

        状况二:主键列能够是范围匹配,聚簇索引和二级索引合并的时候,二级索引必须是等值匹配,二级索引值相同时,是按照主键从小到大排序的,因此能够索引合并

        状况三:or两边的结果是intersection合并后的结果,可使用索引合并

    3.Sort-Union(先排序后,再取并集),由于union必须使用等值,以后二级索引等值才能利用二级索引中的已经排好序的主键进行取交集操做,因此若是select * from table where a>? or b<?  ,是不能直接union的,可是能够先查询a条件中的主键,再查询b条件中的主键,而后对两份主键各自排序,以后再取并集

      (原文)使用条件:

        在上面union的基础上,还须要保证查找出的主键少,不然排序就会很浪费时间,查询优化器也就不会这样合并索引了

 

 

5、单表查询的过程及速度

  根据查询优化器计算出来的方式,咱们有以下几种查询的过程

  1.const   速度为常量,这是最快的一种查询速度,通常的根据主键等值查找,或者根据惟一约束的二级索引查找,一次只查到一条,就是这么快(这时虽然会回表,可是也是会很快),可是条件中不能有跟null产生关系的

  2.ref       速度也挺快,通常是因为普通二级索引等值匹配,但因为二级索引并不限制索引列值的惟一性,因此可能找到多条对应的记录,再根据这些记录回表查找, 也就是说使用二级索引来执行查询的代价取决于等值匹配到的二级索引记录条数.若是匹配的记录较少,则回表的代价仍是比较低的,但若是匹配的记录太多了,可能会直接使用全表扫描(这中间有个叫查询优化器的东西,在sql执行以前,会在表的元数据表中先评判一下用哪一种方式查询,好比在这里,表中一共10w条数据,查询优化器估摸了一下大约有9.9w数据会被匹配到,就会直接跳过索引,使用全表扫描)

      这里关于null要作一个解释

      由于数据库中null != null,因此不管是普通的二级索引,仍是惟一二级索引,它们的索引列对包含NULL值的数量并不限制,因此咱们采用key IS NULL这种形式的搜索条件##最多##只能使用ref的访问方法,而不是const的访问方法。

 

  3.ref_or_null  比上面慢一点,由于条件中多了一些带null值的条件,可能回表次数就会偏多

  4.range    范围查询时,好比用 in  or关联的条件,速度可能就不会太快了,由于有回表操做

  5.index    使用覆盖索引时,也就是说咱们能够直接经过遍历二级索引的叶子节点的记录来比较某个条件是否成立,把匹配成功的二级索引记录的列的值直接加到结果集中就好了。因为二级索引记录比聚簇索记录小的多(聚簇索引记录要存储全部用户定义的列以及所谓的隐藏列,而二级索引记录只须要存放索引列和主键),并且这个过程也不用进行回表操做,因此直接遍历二级索引比直接遍历聚簇索引的成本要小不少

  6.all      全表扫描,直接访问聚簇索引

6、联表查询过程

    联表查询中,有主表和子表,对于主表其实跟上面查询方式同样,而后主表结果根据关联条件去循环匹配子表数据,也就是主表访问一次,子表访问屡次(看主表筛选剩下几条)  

    两张表联接查询至关于两层for循环,三张就是三层

    要想加快联表查询速度,须要优化子表查询

    1.eq_ref  相似于单表中的const,意味着关于子表查询条件上加入了主键或者惟一索引

    剩下的和单表同样

    join buffer:若是每次匹配被驱动表都从新将被驱动表加载到内存,这样的话太耗费时间了,因此提出了join buffer这么一块空间,就至关于预加载,一次加载,屡次利用,能够调节join_buffer_size这个参数的大小,来加快联表查询速度

7、EXPLAIN

  语法 : EXPLAIN {sql语句}

   

  这个表是用来显示结果的,他本质不是用来优化sql的,他的做用是指出你当前sql的一个状态,从中你能够找到下一步应该优化的点,优化sql须要用到上面说的那些方法

  id:在一个大的查询语句中每一个SELECT关键字都对应一个惟一的id,若是为null,说明是临时表

  select_type:有以下几个值

        SIMPLE: 简单查询,不包含UNION或者子查询的查询都算做是SIMPLE类型

        PRIMARY:主要的查询,对于包含UNIONUNION ALL或者子查询的大查询,主要的那个查询:好比最左侧,或者最外层查询

        UNION:不相关联接查询,对于包含UNIONUNION ALL或者子查询的大查询,除了PRIMARY,剩下的都是UNION

        DEPENDENT UNION:相关联接查询,对于包含UNIONUNION ALL或者子查询的大查询,若是其余查询都和外层的查询有关联关系,那么类型就是DEPENDENT UNION

        UNION RESULT:union关键字有去重效果,去重时会将全部数据放在一个临时表中,这个临时表类型就是UNION RESULT

        SUBQUERY:不相关子查询(mysql会将不相关子查询转成一张临时表写入内存或者硬盘,查询时可能直接将sql改写为联接查询,子表只访问一次便可)

        DEPENDENT SUBQUERY:相关子查询(mysql没法优化相关子查询,因此可能屡次访问子表)

        DERIVED:对于采用物化的方式执行的包含派生表的查询,该派生表对应的子查询的select_type就是DERIVED

        MATERIALIZED:当查询优化器在执行包含子查询的语句时,选择将子查询物化以后与外层查询进行链接查询时,该子查询对应的select_type属性就是MATERIALIZED

  type:有以下几个值

        system:若是你表的引擎是MyISAM,就是这个类型,表示精确查询

        const:

        eq_ref:

        ref:

        fulltext:全文索引

        ref_or_null:

        index_merge:索引合并

        unique_subquery:若是查询优化器决定将IN子查询转换为EXISTS子查询,并且子查询可使用到主键进行等值匹配的话,就是unique_subquery

        index_subquery:若是查询优化器决定将IN子查询转换为EXISTS子查询,并且子查询可使用到普通索引进行等值匹配的话,就是index_subquery

        range:

        index:用到了索引,可是须要将索引整个扫描一遍,能够理解为有索引时最低效率

        all

         上面没写的就是跟单表和联表中写的同样

  possible_keys:可能用到的索引,每一种可能用到的索引都会让优化器去计算一遍成本,因此尽可能减小这里的值

  key:查询时真正使用的索引

  ref:联接时用到的外键

  rows:扫描的行数,越小越好

  filtered:在联表查询中有意义,这个值是一个比例,越小越好,表示在主子表关联后扫描出的rows中,大概有多少比例的数据知足联表最后查出的结果

        好比主子表关联条件为 on  t1.key = t2.key  这个条件筛选出的rows为100行

        同时还有条件是where t1.kkeeyy = ? 这个条件从上面100行中大概只有10行

        那么filtered就是10,表示10%

   extra:额外的备注,好比你where条件是否用到了索引,是否使用了buffer  pool,都会显示在这里

 

  补充1:上面的explain关键字展现的表并不能看出成本 能够在explain和sql语句中间加上 FORMAT=JSON ,就能看到一个json数据

    EXPLAIN FORMAT=JSON {sql语句}

  补充2:使用explain语句查询以后,能够接着执行   SHOW WARNINGS  这条语句

    若是显示code=1003,那么massage就会显示mysql重写后的sql,可是这个sql不是标准sql不能执行,意会便可 

  

 

8、optimizer trace 

  经过optimizer trace能够查看优化器优化的过程

  1.首先查看优化器过程记录是否开启:默认关闭

    SHOW VARIABLES LIKE 'optimizer_trace';

  2.接着打开这个功能:

    SET optimizer_trace="enabled=on";

    SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;  # 这条命令用来设置trace可用的空间大小,若是输出的内容太多,会被截断,要看所有的数据,把这个值调大

  3.而后执行sql语句

    select ....

  4.接着查看优化过程

    SELECT * FROM information_schema.OPTIMIZER_TRACE;

    

    四列:分别是  QUERY  查询语句

            TRACE  咱们重点查看的内容,优化过程

             MISSING_BYTES_BEYOND_MAX_MEM_SIZE   因空间不足而忽略掉的文本字节数,为0表示没有忽略,>0表示trace内容显示不全,能够设置

                                  SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;

           INSUFFICIENT_PRIVILEGES    权限是否可用,0可用  1不可用

  5.关闭优化器记录

    SET optimizer_trace="enabled=off";

  这里注意一点,我测试时在sqlyog上,发现总也查不到第4步的数据,可是在命令行中执行是能够看到结果的,说明sqlyog工具会在select语句后执行其余sql,因此建议在命令行中测试(navcat没有试过,你们能够自行测试)

  

  trace内容大体分为3个阶段,准备阶段,优化阶段(挨个索引成本分析,分析可用仍是不可用),执行阶段(最终阶段)

  准备阶段(join_preparation)   :   就是你那条sql语句

  优化阶段(join_optimization)  :   转化条件(将一些无用的条件,位置不对的条件【好比on后面的非关联关系的条件会被放到where后】删除或者转移位置 ),分析每张表的每个索引是否可用(因此我认为若是索引太多,会增长分析过程),预估不一样单表的访问成本,尝试增长一些其余辅助条件

  执行阶段(join_execution)      :   最终选择的执行结果

9、写在后面

  参考文章 : 掘金小册中的<<MySql是怎样运行的:从根上理解MySql> >

  做者 文章写的仍是很详实的,经过阅读他的这本小册,写了上边的这篇随笔,这本小册读完让我对mysql中的一些原理又略知了三四,可是本人对于小册中不少细节还未能吸取,因此上面的文字描述并非相似于教学同样教读者怎么作,权当本身的一篇随笔,记录一些关键知识点,简单串联了一下,若是有缘能看到这篇文章,建议能够去我提供的连接或者本身搜索其中知识点,已得到更加清晰的了解.

  这篇文章能起到抛砖引玉的做用,足矣

相关文章
相关标签/搜索