zoukankan      html  css  js  c++  java
  • sql优化基础篇

    优化的步骤:

      0.先sql运行看看是否真的很慢,注意设置SQL_NO_CACHE
      1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
      2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
      3.order by limit 形式的sql语句让排序的表优先查
      4.了解业务方使用场景
      5.加索引时参照建索引的几大原则
      6.观察结果,不符合预期继续从0分析

    对优化步骤的每步细分:

      1.对于0步骤,没什么好说的,至于什么是SQL_NO_CACHE,百度有很好的答案。

      2.对于1步骤,虽然很长,但是仔细读前面的也很好理解,其中的区分度,简单说下,如下图:

    select count(*),sync_status from stage_poi  group by sync_status;
    +----------+-------------+
    | count(*) | sync_status |
    +----------+-------------+
    |     3080 |           0 |
    |  3085413 |           3 |
    +----------+-------------+
    此表中,sync_status 列在整个表中,只有 0 和 3 这 2种状态值,在整个300万的数据中,区分度很低。怎么说,如果这个时候 sync_status=3 那么接下来要在300万的数据中查找数据。咋样的区分度才高呢,理想条件下300万数据的 sync_status都不同
    查表是很好找数据。

    3.对于2步骤,什么是执行计划。这里详细说下。

    Explain语法

    EXPLAIN  SELECT ……
    变体:
    1. EXPLAIN EXTENDED SELECT ……
    将执行计划“反编译”成SELECT语句,运行SHOW WARNINGS 可得到被MySQL优化器优化后的查询语句 
    2. EXPLAIN PARTITIONS SELECT ……
    用于分区表的EXPLAIN
    如:
    只需在查询语句前加
    EXPLAIN 运行即可。

    其中 允许执行计划后包含的信息

    分别来说下都是代表什么意思:

    id

    包含一组数字,表示查询中执行select子句或操作表的顺序

    id相同,执行顺序由上至下

    如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

    id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

    select_type 

    表示查询中每个select子句的类型(简单 OR复杂)
     

    a.SIMPLE:查询中不包含子查询或者UNION
    b.查询中若包含任何复杂的子部分,最外层查询则被标记为:PRIMARY
    c.在SELECT或WHERE列表中包含了子查询,该子查询被标记为:SUBQUERY
    d.在FROM列表中包含的子查询被标记为:DERIVED(衍生)
    e.若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在  FROM子句的子查询中,外层SELECT将被标记为:DERIVED
    f.从UNION表获取结果的SELECT被标记为:UNION RESULT
     

    type

     
    表示MySQL在表中找到所需行的方式,又称“访问类型”,常见类型如下:
     

    由左至右,由最差到最好
     
    a.ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
     

    b.index:Full Index Scan,index与ALL区别为index类型只遍历索引树
     

    c.range:索引范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于between、<、>等的查询

    range访问类型的不同形式的索引访问性能差异

    d.ref:非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找
     

     
    e.eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

     
    f.const、system:当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量

    system是const类型的特例,当查询的表只有一行的情况下, 使用system
     
     
    g.NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引
     

    possible_keys

     
    指出MySQL能使用哪个索引在表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
     

    key

    显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL
    TIPS:查询中若使用了覆盖索引,则该索引仅出现在key列表中

    key_len

    表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度

    key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
     

    ref

    表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
     

    本例中,由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 ’ac’
     
     
    rows
    表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
     

    Extra

    包含不适合在其他列中显示但十分重要的额外信息
     
    a.Using index
     
    该值表示相应的select操作中使用了覆盖索引(Covering Index)
     

    TIPS:覆盖索引(Covering Index)
     
    MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件
     
    包含所有满足查询需要的数据的索引称为 覆盖索引(Covering Index)
     
    注意:
    如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降
     
    b.Using where
     
    表示MySQL服务器在存储引擎受到记录后进行“后过滤”(Post-filter),
    如果查询未能使用索引,Using where的作用只是提醒我们MySQL将用where子句来过滤结果集
     

    c.Using temporary
     
    表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
     

    d.Using filesort
     
    MySQL中无法利用索引完成的排序操作称为“文件排序”
     

    4.步骤3和4都好理解

    5.重点来说说5步骤。

    先来简单介绍下什么是索引:

    MySQL索引原理

    ##索引目的
    索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者ze开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?

    ##索引原理
    除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的目录等。它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。
    数据库也是一样,但显然要复杂许多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段......这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。

    ###磁盘IO与预读
    前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考:
    various-system-software-hardware-latencies
    考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。

    ###索引的数据结构
    前面讲了生活中索引的例子,索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。

    ###详解b+树
    b+树
    如上图,是一颗b+树,关于b+树的定义可以参见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并不真实存在于数据表中。

    ###b+树的查找过程
    如图所示,如果要查找数据项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,显然成本非常非常高。

    ###b+树性质
    1.通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。
    2.当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。

    慢查询优化

    关于MySQL索引原理是比较枯燥的东西,大家只需要有一个感性的认识,并不需要理解得非常透彻和深入。我们回头来看看一开始我们说的慢查询,了解完索引原理之后,大家是不是有什么想法呢?先总结一下索引的几大基本原则

    建索引的几大原则

    1.最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
    2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
    3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录
    4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
    5.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可



  • 相关阅读:
    《C++标准程序库》 第6章 STL Container
    《C++语言99个常见编程错误》
    单例模式
    《C++标准程序库》 第7章 Iterator Adapters
    Shell颜色封装(C++)
    《改善C++程序的150个建议》
    OpenCV之图片的创建、保存和复制
    XMLDOM对象方法:对象事件
    三国中最精辟的十句话
    中国十大名茶及鉴别方法
  • 原文地址:https://www.cnblogs.com/daishiwen/p/8760764.html
Copyright © 2011-2022 走看看