zoukankan      html  css  js  c++  java
  • SQL优化的一些总结 SQL编写一般要求

    SQL编写一般要求
    ---SQL语句尽可能简单
    ---分解联接保证高并发
    ---同数据类型的列值比较
    ---不在索引列做运算
    ---禁止使用SELECT *
    ---避免负向查询和%前缀模糊查询
    ---保持事务(连接)短小
    ---改写OR为IN()
    ---改写OR为UNION
    ---LIMIT高效分页
    ---用UNION ALL而非 UNION
    ---GROUP BY 去除排序

    SQL语句尽可能简单

    l 大SQL VS 多个简单SQL
    Ø  传统设计思想
    Ø  BUT MySQL NOT
    Ø  一条SQL只能在一个CPU运算
    Ø  5000+ QPS的高并发中,1秒大SQL意味着?
    Ø  可能一条大SQL就把整个数据库堵死
    l  拒绝大SQL,拆解成多条简单SQL
    Ø  简单SQL缓存命中率更高
    Ø  减少锁表时间
    Ø  用上多CPU

    分解联接保证高并发

    l  高并发DB不建议进行两个表以上的JOIN
    l  适当分解联接保证高并发
    Ø 可缓存大量早期数据
    Ø 对大表的ID使用IN函数
    Ø 联接会引用同一个表多次
    l  举例

    MySQL> Select post_name from tag JOIN tag_post

                   on tag_post.tag_id=tag.id JOIN post

                   on tag_post.post_id=post.id

                   WHERE tag.tag='二手玩具';

    MySQL> Select tag_id from tag WHERE tag='二手玩具';

    MySQL> Select post_id from tag_post WHERE tag_id=1321;

    MySQL> Select post_name from post WHERE post.id in (123,456,314,141);

    同数据类型的列值比较

    l原则:数字对数字,字符对字符
    l数值列不字符类型比较
    Ø  同时转换为双精度
    Ø  进行比对
    l字符列不数值类型比较
    Ø  字符列整列转数值
    Ø  不会使用索引查询
     

    不在索引列做运算

    l 不在索引列进行数学运算或凼数运算
    Ø  无法使用索引
    Ø  导致全表扫描
    l 举例

     

    禁止使用SELECT *

    l用SELECT * 时
    Ø更多消耗CPU、内存、IO、网络带宽
    Ø先向数据库请求所有列,然后丢掉不需要列?
    l尽量不用SELECT *,叧取需要数据列
    Ø更安全的设计:减少表变化带来的影响
    Ø为使用covering index提供可能性
    ØSelect/JOIN减少硬盘临时表生成,特别是有TEXT/BLOB时
    l举例

    SELECT * FROM tag WHERE id = 999184;

    SELECT keyword FROM tag WHERE id = 999184;

    避免负向查询和%前缀模糊查询

    l避免负向查询
    ØNOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等
    l避免%前缀模糊查询
    ØB+ Tree
    Ø使用不了索引
    Ø导致全表扫描
    l举例

     MySQL> select * from post WHERE title like '北京%' ;

     298 rows in set (0.01 sec)

     MySQL> select * from post WHERE title like '%北京%' ;

     572 rows in set (3.27 sec)

    保持事务(连接)短小

    保持事务/DB连接短小精悍

    Ø  事务/连接使用原则:即开即用,用完即关
    Ø  与事务无关操作放到事务外面, 减少锁资源的占用
    Ø  不破坏一致性前提下,使用多个短事务代替长事务
     

    改写OR为IN()

    l同一字段,将or改写为in()
    Ø OR效率:O(n)
    Ø IN 效率:O(Log n)
    Ø 当n很大时,OR会慢很多
    l注意控制IN的个数,建议n小于300
    l举例

    Select * from opp WHERE phone='12347856' or phone='42242233' ;

    Select * from opp WHERE phone in ('12347856' , '42242233') ;

    改写OR为UNION

    l不同字段,将or改为union
    Ø  减少对不同字段进行 "or" 查询
    Ø  Merge index往往很弱智!
    l举例

    Select * from opp WHERE phone='010-88886666' or cellPhone='13800138000';

    Select * from opp WHERE phone='010-88886666'

    union

    Select * from opp WHERE cellPhone='13800138000';

    LIMIT高效分页(一)

    l  传统分页:
    Ø  Select * from table limit 10000,10;
    lLIMIT原理:
    Ø  Limit 10000,10
    Ø  偏移量越大则越慢
    l推荐分页:

    Select * from table WHERE id>=23423 limit 11; #10+1 (每页10条)

    select * from table WHERE id>=23434 limit 11;

    LIMIT高效分页(二)

    l 分页方式二:

    Select * from table WHERE id >= ( select id from table limit 10000,1 ) limit 10;

    l 分页方式三:

    SELECT * FROM table INNER JOIN (SELECT id FROM table LIMIT 10000,10) USING (id) ;

    l 分页方式四:
    Ø  程序取ID:select id from table limit 10000,10;
    Ø  Select * from table WHERE id in (123,456…) ;
    l 可能需按场景分析并重组索引
     

    LIMIT高效分页(三)

    l 示例

    MySQL> select sql_no_cache * from post limit 10,10;

    10 row in set (0.01 sec)

    MySQL> select sql_no_cache * from post limit 20000,10;

    10 row in set (0.13 sec)

    MySQL> select sql_no_cache * from post limit 80000,10;

    10 rows in set (0.58 sec)

    MySQL> select sql_no_cache id from post limit 80000,10;

    10 rows in set (0.02 sec)

    MySQL> select sql_no_cache * from post WHERE id>=323423 limit 10;

    10 rows in set (0.01 sec)

    MySQL> select * from post WHERE id >=

    ( select sql_no_cache id from post limit 80000,1 ) limit 10 ;

    10 rows in set (0.02 sec)

     

    用UNION ALL而非 UNION

    l若无需对结果进行去重,则用UNION ALL
    ØUNION有去重开销
    l举例

    MySQL> SELECT * FROM detail20091128 UNION ALL

                   SELECT * FROM detail20110427 UNION ALL

                   SELECT * FROM detail20110426 UNION ALL

                   SELECT * FROM detail20110425 UNION ALL

                   SELECT * FROM detail20110424 UNION ALL

                   SELECT * FROM detail20110423;

    GROUP BY 去除排序

    lGROUP BY 实现
    Ø  分组
    Ø  自动排序
    l无需排序:Order by NULL
    l特定排序:Group by DESC/ASC
    l举例

    MySQL> select phone,count(*) from post group by phone limit 1 ;

    1 row in set (2.19 sec)

    MySQL> select phone,count(*) from post group by phone order by null limit 1;

    1 row in set (2.02 sec)

     
    ----------------------------------------------------------------

    http://www.taobaodba.com/html/851_sql%E4%BC%98%E5%8C%96%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93.html SQL的优化是DBA日常工作中不可缺少的一部分,记得在学生时期,曾经在ITPUB上看到一篇帖子,当时楼主在介绍SQL优化的时候,用一个公式来讲解他在做sql优化的时候遵循的原则:

              T=S/V(T代表时间,S代表路程,V代表速度)

    S指SQL所需访问的资源总量,V指SQL单位时间所能访问的资源量,T自然就是SQL执行所需时间了;我们为了获得SQL最快的执行时间,可以根据公式定义上去反推:

    1. 在S不变的情况下,我们可以提升V来降低T:通过适当的索引调整,我们可以将大量的速度较慢的随机IO转换为速度较快的顺序IO;通过提升服务器的内存,使得将更多的数据放到内存中,会比数据放到磁盘上会得到明显的速度提升;采用电子存储介质进行数据存储和读取的SSD,突破了传统机械硬盘的性能瓶颈,使其拥有极高的存储性能;在提升V上我们可以采用较高配置的硬件来完成速度的提升;
    2. 在V不变的情况下,我们可以减小S来降低T:这是SQL优化中非常核心的一个环节,在减小S环节上,DBA可以做的可以有很多,通常可以在查询条件中建立适当的索引,来避免全表扫描;有时候可以改写SQl,添加一些适当的提示符,来改变SQL的执行计划,使SQL以最少的扫描路径完成查询;当这些方法都使用完了之后,你是否还有其他方案来优化喃?在阿里系的DBA职位描述中有条就是要求DBA需要深入的了解业务,当DBA深入的了解业务之后,这个时候能站在业务上,又站DB角度上考虑,这个时候在去做优化,有时候能达到事半功倍的效果。

    案例一:通过降低S,来提升T

    原理介绍:
    我们知道B+索引叶子节点的值是按照索引字段升序的,比如我们对(nick,appkey)两个字段做了索引,那么在索引中的则是按照nick,appkey的升序排列;如果我们现在的一条sql:
    select count(distinct nick) from xxxx_nickapp_09_29;
    用于查询统计某天日志表中的UV,优化器选择了该表上索引ind_nick_appkey(nick,appkey)来完成查询,则开始从nick1开始一条条扫描下来,直到扫描到最后一个nick_n,那么中间过程会扫描很多重复的nick(最左边普通扫描),如果我们能够跳过中间重复的nick,则性能会优化非常多(最右边的松散扫描):

    从上面的可以得到一个结论:

    如果这条统计uv的sql能够按照右边的loose index scan的方式来扫描话,会大大的减小我们上面提到的S;所以需要通过改写sql来达到伪loose index scan:(MySql优化器不能直接的对count(distinct column)做优化)

    root@DB 09:41:30>select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;
    +———-+
    | count(*) |
    +———-+
    | 806934 |
    +———-+
    Sql内查询中先选出不同的nick,最后在外面套一层count,就可以得到nick的distinct值总和;
    最重要的是在子查询中:select distinct(nick) 实现了上图中的伪loose index scan,优化器在这个时候的执行计划为Using index for group-by ,这样mysql就把distinct优化为group by,首先利用索引来分组,然后扫描索引,对需要的nick只扫描一条记录。

    真实案例:

    该案例选自我们的一个线上的生产系统,该系统每天有大量的日志数据入库,单表的容量在10G-50G之间,然后做汇总分析,计算日志数据中的uv就是其中一个逻辑,sql如下:

    select count(distinct nick) from xxxx_nickapp_09_29;

    即使在_xxxx分表上加上nick的索引,通过查看执行计划,为全索引扫描,由于单表的数据量过大,sql在执行的时候,会对整个服务器带来抖动,需要对原来的SQL进行改写,使其支持loose index scan;

    优化前:

    root@DB 09:41:30>select count(distinct nick) from xxxx_nickapp_09_29;
    +———-+
    | count(*) |
    +———-+
    | 806934 |

    1 row in set (52.78 sec)
    执行一次sql需要花费52.78s

    优化后:

    root@DB 09:41:30>select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;

    +———-+
    | count(*) |
    +———-+
    | 806934 |
    +———-+
    1 row in set (5.81 sec)

    由52.78秒降至5.81秒,速度提升了差不多10倍;

    查看SQL的执行计划:

    优化写法:

    root@DB 09:41:30>explain select count(*) from ( select distinct(nick) from xxxx_nickapp_09_29)t ;

    +—-+————-+——————————+——-+—————+———————————+———+—–
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +—-+————-+——————————+——-+—————+———————————+———+—–
    | 1 | SIMPLE | xxxx_nickapp_09_29 | range | NULL |ind_nick_appkey | 67 | NULL | 2124695 |Using index for group-by |
    +—-+————-+——————————+——-+—————+———————————+———+—–
    原始写法:
    root@DB 09:41:50>explain select count(distinct nick) from xxxx_nickapp_09_29;
    +—-+————-+——————————+——-+—————+—————————-+———+——+–
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +—-+————-+——————————+——-+—————+—————————-+———+——+–
    | 1 | SIMPLE | xxxx_nickapp_09_29 | index | NULL | ind_nick_appkey | 177 | NULL | 19546123 |Using index |
    +—-+————-+——————————+——-+————–+—————————-+———+——+–

    可以看到我们的路程由19546123减小到2124695,减小了9倍多.^_^

    案例二:结合业务递增的写入特点,巧妙优化UV统计count(*)

    有时候觉得,优化一条sql的最高境界就是让这sql能够从把这条从系统中拿掉,不管怎样,这些都是建立在你足够的了解业务上,就能够推动一些业务产品的升级或者下线,这样的DBA你能做到吗?

    下面看一个案例:应用每天都会对入库的分表统计一个总数:select count(*) from xx_01_01;
    随着单表的数据量越来越大(单表在20G左右),每次进行count的时候,速度越来越慢,同时需要扫描较多的数据页块,导致整个数据库性能的抖动,通过分析业务的特点,由于每张表采用自增id的方式进行插入,并且没有数据的删除,所以统计全表的总数就可以变通一下:

    所以这条sql:select count(*) from xx_01_01;
    可以变通为: select max(id)-min(id)+1 from xx_01_01;
    执行速度可以得到质的飞跃 ^_^.

    案例三:通过提升V,来降低T—随机IO  VS  顺序IO

                在前面我们提到,提升V的一些方法,通常可以采用提升服务器硬件的方式来达到,但是很多中小型企业来说,现在比较高的成本对于他们来说还是望尘莫及,同时没有成熟的使用经验,对于他们可能还是一件坏事情。总的来说,你的服务器硬件无论在牛,如果SQL写的烂,索引建的不好,那还是不行的。

    真实线上案例:在我们的一个核心产品库上,承载着非常大量的随机读,就叫它读库好了。一天读库的load非常的高,通过慢日志发现,有一条sql频繁的出现在慢日中,这条sql的查询条件很复杂,同时该表上的类似相同的索引也非常的多,当时是怀疑索引走错,通过explain 来查看SQL的执行计划:发现执行计划中的using where代表查询回表了,同时由于回表的记录rows较大,所以带来了大量的随机IO:

    所以我们只需要在原来的索引冗余掉is_detail字段就可以通过覆盖索引的方法优化掉该sql,避免了查询回表而导致的随机io,用顺序io替换了原来的随机io,SQL的执行速度得到极大提升:(下图会去掉is_detail字段的测试) 

    总结:SQL优化是很有趣的一件事情,我们在日常工作中可以按照t=s/v的思路来进行优化,也许你第一次运用它的时候有些陌生,但是只要不断的练习,善于总结,你也会发现其中的规律,真是妙哉妙哉。还有一点很重要的是,你的SQL优化不要脱离实际业务,也许你在哪里优化一条sql花了1个小时,但是去和开发同学讨论优化成果的时候,开发同学说这条sql其实可以下线了,那时候真的哭笑不得了 ^_^.

  • 相关阅读:
    BZOJ4240: 有趣的家庭菜园
    BZOJ1509: [NOI2003]逃学的小孩
    BZOJ5301: [Cqoi2018]异或序列
    BZOJ4540: [Hnoi2016]序列
    BZOJ4956: [Wf2017]Secret Chamber at Mount Rushmore
    BZOJ2141: 排队
    BZOJ1833: [ZJOI2010]count 数字计数
    HDU2089: 不要62
    BZOJ5178: [Jsoi2011]棒棒糖
    BZOJ3439: Kpm的MC密码
  • 原文地址:https://www.cnblogs.com/svennee/p/4080870.html
Copyright © 2011-2022 走看看